From d9034eab26e8abe8698a4de5feacd1be0ae8ecff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 22:54:54 +0300 Subject: [PATCH 001/102] Make model manager in `DownloadTrackingComposite` protected --- osu.Game/Online/DownloadTrackingComposite.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 7a64c9002d..4e52761813 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online protected readonly Bindable Model = new Bindable(); [Resolved(CanBeNull = true)] - private TModelManager manager { get; set; } + protected TModelManager Manager { get; private set; } /// /// Holds the current download state of the , whether is has already been downloaded, is in progress, or is not downloaded. @@ -49,19 +49,19 @@ namespace osu.Game.Online else if (manager?.IsAvailableLocally(modelInfo.NewValue) == true) State.Value = DownloadState.LocallyAvailable; else - attachDownload(manager?.GetExistingDownload(modelInfo.NewValue)); + attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue)); }, true); - if (manager == null) + if (Manager == null) return; - managerDownloadBegan = manager.DownloadBegan.GetBoundCopy(); + managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); managerDownloadBegan.BindValueChanged(downloadBegan); - managerDownloadFailed = manager.DownloadFailed.GetBoundCopy(); + managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); managerDownloadFailed.BindValueChanged(downloadFailed); - managedUpdated = manager.ItemUpdated.GetBoundCopy(); + managedUpdated = Manager.ItemUpdated.GetBoundCopy(); managedUpdated.BindValueChanged(itemUpdated); - managerRemoved = manager.ItemRemoved.GetBoundCopy(); + managerRemoved = Manager.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); } From 04d17aadfa4685f54e670b934e17fc3be3cf2b3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 22:57:55 +0300 Subject: [PATCH 002/102] Add overridable method for verifying models in database --- osu.Game/Online/DownloadTrackingComposite.cs | 42 +++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 4e52761813..9dd8258e78 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -65,6 +66,15 @@ namespace osu.Game.Online managerRemoved.BindValueChanged(itemRemoved); } + /// + /// Verifies that the given databased model is in a correct state to be considered available. + /// + /// + /// In the case of multiplayer/playlists, this has to verify that the databased beatmap set with the selected beatmap matches what's online. + /// + /// The model in database. + protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; + private void downloadBegan(ValueChangedEvent>> weakRequest) { if (weakRequest.NewValue.TryGetTarget(out var request)) @@ -134,23 +144,35 @@ namespace osu.Game.Online private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - setDownloadStateFromManager(item, DownloadState.LocallyAvailable); + { + Schedule(() => + { + if (!item.Equals(Model.Value)) + return; + + if (!VerifyDatabasedModel(item)) + { + State.Value = DownloadState.NotDownloaded; + return; + } + + State.Value = DownloadState.LocallyAvailable; + }); + } } private void itemRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - setDownloadStateFromManager(item, DownloadState.NotDownloaded); + { + Schedule(() => + { + if (item.Equals(Model.Value)) + State.Value = DownloadState.NotDownloaded; + }); + } } - private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() => - { - if (!s.Equals(Model.Value)) - return; - - State.Value = state; - }); - #region Disposal protected override void Dispose(bool isDisposing) From 7ad8b167ccb2f73247f2506cace66b22ec333665 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 22:58:29 +0300 Subject: [PATCH 003/102] Add overridable method for checking local availability of current model --- osu.Game/Online/DownloadTrackingComposite.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 9dd8258e78..1631d9790b 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online { if (modelInfo.NewValue == null) attachDownload(null); - else if (manager?.IsAvailableLocally(modelInfo.NewValue) == true) + else if (IsModelAvailableLocally()) State.Value = DownloadState.LocallyAvailable; else attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue)); @@ -75,6 +75,13 @@ namespace osu.Game.Online /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; + /// + /// Whether the given model is available in the database. + /// By default, this calls , + /// but can be overriden to add additional checks for verifying the model in database. + /// + protected virtual bool IsModelAvailableLocally() => Manager.IsAvailableLocally(Model.Value); + private void downloadBegan(ValueChangedEvent>> weakRequest) { if (weakRequest.NewValue.TryGetTarget(out var request)) From da9c23f3478356b9183fecbe82d0d416c039a26d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:00:56 +0300 Subject: [PATCH 004/102] Add beatmap availability tracker component for multiplayer --- .../Online/Rooms/MultiplayerBeatmapTracker.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs new file mode 100644 index 0000000000..b22d17f3ef --- /dev/null +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -0,0 +1,89 @@ +// 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.Linq; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; + +namespace osu.Game.Online.Rooms +{ + public class MultiplayerBeatmapTracker : DownloadTrackingComposite + { + public readonly IBindable SelectedItem = new Bindable(); + + /// + /// The availability state of the currently selected playlist item. + /// + public IBindable Availability => availability; + + private readonly Bindable availability = new Bindable(); + + public MultiplayerBeatmapTracker() + { + State.BindValueChanged(_ => updateAvailability()); + Progress.BindValueChanged(_ => updateAvailability()); + updateAvailability(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(item => Model.Value = item.NewValue?.Beatmap.Value.BeatmapSet, true); + } + + protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) + { + int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; + string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; + + BeatmapInfo matchingBeatmap; + + if (databasedSet.Beatmaps == null) + { + // The given databased beatmap set is not passed in a usable state to check with. + // Perform a full query instead, as per https://github.com/ppy/osu/pull/11415. + matchingBeatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + return matchingBeatmap != null; + } + + matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + return matchingBeatmap != null; + } + + protected override bool IsModelAvailableLocally() + { + int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; + string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; + + var beatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + return beatmap?.BeatmapSet.DeletePending == false; + } + + private void updateAvailability() + { + switch (State.Value) + { + case DownloadState.NotDownloaded: + availability.Value = BeatmapAvailability.NotDownloaded(); + break; + + case DownloadState.Downloading: + availability.Value = BeatmapAvailability.Downloading(Progress.Value); + break; + + case DownloadState.Importing: + availability.Value = BeatmapAvailability.Importing(); + break; + + case DownloadState.LocallyAvailable: + availability.Value = BeatmapAvailability.LocallyAvailable(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(State)); + } + } + } +} From cf2378103656863879a08b9e2e2704bdffdebd03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:02:30 +0300 Subject: [PATCH 005/102] Cache beatmap tracker and bind to selected item in `RoomSubScreen` --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2449563c73..c049d4be20 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -40,6 +40,17 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; + [Cached] + protected readonly MultiplayerBeatmapTracker BeatmapTracker; + + protected RoomSubScreen() + { + InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = SelectedItem }, + }; + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 96feaa027ded707f31ef18482aa0b632dad5627c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:06:54 +0300 Subject: [PATCH 006/102] Make `ArchiveModelManager` import method overridable (for testing purposes) --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 36cc4cce39..8502ab5965 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -308,7 +308,7 @@ namespace osu.Game.Database /// The model to be imported. /// An optional archive to use for model population. /// An optional cancellation token. - public async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => + public virtual async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { cancellationToken.ThrowIfCancellationRequested(); From 4778686dc4b712e7c93a30661718fd383674fee2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:07:46 +0300 Subject: [PATCH 007/102] Expose method for triggering filename-backed success in `APIDownloadRequest` Exactly like in `APIRequest` --- osu.Game/Online/API/APIDownloadRequest.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 940b9b4803..02c589403c 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -1,6 +1,7 @@ // 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.IO; using osu.Framework.IO.Network; @@ -28,13 +29,19 @@ namespace osu.Game.Online.API private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total)); - protected APIDownloadRequest() + internal void TriggerSuccess(string filename) { - base.Success += onSuccess; + if (this.filename != null) + throw new InvalidOperationException("Attempted to trigger success more than once"); + + this.filename = filename; + + TriggerSuccess(); } - private void onSuccess() + internal override void TriggerSuccess() { + base.TriggerSuccess(); Success?.Invoke(filename); } From 23c7afa573b6d9b8b0f4af49283224e2231394b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:17:05 +0300 Subject: [PATCH 008/102] Expose method for setting progress of archive download request --- osu.Game/Online/API/ArchiveDownloadRequest.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index f1966aeb2b..bb57a7a5f8 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -18,7 +18,13 @@ namespace osu.Game.Online.API { Model = model; - Progressed += (current, total) => DownloadProgressed?.Invoke(Progress = (float)current / total); + Progressed += (current, total) => SetProgress((float)current / total); + } + + protected void SetProgress(float progress) + { + Progress = progress; + DownloadProgressed?.Invoke(progress); } } } From adb2605d5d7ddb30fea80a5f289e28e3c357a8db Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:17:47 +0300 Subject: [PATCH 009/102] Enforce `double` type in the download progress path Wasn't sure where to exactly put this, or whether to split it, but it's very small change to worry about, so I guess it's fine being here --- osu.Game/Online/API/ArchiveDownloadRequest.cs | 8 ++++---- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index bb57a7a5f8..fdb2a984dc 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -10,18 +10,18 @@ namespace osu.Game.Online.API { public readonly TModel Model; - public float Progress; + public double Progress { get; private set; } - public event Action DownloadProgressed; + public event Action DownloadProgressed; protected ArchiveDownloadRequest(TModel model) { Model = model; - Progressed += (current, total) => SetProgress((float)current / total); + Progressed += (current, total) => SetProgress((double)current / total); } - protected void SetProgress(float progress) + protected void SetProgress(double progress) { Progress = progress; DownloadProgressed?.Invoke(progress); diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 1631d9790b..b72cf38369 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing); - private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); + private void onRequestProgress(double progress) => Schedule(() => Progress.Value = progress); private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 3105ecd742..e18bab8e83 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -23,9 +23,9 @@ namespace osu.Game.Overlays.Notifications public string CompletionText { get; set; } = "Task has completed!"; - private float progress; + private double progress; - public float Progress + public double Progress { get => progress; set @@ -185,9 +185,9 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourInactive; - private float progress; + private double progress; - public float Progress + public double Progress { get => progress; set @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Notifications if (progress == value) return; progress = value; - box.ResizeTo(new Vector2(progress, 1), 100, Easing.OutQuad); + box.ResizeTo(new Vector2((float)progress, 1), 100, Easing.OutQuad); } } From f0602243bfa792f3880e9d083f1ddc67431a29cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:30:01 +0300 Subject: [PATCH 010/102] Add beatmapset file containing the eaxct beatmap in `TestBeatmap` solely --- .../Resources/Archives/test-beatmap.osz | Bin 0 -> 7286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/test-beatmap.osz diff --git a/osu.Game.Tests/Resources/Archives/test-beatmap.osz b/osu.Game.Tests/Resources/Archives/test-beatmap.osz new file mode 100644 index 0000000000000000000000000000000000000000..f98fd2f8ffef0968270a1f2c7d75162de80417c9 GIT binary patch literal 7286 zcma)BRa6vEy9JRFrMo+%k?uwux?|{W36WAjVu%48V(9MfMi2(1bCB+C7(hBM|GH22 z;a}^X?_r<)?fvbCv(DS60YG_8h=hcMf%K5attHDLX3|4}gk)-mgv9pJ1$?ozpjCJA zq~)Yl^`%vD^R}U7)u*+mb)fa4wPB+*{%GN0ZNlZ|`6UME6i3;d^vEUsoGWD;yuxeN zrxX}n;Y@i%il;aKu13ZjT%i0_E=9A`R2 z49_mB7vFE56!#aae;sc(YxI9S_*dTbh`9M2(ANHZf2iN}e22L7@$b05DF(q@HD1)O z>iuTw{ee&0m-`W7Gle&If1BgXXtJavp9s3TxmQ-d-30``l;qKh)YB!HV*D%3_f`qX zj946+?=W@Oo= zJMQmqFOCCc67N5qAwvCePmcG8FP34T&HI;7HJoSZ`xxKHgHgoE?eYFBJh{IZhP`>a z3E$tmhd)zo<3xQ6^b>x%-rOuk_^#4qpGvMstUhLrj$ik~uDgz1X#ypnQqLFbm%%-1 zUjv^n!6Y3>NIi$s1zw*V z!+4xP79I{&y*nrk;JO z-d`Nj>F(O`m<$J*ZGmq82HyO;IeS2@e%zrjJYH^pc_st{^YGEPD4FB@cQOcd3DTIZ zf4JL!gjPq=YTAF@AKx@+x3B&Rwtd{hJh(=frAbY;uRbm=?{_ZSdw2PNz1{}2Pv-4z z!fPMTt~*+Xqn;Gd(E56ee))@UuSf2U^{4Y3Ew3CQ(8KHcg)=;0PiBo;+nu^MQY*77 z)7K`gQjGaVUDre5GpBMNM|VL5>Er5>B|DF}+*wkI_eMC$D4(@Mx+uG+VWRMWu%-}~ zE-RkW$Jp-uE3qH0ICAK4G9|4{cgAIEkY=d_cD}AZ<)6?iVX*SCJ882)tOmI=sW|zF zqlpXu%FH{MkCgAK*6Vg40zqW(c2TdpPy_Pra~3SerGnyN+B`gW9#+1VF43XZG|#~5 zk}mP1$)%KMzipo3Jx#g<{gZ${W8uQ)$CNAYxUIC!wcqKRXYc>)2#f2qiC|afyT-!g zh|)ICm>Xtci@Cq3SMIL-e1u+z*s3WG4rr3PpXY^GksiC>yr|S=i_}V=Gye^E!}su9 z>?F*ej@=m-4db#(`?6At3scA6ZG4)!%J%cl^5dRuv)ePe=Abg|vO2KepA&s0+dnrs zRqI{pzY`0M{gq#6b@n1tb5bR|I4|t?NhOR#^{Q3Jk6ujvE`H&0(AaG!_=j%|Ue0Fe zS%??^##1sN);Sz{sBq}MLo<$%H=*O~$d!pFy-ypmA#UgQ1sRt3) zR?L5AN%!pAsrXg39xMkhlhtHsdTw?h*N2(=HQ}m^6WdE3BVI4ohpoL(;5f0Y z5H!@419RB`zpa+h%Sw6gPy9p-Q!WMI{|Mez%13|VOSFsts`rr?S5BB7!?-5J>UD>R zkJ0;vR!53Ob1^El6seX%e0{FgQOg5_?GQF^2JQbUXh}G!l-b27Usv$%X9;LET=~Sm z;G)|&7?pX`*@mp%x5@l9c7?ilN&|QFc+Nfj)rqrxWzBm zaV`Jf=h0B*A2#Cu5?%*klW5)VOMG8v>v_luE5^9+CngmfIW;t&5hVUg}jv;&=dt8EnWs_9`-5H&7fV0f9Wt9GpIm4u9^J zrfNlAej9dYmzAfzLoSkoVcp&Z9N14jIaRNyM=pA5xDnDCcWTHG4<1W9V16^eDdF@w z=&;h^$T0tRjIMP&l$ynQRycg9A9#O-ctRW!I2o);e!oL#o??qp(SjBk;O~WEmKywfVEl>c z#6rF(Yk+zU4k44))TQsFTeqv3SN<5^K-h8PT(vmHP$Vr8!J+@N+Eivpe0z+^bnL`Pw0> z2F;d@R!L$=YTy~SyV^EraI^2~h`(ycRrz=#50Ons%yyMtk9B~w&PCU0&|vGd^Aq}&{wA+Kl>sS&|>JEoRJ_kC&rY)Ms$ZH$7? z**%RZS2eHN0Ti&T;!V~HSn?Yy4Kos-kp)@madT#s4xljQr5K+Jr>+87iAGbh_b!kc zeTu^vfoAU;ZqmXSYd)7HwIp(2WN_QMYGX&lUT>c}3346gC>huvW`KJJC>~q=exK9j z>$raOW8+mFuaQWb^x+`HMG5*BLFDqDUeVhA^~OR}5V9aS7{k8MIt!Jb^AKbY#d?oj zGuHy&a~qc_U|m+N7dw%&Y22br-t6g(2*hRaZ4Ju69E;!qN6+Vky9?%-I^f$tn)`-E zo*$F38G^SOn*{yN0s6@lS1@L^s{ z1bFLXbP~!+P!g}JT5b%xU84xj8FWf-$5QX14@)@mmMMh7MIAnjV+!P2{s#@}>^(Nu z(^TO=_ap{lS~c$;eW?NzGJwFQ?%syI$VO*D$slLtqPMYS+9;0d%pR;0bo)?KF_^RP+qDmha-?EwPh=MRxXnbUC}H7q zXtD_tq1S2{$5$$QN=x3l{&YBR$JGX+XOeQLvE7xZsz{Sz4Vp!0uRJda!HooHD8fCo z0x}a`g^4F^iLu<>I4&^_{O`U9FS4oyc8aowsbgeE1iBj!R|X-AHzPNKu~H^Oo8CAX zDqNzyqamfJ#=@q1UAQHoG;g-R#{64C!%Dd7+ln^FV93TCSu5`l88fwb9Aqf6l4lwxKrtvFP$vz6oHMM zHzbu8daGH)(2~YUY#Iiv2z$!;#)j*WFr@#*uD5)(V4Hl1pE2iB zt(IejY^`S| zJ+hzt>K`v+LwN~iuG(>sl1KGj^2r>$$90zz9c2o{?{MsY@Mjd zY9#!}Q*Im*zF`y!nXMj_Tf;q{O z{K~I%|6*3dcOt>;R^)|g_@JW;u(53E@T7qx1imsrTEXOx5j;g{R}n5B@+!Q4im_3E zT4`xSg>E$X&pSQ(7*AVlfnz+wDio1=(F9W1w^s5x;c|JGT2qyh$PLB>(swGFx=coY zFiBb!r)Xtjv<6He5b7kKpz@#L^}Y*9orn0>0qoU0pCW|Jc8QSCJ`#lz@n@vDo6~2! zb)lpEg-_s9N@r5E7SG|aMbPLEaMWMw31`L>Lx&tL^~m_U5DE&qq=UMR`A9*l>s>k-X|$~9QcPz+{FE2Raq#U7v4;C0K8cBuUxgd>Wu>C+HaHP6lAH`Uyu01tL5 zYi0$M!TW}POj+9TO(#~W>!cPXy@Lnrk<|lWA6$wtY6yB|E&XS6$m=)qh}n9xS61d% zL&0!{iE3$?Y91^vK*rGA+3JrCT(Fhua>T_qnI`s!0vqJ~ymZwnb_}+THyqJV-4O@9 zQ1+|^+pBP$rlhwanp&g!ayveM+*P?kOHk^H(83r>C2WOtV`uj$H~ZOtSi=xpD>A=A z`;$IW*~B@j1gvCgf+!j2?A$LH^mS88)iH52uZ7_M0sg4eux(!kau|ep={G@ zmFVEJ+WHQ=Ws#UUiKOjscjduvTmh3WSrXm2JMef*eXM+pF}tuMVY6eL}O4~m@zZt;7+8mD!;w!Dx96xn~D1xug&xoDmCAJrJ6)5oP zo!XTtCKJO3HHh38qs#>j@c;|DLt6r#)=L8CghkqSf44LfH<*zVR(4{AWyx$p#D0iO zB$TF!@*&omTXBW}mxbgbkeEvf`|=cqqO+AZ6>8}tX&QPuB)WJ?X+74z+M~1~e}pkiApwpZQzd9I5qV_?es=hPR7{ASDxLDa zDUz!Ky>f7YrL>W{&9MRj+W}e}KP%f|j*D$PFan)%X>2|uX7NEQX6GkhO!DVCR4{9;BCW@vjJ4CZqho_ zQbQFpbyE1lr@A9Jhag$ueyTumVt`B(g_i%+DD4QDzjm6!tkq#c~7FGyKDPs0Y(z2bUH%6r65k zKU}l{Wo$Q3O5cv*4=!bH-BOZq^d1EC3R9Hs>G)WVa?SHfrs?gVFxjT?eAz+apkox> zuTHK3Y*e;H6?Jp-(hb9^F_`Ch?F3stF=zSCeL0aETf7}(mwif2t;1L5);ZyGL!HC8{A@16vq16+H)leRi|-wiH!j#3JqCD|_(;i+=72m|m-9`qlbDh@m)cbu>B4G0&^zdY!jBzF!nX_6bZ^-Bo`C1|? zlAP+f#lmeQfR7L0EcM8b9q~3^yQ*kJHO(UBiFVv(`kJwo4Z)~|hys})&{=dsUr1NXWsJaw9D&KEb zH-0_|=4-2>ICy-fJJigAVEb^w2BSW_uAu4GUoQ1v?*s zq6H`}a`fSNq+eWvn!cQKT=9M9sgV}6C%OC(vL_A=BNtNw$(rGkd z()vlm@!efXjzyl>%GTYD0n2VuX@w_T_rTbiuZiCgzey$pLj;B%8ifXNA?1)#?&&Di zWFnhOj>|}VnyG6GeS!seROA~~?5>f}={3oLFhv+Yvd5K0A!!O9kT^@>F6jGJN4Ik9)Rey8;p_Lfu+sfE z2e4$5OPO1sl7r!bwcX+%X&BTn!z?won6_&2Hg0s<+qB58lEz3SXAujcKnve1n9BtT z+i6Z;##pSG7U`7aW)e8$EBx>0-GtSdZx6QIU6Guo`Ofwb?ePoSxz{<(6TXc|JY4=M z1_<)dId^!cVFR6I1T3JFZ3jXoqcQu4%@8#$N#8T%ah^cwF`tYBBfN`ax$_dqJ`kEU z$LH2j@g=-s1Rhnop2ye4BxE$EH-|=YZe5fw7G#dzUrf^J7MO?^0Gm~H8!cUw!I7_? ztU{=Bwee&*i-uNrLyYWL66&UJi@Io5Av!>|UlTuYd3BXUAvaVH%+fzAjRP!op#mJp z?^#e=*?DAYSlZJY)$OL=#`liqh|uPEX19n6Nc_Tq`hCR`JOe8uEm$p))r zOiZ%sOJYLyVW z8B*zN3yP>!Wq6v5RGCQ94qQU3PdPqHi-?UiwPL)#lTxi-pp7z-Oek~AaJ@hV>LFLD zR3Ft1`OEG4vBcks{~80Pn>xj-R0k|u-7N+E<@>d?^N_dy&Uaj}V{ET>72^)mwj*&= z&-hKo)^CGb>WOpfU}(R-7!&%5juJl49LWiVO^L3qE?+jYoY3&EeQ#}$>WU;trIOyI zfg@I4%xa3+9BT3bSioc+hg_={&C*c?Q%f?e`rJHhT(DbiqJH@)+2(M*Ga%01#DCn= zgott7Nn|ky^RL(S7b7hTk?G1Zf=8p>ee9D3FTlE1Ct6-l06C+B-jt_}^V09B)DzO- zS$VG_Ms8vAEbg45!@7|3jS9_4f^Y5vy0K2}(VUH(kE4X+odFN_wIV^Fl$$lj1jfu= zfcWPbATyV4?$7)40_kDS#$-Es{l(LWlb|3l?oR$ynlxO}805uGOtL8kFJ;W56EGyrTokh8*F&{bI2>klcg4X*6#E1#mI10X0+_3?otJyOxY zU-a3NU4rck(D|6ZlT5d-Wq+L9_9M+p2>$tLA*vx{AZm?Lk^lqoLEoi>1)b(qd!LTmV{}ISOMd=HL zNP^L=?uscIm7MtqAlMqPEv1~iuR<~dK-8M!FV`I!YSqJ_%&48u2(ox3qxR})tL4go zA!SKXZOJnJ=e=iS4FK{hLZtt`3VhiH{_hF?5B$FugBk!-wEqkszm)z9Vp9Hx{SV^2 BJ!=2} literal 0 HcmV?d00001 From 80f7db8db364dc4903bf2ec4888b1269e82cde3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:31:15 +0300 Subject: [PATCH 011/102] Add test coverage for the new multiplayer beatmap tracker --- .../TestSceneMultiplayerBeatmapTracker.cs | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs new file mode 100644 index 0000000000..839d47afc2 --- /dev/null +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -0,0 +1,178 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Online +{ + [HeadlessTest] + public class TestSceneMultiplayerBeatmapTracker : OsuTestScene + { + private RulesetStore rulesets; + private TestBeatmapManager beatmaps; + + private string testBeatmapFile; + private BeatmapInfo testBeatmapInfo; + private BeatmapSetInfo testBeatmapSet; + + private readonly Bindable selectedItem = new Bindable(); + private MultiplayerBeatmapTracker tracker; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, GameHost host) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, host, Beatmap.Default)); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + testBeatmapFile = getTestBeatmapOsz(); + + testBeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo; + testBeatmapSet = testBeatmapInfo.BeatmapSet; + + var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); + if (existing != null) + beatmaps.Delete(existing); + + selectedItem.Value = new PlaylistItem + { + Beatmap = { Value = testBeatmapInfo }, + Ruleset = { Value = testBeatmapInfo.Ruleset }, + }; + + Child = tracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = selectedItem, } + }; + }); + + [Test] + public void TestBeatmapDownloadingFlow() + { + AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); + addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); + + AddStep("start downloading", () => beatmaps.Download(testBeatmapSet)); + addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0)); + + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4)); + addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4)); + + AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); + addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); + + AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddUntilStep("wait for import", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + } + + [Test] + public void TestTrackerRespectsSoftDeleting() + { + AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapSet).Wait()); + addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + + AddStep("delete beatmap", () => beatmaps.Delete(testBeatmapSet)); + addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); + + AddStep("undelete beatmap", () => beatmaps.Undelete(testBeatmapSet)); + addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + } + + [Test] + public void TestTrackerRespectsChecksum() + { + AddStep("allow importing", () => beatmaps.AllowImport.Set()); + + BeatmapInfo wrongBeatmap = null; + + AddStep("import wrong checksum beatmap", () => + { + wrongBeatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo; + wrongBeatmap.MD5Hash = "1337"; + + beatmaps.Import(wrongBeatmap.BeatmapSet).Wait(); + }); + AddAssert("wrong beatmap available", () => beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == wrongBeatmap.OnlineBeatmapID) != null); + addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); + + AddStep("recreate tracker", () => Child = tracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = selectedItem } + }); + addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); + } + + private void addAvailabilityCheckStep(string description, Func expected) + { + AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); + } + + private string getTestBeatmapOsz() + { + var filename = Path.GetTempFileName() + ".osz"; + + using (var stream = TestResources.OpenResource("Archives/test-beatmap.osz")) + using (var file = File.Create(filename)) + stream.CopyTo(file); + + return filename; + } + + private class TestBeatmapManager : BeatmapManager + { + public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim(); + + protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) + => new TestDownloadRequest(set); + + public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) + : base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, performOnlineLookups) + { + } + + public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) + { + while (!AllowImport.IsSet) + await Task.Delay(10, cancellationToken); + + return await base.Import(item, archive, cancellationToken); + } + } + + private class TestDownloadRequest : ArchiveDownloadRequest + { + public new void SetProgress(double progress) => base.SetProgress(progress); + + public TestDownloadRequest(BeatmapSetInfo model) + : base(model) + { + } + + protected override string Target => null; + } + } +} From 59ae50b0e58d1ff813498093b39c60c73b3e790f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:02:30 +0300 Subject: [PATCH 012/102] Clean up ready button logic into using `MultiplayerBeatmapTracker` --- .../OnlinePlay/Components/ReadyButton.cs | 62 +++---------------- .../OnlinePlay/Match/Components/Footer.cs | 4 -- .../Match/MultiplayerMatchFooter.cs | 5 -- .../Match/MultiplayerReadyButton.cs | 3 - .../Multiplayer/MultiplayerMatchSubScreen.cs | 5 +- .../Playlists/PlaylistsReadyButton.cs | 6 +- .../Playlists/PlaylistsRoomSubScreen.cs | 5 +- 7 files changed, 16 insertions(+), 74 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 64ddba669d..d4e34b5331 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -1,77 +1,29 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public abstract class ReadyButton : TriangleButton { - public readonly Bindable SelectedItem = new Bindable(); - public new readonly BindableBool Enabled = new BindableBool(); - [Resolved] - protected IBindable GameBeatmap { get; private set; } - - [Resolved] - private BeatmapManager beatmaps { get; set; } - - private bool hasBeatmap; - - private IBindable> managerUpdated; - private IBindable> managerRemoved; + private IBindable availability; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(MultiplayerBeatmapTracker beatmapTracker) { - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); - managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(beatmapRemoved); + availability = beatmapTracker.Availability.GetBoundCopy(); - SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true); + availability.BindValueChanged(_ => updateState()); + Enabled.BindValueChanged(_ => updateState(), true); } - private void updateSelectedItem(PlaylistItem _) => Scheduler.AddOnce(updateBeatmapState); - private void beatmapUpdated(ValueChangedEvent> _) => Scheduler.AddOnce(updateBeatmapState); - private void beatmapRemoved(ValueChangedEvent> _) => Scheduler.AddOnce(updateBeatmapState); - - private void updateBeatmapState() - { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash; - - if (beatmapId == null || checksum == null) - return; - - var databasedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - - hasBeatmap = databasedBeatmap?.BeatmapSet?.DeletePending == false; - } - - protected override void Update() - { - base.Update(); - - updateEnabledState(); - } - - private void updateEnabledState() - { - if (GameBeatmap.Value == null || SelectedItem.Value == null) - { - base.Enabled.Value = false; - return; - } - - base.Enabled.Value = hasBeatmap && Enabled.Value; - } + private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; } } diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs b/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs index 5c27d78d50..e91c46beed 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs @@ -3,13 +3,11 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Playlists; using osuTK; @@ -20,7 +18,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public const float HEIGHT = 50; public Action OnStart; - public readonly Bindable SelectedItem = new Bindable(); private readonly Drawable background; @@ -37,7 +34,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(600, 50), - SelectedItem = { BindTarget = SelectedItem }, Action = () => OnStart?.Invoke() } }; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index bbf861fac3..fdc1ae9d3c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs @@ -3,13 +3,11 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -18,8 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public const float HEIGHT = 50; - public readonly Bindable SelectedItem = new Bindable(); - public Action OnReadyClick { set => readyButton.OnReadyClick = value; @@ -41,7 +37,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(600, 50), - SelectedItem = { BindTarget = SelectedItem } } }; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 04030cdbfd..389a2380fa 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osuTK; @@ -21,8 +20,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerReadyButton : MultiplayerRoomComposite { - public Bindable SelectedItem => button.SelectedItem; - public Action OnReadyClick { set => button.Action = value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 80991569dc..a641935b9a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { new GridContainer { @@ -161,7 +161,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { new MultiplayerMatchFooter { - SelectedItem = { BindTarget = SelectedItem }, OnReadyClick = onReadyClick } } @@ -177,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index edee8e571a..9ac1fe1722 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -15,6 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(typeof(Room), nameof(Room.EndDate))] private Bindable endDate { get; set; } + [Resolved] + private IBindable gameBeatmap { get; set; } + public PlaylistsReadyButton() { Text = "Start"; @@ -32,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { base.Update(); - Enabled.Value = endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(GameBeatmap.Value.Track.Length) < endDate.Value; + Enabled.Value = endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index e76ca995bf..7b3cdf16db 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { new GridContainer { @@ -173,7 +173,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new Footer { OnStart = onStart, - SelectedItem = { BindTarget = SelectedItem } } } }, @@ -189,7 +188,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); } [Resolved] From 63c0dc9bd9cf9d7c7f7251aa91d778c23a021071 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:04:28 +0300 Subject: [PATCH 013/102] Update ready button test scene with new logic --- .../TestSceneMultiplayerReadyButton.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 878776bf51..1357adb39a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -26,8 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerReadyButton : MultiplayerTestScene { private MultiplayerReadyButton button; + private MultiplayerBeatmapTracker beatmapTracker; private BeatmapSetInfo importedSet; + private readonly Bindable selectedItem = new Bindable(); + private BeatmapManager beatmaps; private RulesetStore rulesets; @@ -39,6 +43,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); + + Add(beatmapTracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = selectedItem } + }); + + Dependencies.Cache(beatmapTracker); } [SetUp] @@ -46,20 +57,20 @@ namespace osu.Game.Tests.Visual.Multiplayer { importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + selectedItem.Value = new PlaylistItem + { + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }, + }; - Child = button = new MultiplayerReadyButton + if (button != null) + Remove(button); + + Add(button = new MultiplayerReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - SelectedItem = - { - Value = new PlaylistItem - { - Beatmap = { Value = Beatmap.Value.BeatmapInfo }, - Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset } - } - }, OnReadyClick = async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); @@ -73,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await Client.ToggleReady(); readyClickOperation.Dispose(); } - }; + }); }); [Test] From 172552d55173791a5a5293bcab1d2240b8b02783 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 21:02:33 +0300 Subject: [PATCH 014/102] Use `TaskCompletionSource` for better awaiting --- .../Online/TestSceneMultiplayerBeatmapTracker.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 839d47afc2..05e8f8b2e4 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -47,6 +47,8 @@ namespace osu.Game.Tests.Online [SetUp] public void SetUp() => Schedule(() => { + beatmaps.AllowImport = new TaskCompletionSource(); + testBeatmapFile = getTestBeatmapOsz(); testBeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo; @@ -83,7 +85,7 @@ namespace osu.Game.Tests.Online AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); - AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddUntilStep("wait for import", () => beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } @@ -91,7 +93,7 @@ namespace osu.Game.Tests.Online [Test] public void TestTrackerRespectsSoftDeleting() { - AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("import beatmap", () => beatmaps.Import(testBeatmapSet).Wait()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); @@ -105,7 +107,7 @@ namespace osu.Game.Tests.Online [Test] public void TestTrackerRespectsChecksum() { - AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); BeatmapInfo wrongBeatmap = null; @@ -144,7 +146,7 @@ namespace osu.Game.Tests.Online private class TestBeatmapManager : BeatmapManager { - public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim(); + public TaskCompletionSource AllowImport = new TaskCompletionSource(); protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => new TestDownloadRequest(set); @@ -156,9 +158,7 @@ namespace osu.Game.Tests.Online public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) { - while (!AllowImport.IsSet) - await Task.Delay(10, cancellationToken); - + await AllowImport.Task; return await base.Import(item, archive, cancellationToken); } } From d93a853dfd531e9d8d83473b04e3c22b5d290023 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 21:16:45 +0300 Subject: [PATCH 015/102] Enforce `float` type in the download progress path instead --- .../Online/TestSceneMultiplayerBeatmapTracker.cs | 8 ++++---- osu.Game/Online/API/ArchiveDownloadRequest.cs | 6 +++--- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- osu.Game/Online/Rooms/BeatmapAvailability.cs | 6 +++--- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 10 +++++----- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 05e8f8b2e4..60a4508b3d 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -77,10 +77,10 @@ namespace osu.Game.Tests.Online addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); AddStep("start downloading", () => beatmaps.Download(testBeatmapSet)); - addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0)); + addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f)); - AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4)); - addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4)); + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f)); + addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f)); AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Online private class TestDownloadRequest : ArchiveDownloadRequest { - public new void SetProgress(double progress) => base.SetProgress(progress); + public new void SetProgress(float progress) => base.SetProgress(progress); public TestDownloadRequest(BeatmapSetInfo model) : base(model) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index fdb2a984dc..ccb4e9c119 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -12,16 +12,16 @@ namespace osu.Game.Online.API public double Progress { get; private set; } - public event Action DownloadProgressed; + public event Action DownloadProgressed; protected ArchiveDownloadRequest(TModel model) { Model = model; - Progressed += (current, total) => SetProgress((double)current / total); + Progressed += (current, total) => SetProgress((float)current / total); } - protected void SetProgress(double progress) + protected void SetProgress(float progress) { Progress = progress; DownloadProgressed?.Invoke(progress); diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index b72cf38369..1631d9790b 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing); - private void onRequestProgress(double progress) => Schedule(() => Progress.Value = progress); + private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index e7dbc5f436..170009a85b 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -19,17 +19,17 @@ namespace osu.Game.Online.Rooms /// /// The beatmap's downloading progress, null when not in state. /// - public readonly double? DownloadProgress; + public readonly float? DownloadProgress; [JsonConstructor] - private BeatmapAvailability(DownloadState state, double? downloadProgress = null) + private BeatmapAvailability(DownloadState state, float? downloadProgress = null) { State = state; DownloadProgress = downloadProgress; } public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded); - public static BeatmapAvailability Downloading(double progress) => new BeatmapAvailability(DownloadState.Downloading, progress); + public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress); public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing); public static BeatmapAvailability LocallyAvailable() => new BeatmapAvailability(DownloadState.LocallyAvailable); diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index b22d17f3ef..b15399ad62 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online.Rooms break; case DownloadState.Downloading: - availability.Value = BeatmapAvailability.Downloading(Progress.Value); + availability.Value = BeatmapAvailability.Downloading((float)Progress.Value); break; case DownloadState.Importing: diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index e18bab8e83..3105ecd742 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -23,9 +23,9 @@ namespace osu.Game.Overlays.Notifications public string CompletionText { get; set; } = "Task has completed!"; - private double progress; + private float progress; - public double Progress + public float Progress { get => progress; set @@ -185,9 +185,9 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourInactive; - private double progress; + private float progress; - public double Progress + public float Progress { get => progress; set @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Notifications if (progress == value) return; progress = value; - box.ResizeTo(new Vector2((float)progress, 1), 100, Easing.OutQuad); + box.ResizeTo(new Vector2(progress, 1), 100, Easing.OutQuad); } } From 0425a659a8c0bae3117e77dd512e899ca6a5c3da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 21:19:55 +0300 Subject: [PATCH 016/102] Add null-permissive operator to manager back --- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 1631d9790b..188cb9be7a 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online /// By default, this calls , /// but can be overriden to add additional checks for verifying the model in database. /// - protected virtual bool IsModelAvailableLocally() => Manager.IsAvailableLocally(Model.Value); + protected virtual bool IsModelAvailableLocally() => Manager?.IsAvailableLocally(Model.Value) == true; private void downloadBegan(ValueChangedEvent>> weakRequest) { From ccef50e2a2e16767565c75bdc70c52faba7c57e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 22:04:12 +0300 Subject: [PATCH 017/102] Log important message if user imported bad-checksum beatmap --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index b15399ad62..659f52f00f 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Game.Beatmaps; namespace osu.Game.Online.Rooms @@ -34,6 +35,15 @@ namespace osu.Game.Online.Rooms } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) + { + var verified = verifyDatabasedModel(databasedSet); + if (!verified) + Logger.Log("The imported beatmapset does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + + return verified; + } + + private bool verifyDatabasedModel(BeatmapSetInfo databasedSet) { int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; From b6a37c1c15fe2a95b7efb94b61e37de132d9864e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 22:08:28 +0300 Subject: [PATCH 018/102] Make `TriggerSuccess(filename)` protected and expose in test instead --- osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs | 1 + osu.Game/Online/API/APIDownloadRequest.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 60a4508b3d..4b8992052e 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -166,6 +166,7 @@ namespace osu.Game.Tests.Online private class TestDownloadRequest : ArchiveDownloadRequest { public new void SetProgress(float progress) => base.SetProgress(progress); + public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename); public TestDownloadRequest(BeatmapSetInfo model) : base(model) diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 02c589403c..62e22d8f88 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.API private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total)); - internal void TriggerSuccess(string filename) + protected void TriggerSuccess(string filename) { if (this.filename != null) throw new InvalidOperationException("Attempted to trigger success more than once"); From ddcfd854bd1c66b673d73e5459526ded76ebcb41 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 19:20:19 +0300 Subject: [PATCH 019/102] Wait for scheduled state changes before asserting --- osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 4b8992052e..d692e6f9a3 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -130,6 +130,8 @@ namespace osu.Game.Tests.Online private void addAvailabilityCheckStep(string description, Func expected) { + // In DownloadTrackingComposite, state changes are scheduled one frame later, wait one step. + AddWaitStep("wait for potential change", 1); AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); } From 25f511fd5b2b6b2bfbd9e1787182a8a87f086de0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 21:34:24 +0300 Subject: [PATCH 020/102] Remove unnecessary full querying --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index 659f52f00f..c2c800badc 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -48,17 +48,7 @@ namespace osu.Game.Online.Rooms int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - BeatmapInfo matchingBeatmap; - - if (databasedSet.Beatmaps == null) - { - // The given databased beatmap set is not passed in a usable state to check with. - // Perform a full query instead, as per https://github.com/ppy/osu/pull/11415. - matchingBeatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - return matchingBeatmap != null; - } - - matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); return matchingBeatmap != null; } From 5e476fa189d7637db79c228721a601a005f2d355 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 22:07:25 +0300 Subject: [PATCH 021/102] Enforce one missed property back to single-floating type --- osu.Game/Online/API/ArchiveDownloadRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index ccb4e9c119..0bf238109e 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -10,7 +10,7 @@ namespace osu.Game.Online.API { public readonly TModel Model; - public double Progress { get; private set; } + public float Progress { get; private set; } public event Action DownloadProgressed; From 63ca9de7e4a85990bd162d25cd9be01f8c91c239 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 23:00:07 +0300 Subject: [PATCH 022/102] Rewerite beatmap term properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index c2c800badc..32a6c39ea8 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.Rooms { var verified = verifyDatabasedModel(databasedSet); if (!verified) - Logger.Log("The imported beatmapset does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); return verified; } From 5a64abee648f7c49f92305f6ba988c19199e9f64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 11:51:31 +0300 Subject: [PATCH 023/102] Inline with above method --- .../Online/Rooms/MultiplayerBeatmapTracker.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index 32a6c39ea8..59dfdcf464 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -35,21 +35,19 @@ namespace osu.Game.Online.Rooms } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) - { - var verified = verifyDatabasedModel(databasedSet); - if (!verified) - Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); - - return verified; - } - - private bool verifyDatabasedModel(BeatmapSetInfo databasedSet) { int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - return matchingBeatmap != null; + + if (matchingBeatmap == null) + { + Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + return false; + } + + return true; } protected override bool IsModelAvailableLocally() From 63b4c529a6fe1f3a996f6074bfb736144e2e9c76 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 11:57:40 +0300 Subject: [PATCH 024/102] Add xmldoc explaining what the multiplayer beatmap tracker is for --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index 59dfdcf464..28e4872ad3 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -9,6 +9,12 @@ using osu.Game.Beatmaps; namespace osu.Game.Online.Rooms { + /// + /// Represent a checksum-verifying beatmap availability tracker usable for online play screens. + /// + /// This differs from a regular download tracking composite as this accounts for the + /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. + /// public class MultiplayerBeatmapTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); From ed3dece9f8f7c0c0e0d35ac3fa330f2359b84cbc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 18:36:05 +0300 Subject: [PATCH 025/102] Fix wrong importing of test beatmaps Importing via `testBeatmapSet` causes the beatmapset hash to not be calculated due to no files existing in the importing process, which leads into not reusing existing test beatmaps due to no hash. --- osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index d692e6f9a3..a6275f14e6 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -94,13 +94,13 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsSoftDeleting() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddStep("import beatmap", () => beatmaps.Import(testBeatmapSet).Wait()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); - AddStep("delete beatmap", () => beatmaps.Delete(testBeatmapSet)); + AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("undelete beatmap", () => beatmaps.Undelete(testBeatmapSet)); + AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } From dba01cf2b1ea7cf3445f14e30855ce9e95e6bd76 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 18:43:16 +0300 Subject: [PATCH 026/102] Use beatmap "soleily" and remove no longer needed archive --- .../TestSceneMultiplayerBeatmapTracker.cs | 41 ++++++++++-------- .../Resources/Archives/test-beatmap.osz | Bin 7286 -> 0 bytes 2 files changed, 24 insertions(+), 17 deletions(-) delete mode 100644 osu.Game.Tests/Resources/Archives/test-beatmap.osz diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index a6275f14e6..9caecc198a 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -10,15 +11,17 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Database; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -49,9 +52,9 @@ namespace osu.Game.Tests.Online { beatmaps.AllowImport = new TaskCompletionSource(); - testBeatmapFile = getTestBeatmapOsz(); + testBeatmapFile = TestResources.GetTestBeatmapForImport(); - testBeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo; + testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); @@ -109,16 +112,10 @@ namespace osu.Game.Tests.Online { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - BeatmapInfo wrongBeatmap = null; - - AddStep("import wrong checksum beatmap", () => + AddStep("import altered beatmap", () => { - wrongBeatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo; - wrongBeatmap.MD5Hash = "1337"; - - beatmaps.Import(wrongBeatmap.BeatmapSet).Wait(); + beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); }); - AddAssert("wrong beatmap available", () => beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == wrongBeatmap.OnlineBeatmapID) != null); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); AddStep("recreate tracker", () => Child = tracker = new MultiplayerBeatmapTracker @@ -135,15 +132,25 @@ namespace osu.Game.Tests.Online AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); } - private string getTestBeatmapOsz() + private static BeatmapInfo getTestBeatmapInfo(string archiveFile) { - var filename = Path.GetTempFileName() + ".osz"; + BeatmapInfo info; - using (var stream = TestResources.OpenResource("Archives/test-beatmap.osz")) - using (var file = File.Create(filename)) - stream.CopyTo(file); + using (var archive = new ZipArchiveReader(File.OpenRead(archiveFile))) + using (var stream = archive.GetStream("Soleily - Renatus (Gamu) [Insane].osu")) + using (var reader = new LineBufferedReader(stream)) + { + var decoder = Decoder.GetDecoder(reader); + var beatmap = decoder.Decode(reader); - return filename; + info = beatmap.BeatmapInfo; + info.BeatmapSet.Beatmaps = new List { info }; + info.BeatmapSet.Metadata = info.Metadata; + info.MD5Hash = stream.ComputeMD5Hash(); + info.Hash = stream.ComputeSHA2Hash(); + } + + return info; } private class TestBeatmapManager : BeatmapManager diff --git a/osu.Game.Tests/Resources/Archives/test-beatmap.osz b/osu.Game.Tests/Resources/Archives/test-beatmap.osz deleted file mode 100644 index f98fd2f8ffef0968270a1f2c7d75162de80417c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7286 zcma)BRa6vEy9JRFrMo+%k?uwux?|{W36WAjVu%48V(9MfMi2(1bCB+C7(hBM|GH22 z;a}^X?_r<)?fvbCv(DS60YG_8h=hcMf%K5attHDLX3|4}gk)-mgv9pJ1$?ozpjCJA zq~)Yl^`%vD^R}U7)u*+mb)fa4wPB+*{%GN0ZNlZ|`6UME6i3;d^vEUsoGWD;yuxeN zrxX}n;Y@i%il;aKu13ZjT%i0_E=9A`R2 z49_mB7vFE56!#aae;sc(YxI9S_*dTbh`9M2(ANHZf2iN}e22L7@$b05DF(q@HD1)O z>iuTw{ee&0m-`W7Gle&If1BgXXtJavp9s3TxmQ-d-30``l;qKh)YB!HV*D%3_f`qX zj946+?=W@Oo= zJMQmqFOCCc67N5qAwvCePmcG8FP34T&HI;7HJoSZ`xxKHgHgoE?eYFBJh{IZhP`>a z3E$tmhd)zo<3xQ6^b>x%-rOuk_^#4qpGvMstUhLrj$ik~uDgz1X#ypnQqLFbm%%-1 zUjv^n!6Y3>NIi$s1zw*V z!+4xP79I{&y*nrk;JO z-d`Nj>F(O`m<$J*ZGmq82HyO;IeS2@e%zrjJYH^pc_st{^YGEPD4FB@cQOcd3DTIZ zf4JL!gjPq=YTAF@AKx@+x3B&Rwtd{hJh(=frAbY;uRbm=?{_ZSdw2PNz1{}2Pv-4z z!fPMTt~*+Xqn;Gd(E56ee))@UuSf2U^{4Y3Ew3CQ(8KHcg)=;0PiBo;+nu^MQY*77 z)7K`gQjGaVUDre5GpBMNM|VL5>Er5>B|DF}+*wkI_eMC$D4(@Mx+uG+VWRMWu%-}~ zE-RkW$Jp-uE3qH0ICAK4G9|4{cgAIEkY=d_cD}AZ<)6?iVX*SCJ882)tOmI=sW|zF zqlpXu%FH{MkCgAK*6Vg40zqW(c2TdpPy_Pra~3SerGnyN+B`gW9#+1VF43XZG|#~5 zk}mP1$)%KMzipo3Jx#g<{gZ${W8uQ)$CNAYxUIC!wcqKRXYc>)2#f2qiC|afyT-!g zh|)ICm>Xtci@Cq3SMIL-e1u+z*s3WG4rr3PpXY^GksiC>yr|S=i_}V=Gye^E!}su9 z>?F*ej@=m-4db#(`?6At3scA6ZG4)!%J%cl^5dRuv)ePe=Abg|vO2KepA&s0+dnrs zRqI{pzY`0M{gq#6b@n1tb5bR|I4|t?NhOR#^{Q3Jk6ujvE`H&0(AaG!_=j%|Ue0Fe zS%??^##1sN);Sz{sBq}MLo<$%H=*O~$d!pFy-ypmA#UgQ1sRt3) zR?L5AN%!pAsrXg39xMkhlhtHsdTw?h*N2(=HQ}m^6WdE3BVI4ohpoL(;5f0Y z5H!@419RB`zpa+h%Sw6gPy9p-Q!WMI{|Mez%13|VOSFsts`rr?S5BB7!?-5J>UD>R zkJ0;vR!53Ob1^El6seX%e0{FgQOg5_?GQF^2JQbUXh}G!l-b27Usv$%X9;LET=~Sm z;G)|&7?pX`*@mp%x5@l9c7?ilN&|QFc+Nfj)rqrxWzBm zaV`Jf=h0B*A2#Cu5?%*klW5)VOMG8v>v_luE5^9+CngmfIW;t&5hVUg}jv;&=dt8EnWs_9`-5H&7fV0f9Wt9GpIm4u9^J zrfNlAej9dYmzAfzLoSkoVcp&Z9N14jIaRNyM=pA5xDnDCcWTHG4<1W9V16^eDdF@w z=&;h^$T0tRjIMP&l$ynQRycg9A9#O-ctRW!I2o);e!oL#o??qp(SjBk;O~WEmKywfVEl>c z#6rF(Yk+zU4k44))TQsFTeqv3SN<5^K-h8PT(vmHP$Vr8!J+@N+Eivpe0z+^bnL`Pw0> z2F;d@R!L$=YTy~SyV^EraI^2~h`(ycRrz=#50Ons%yyMtk9B~w&PCU0&|vGd^Aq}&{wA+Kl>sS&|>JEoRJ_kC&rY)Ms$ZH$7? z**%RZS2eHN0Ti&T;!V~HSn?Yy4Kos-kp)@madT#s4xljQr5K+Jr>+87iAGbh_b!kc zeTu^vfoAU;ZqmXSYd)7HwIp(2WN_QMYGX&lUT>c}3346gC>huvW`KJJC>~q=exK9j z>$raOW8+mFuaQWb^x+`HMG5*BLFDqDUeVhA^~OR}5V9aS7{k8MIt!Jb^AKbY#d?oj zGuHy&a~qc_U|m+N7dw%&Y22br-t6g(2*hRaZ4Ju69E;!qN6+Vky9?%-I^f$tn)`-E zo*$F38G^SOn*{yN0s6@lS1@L^s{ z1bFLXbP~!+P!g}JT5b%xU84xj8FWf-$5QX14@)@mmMMh7MIAnjV+!P2{s#@}>^(Nu z(^TO=_ap{lS~c$;eW?NzGJwFQ?%syI$VO*D$slLtqPMYS+9;0d%pR;0bo)?KF_^RP+qDmha-?EwPh=MRxXnbUC}H7q zXtD_tq1S2{$5$$QN=x3l{&YBR$JGX+XOeQLvE7xZsz{Sz4Vp!0uRJda!HooHD8fCo z0x}a`g^4F^iLu<>I4&^_{O`U9FS4oyc8aowsbgeE1iBj!R|X-AHzPNKu~H^Oo8CAX zDqNzyqamfJ#=@q1UAQHoG;g-R#{64C!%Dd7+ln^FV93TCSu5`l88fwb9Aqf6l4lwxKrtvFP$vz6oHMM zHzbu8daGH)(2~YUY#Iiv2z$!;#)j*WFr@#*uD5)(V4Hl1pE2iB zt(IejY^`S| zJ+hzt>K`v+LwN~iuG(>sl1KGj^2r>$$90zz9c2o{?{MsY@Mjd zY9#!}Q*Im*zF`y!nXMj_Tf;q{O z{K~I%|6*3dcOt>;R^)|g_@JW;u(53E@T7qx1imsrTEXOx5j;g{R}n5B@+!Q4im_3E zT4`xSg>E$X&pSQ(7*AVlfnz+wDio1=(F9W1w^s5x;c|JGT2qyh$PLB>(swGFx=coY zFiBb!r)Xtjv<6He5b7kKpz@#L^}Y*9orn0>0qoU0pCW|Jc8QSCJ`#lz@n@vDo6~2! zb)lpEg-_s9N@r5E7SG|aMbPLEaMWMw31`L>Lx&tL^~m_U5DE&qq=UMR`A9*l>s>k-X|$~9QcPz+{FE2Raq#U7v4;C0K8cBuUxgd>Wu>C+HaHP6lAH`Uyu01tL5 zYi0$M!TW}POj+9TO(#~W>!cPXy@Lnrk<|lWA6$wtY6yB|E&XS6$m=)qh}n9xS61d% zL&0!{iE3$?Y91^vK*rGA+3JrCT(Fhua>T_qnI`s!0vqJ~ymZwnb_}+THyqJV-4O@9 zQ1+|^+pBP$rlhwanp&g!ayveM+*P?kOHk^H(83r>C2WOtV`uj$H~ZOtSi=xpD>A=A z`;$IW*~B@j1gvCgf+!j2?A$LH^mS88)iH52uZ7_M0sg4eux(!kau|ep={G@ zmFVEJ+WHQ=Ws#UUiKOjscjduvTmh3WSrXm2JMef*eXM+pF}tuMVY6eL}O4~m@zZt;7+8mD!;w!Dx96xn~D1xug&xoDmCAJrJ6)5oP zo!XTtCKJO3HHh38qs#>j@c;|DLt6r#)=L8CghkqSf44LfH<*zVR(4{AWyx$p#D0iO zB$TF!@*&omTXBW}mxbgbkeEvf`|=cqqO+AZ6>8}tX&QPuB)WJ?X+74z+M~1~e}pkiApwpZQzd9I5qV_?es=hPR7{ASDxLDa zDUz!Ky>f7YrL>W{&9MRj+W}e}KP%f|j*D$PFan)%X>2|uX7NEQX6GkhO!DVCR4{9;BCW@vjJ4CZqho_ zQbQFpbyE1lr@A9Jhag$ueyTumVt`B(g_i%+DD4QDzjm6!tkq#c~7FGyKDPs0Y(z2bUH%6r65k zKU}l{Wo$Q3O5cv*4=!bH-BOZq^d1EC3R9Hs>G)WVa?SHfrs?gVFxjT?eAz+apkox> zuTHK3Y*e;H6?Jp-(hb9^F_`Ch?F3stF=zSCeL0aETf7}(mwif2t;1L5);ZyGL!HC8{A@16vq16+H)leRi|-wiH!j#3JqCD|_(;i+=72m|m-9`qlbDh@m)cbu>B4G0&^zdY!jBzF!nX_6bZ^-Bo`C1|? zlAP+f#lmeQfR7L0EcM8b9q~3^yQ*kJHO(UBiFVv(`kJwo4Z)~|hys})&{=dsUr1NXWsJaw9D&KEb zH-0_|=4-2>ICy-fJJigAVEb^w2BSW_uAu4GUoQ1v?*s zq6H`}a`fSNq+eWvn!cQKT=9M9sgV}6C%OC(vL_A=BNtNw$(rGkd z()vlm@!efXjzyl>%GTYD0n2VuX@w_T_rTbiuZiCgzey$pLj;B%8ifXNA?1)#?&&Di zWFnhOj>|}VnyG6GeS!seROA~~?5>f}={3oLFhv+Yvd5K0A!!O9kT^@>F6jGJN4Ik9)Rey8;p_Lfu+sfE z2e4$5OPO1sl7r!bwcX+%X&BTn!z?won6_&2Hg0s<+qB58lEz3SXAujcKnve1n9BtT z+i6Z;##pSG7U`7aW)e8$EBx>0-GtSdZx6QIU6Guo`Ofwb?ePoSxz{<(6TXc|JY4=M z1_<)dId^!cVFR6I1T3JFZ3jXoqcQu4%@8#$N#8T%ah^cwF`tYBBfN`ax$_dqJ`kEU z$LH2j@g=-s1Rhnop2ye4BxE$EH-|=YZe5fw7G#dzUrf^J7MO?^0Gm~H8!cUw!I7_? ztU{=Bwee&*i-uNrLyYWL66&UJi@Io5Av!>|UlTuYd3BXUAvaVH%+fzAjRP!op#mJp z?^#e=*?DAYSlZJY)$OL=#`liqh|uPEX19n6Nc_Tq`hCR`JOe8uEm$p))r zOiZ%sOJYLyVW z8B*zN3yP>!Wq6v5RGCQ94qQU3PdPqHi-?UiwPL)#lTxi-pp7z-Oek~AaJ@hV>LFLD zR3Ft1`OEG4vBcks{~80Pn>xj-R0k|u-7N+E<@>d?^N_dy&Uaj}V{ET>72^)mwj*&= z&-hKo)^CGb>WOpfU}(R-7!&%5juJl49LWiVO^L3qE?+jYoY3&EeQ#}$>WU;trIOyI zfg@I4%xa3+9BT3bSioc+hg_={&C*c?Q%f?e`rJHhT(DbiqJH@)+2(M*Ga%01#DCn= zgott7Nn|ky^RL(S7b7hTk?G1Zf=8p>ee9D3FTlE1Ct6-l06C+B-jt_}^V09B)DzO- zS$VG_Ms8vAEbg45!@7|3jS9_4f^Y5vy0K2}(VUH(kE4X+odFN_wIV^Fl$$lj1jfu= zfcWPbATyV4?$7)40_kDS#$-Es{l(LWlb|3l?oR$ynlxO}805uGOtL8kFJ;W56EGyrTokh8*F&{bI2>klcg4X*6#E1#mI10X0+_3?otJyOxY zU-a3NU4rck(D|6ZlT5d-Wq+L9_9M+p2>$tLA*vx{AZm?Lk^lqoLEoi>1)b(qd!LTmV{}ISOMd=HL zNP^L=?uscIm7MtqAlMqPEv1~iuR<~dK-8M!FV`I!YSqJ_%&48u2(ox3qxR})tL4go zA!SKXZOJnJ=e=iS4FK{hLZtt`3VhiH{_hF?5B$FugBk!-wEqkszm)z9Vp9Hx{SV^2 BJ!=2} From 34612ae233fbf664c7db40567b09505be918d3f2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 19:03:29 +0300 Subject: [PATCH 027/102] Forward internal management to a container alongside tracker --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 17 ++++++++++++++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- .../Playlists/PlaylistsRoomSubScreen.cs | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c049d4be20..4b89a0c278 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -43,14 +45,23 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected readonly MultiplayerBeatmapTracker BeatmapTracker; + private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; + protected RoomSubScreen() { - InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker + base.AddInternal(BeatmapTracker = new MultiplayerBeatmapTracker { - SelectedItem = { BindTarget = SelectedItem }, - }; + SelectedItem = { BindTarget = SelectedItem } + }); + + base.AddInternal(content); } + // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) + protected override void AddInternal(Drawable drawable) => content.Add(drawable); + protected override bool RemoveInternal(Drawable drawable) => content.Remove(drawable); + protected override void ClearInternal(bool disposeChildren = true) => content.Clear(disposeChildren); + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a641935b9a..fa4b972f98 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] + InternalChildren = new Drawable[] { new GridContainer { @@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden } } - }); + }; } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 7b3cdf16db..781c455eb4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] + InternalChildren = new Drawable[] { new GridContainer { @@ -188,7 +188,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } - }); + }; } [Resolved] From a800955bb1c9519732f1bf2d72ab3bd0d5840bfb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 19:46:25 +0900 Subject: [PATCH 028/102] Add mod validation utilities --- osu.Game.Tests/Mods/ModValidationTest.cs | 64 +++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Utils/ModValidation.cs | 116 +++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 osu.Game.Tests/Mods/ModValidationTest.cs create mode 100644 osu.Game/Utils/ModValidation.cs diff --git a/osu.Game.Tests/Mods/ModValidationTest.cs b/osu.Game.Tests/Mods/ModValidationTest.cs new file mode 100644 index 0000000000..c7a7e242e9 --- /dev/null +++ b/osu.Game.Tests/Mods/ModValidationTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Moq; +using NUnit.Framework; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + public class ModValidationTest + { + [Test] + public void TestModIsCompatibleByItself() + { + var mod = new Mock(); + Assert.That(ModValidation.CheckCompatible(new[] { mod.Object })); + } + + [Test] + public void TestIncompatibleThroughTopLevel() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + + mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); + + // Test both orderings. + Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModValidation.CheckCompatible(new[] { mod2.Object, mod1.Object }), Is.False); + } + + [Test] + public void TestIncompatibleThroughMultiMod() + { + var mod1 = new Mock(); + + // The nested mod. + var mod2 = new Mock(); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); + + var multiMod = new MultiMod(new MultiMod(mod2.Object)); + + // Test both orderings. + Assert.That(ModValidation.CheckCompatible(new[] { multiMod, mod1.Object }), Is.False); + Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, multiMod }), Is.False); + } + + [Test] + public void TestAllowedThroughMostDerivedType() + { + var mod = new Mock(); + Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); + } + + [Test] + public void TestNotAllowedThroughBaseType() + { + var mod = new Mock(); + Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c0c0578391..d29ed94b5f 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,6 +7,7 @@ + WinExe diff --git a/osu.Game/Utils/ModValidation.cs b/osu.Game/Utils/ModValidation.cs new file mode 100644 index 0000000000..0c4d58ab2e --- /dev/null +++ b/osu.Game/Utils/ModValidation.cs @@ -0,0 +1,116 @@ +// 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.Linq; +using osu.Framework.Extensions.TypeExtensions; +using osu.Game.Rulesets.Mods; + +#nullable enable + +namespace osu.Game.Utils +{ + /// + /// A set of utilities to validate combinations. + /// + public static class ModValidation + { + /// + /// Checks that all s are compatible with each-other, and that all appear within a set of allowed types. + /// + /// + /// The allowed types must contain exact types for the respective s to be allowed. + /// + /// The s to check. + /// The set of allowed types. + /// Whether all s are compatible with each-other and appear in the set of allowed types. + public static bool CheckCompatibleAndAllowed(IEnumerable combination, IEnumerable allowedTypes) + { + // Prevent multiple-enumeration. + var combinationList = combination as ICollection ?? combination.ToArray(); + return CheckCompatible(combinationList) && CheckAllowed(combinationList, allowedTypes); + } + + /// + /// Checks that all s in a combination are compatible with each-other. + /// + /// The combination to check. + /// Whether all s in the combination are compatible with each-other. + public static bool CheckCompatible(IEnumerable combination) + { + var incompatibleTypes = new HashSet(); + var incomingTypes = new HashSet(); + + foreach (var mod in combination.SelectMany(flattenMod)) + { + // Add the new mod incompatibilities, checking whether any match the existing mod types. + foreach (var t in mod.IncompatibleMods) + { + if (incomingTypes.Contains(t)) + return false; + + incompatibleTypes.Add(t); + } + + // Add the new mod types, checking whether any match the incompatible types. + foreach (var t in mod.GetType().EnumerateBaseTypes()) + { + if (incomingTypes.Contains(t)) + return false; + + incomingTypes.Add(t); + } + } + + return true; + } + + /// + /// Checks that all s in a combination appear within a set of allowed types. + /// + /// + /// The set of allowed types must contain exact types for the respective s to be allowed. + /// + /// The combination to check. + /// The set of allowed types. + /// Whether all s in the combination are allowed. + public static bool CheckAllowed(IEnumerable combination, IEnumerable allowedTypes) + { + var allowedSet = new HashSet(allowedTypes); + + return combination.SelectMany(flattenMod) + .All(m => allowedSet.Contains(m.GetType())); + } + + /// + /// Determines whether a is in a set of incompatible types. + /// + /// + /// A can be incompatible through its most-declared type or any of its base types. + /// + /// The to test. + /// The set of incompatible types. + /// Whether the given is incompatible. + private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) + => flattenMod(mod) + .SelectMany(m => m.GetType().EnumerateBaseTypes()) + .Any(incompatibleTypes.Contains); + + /// + /// Flattens a , returning a set of s in-place of any s. + /// + /// The to flatten. + /// A set of singular "flattened" s + private static IEnumerable flattenMod(Mod mod) + { + if (mod is MultiMod multi) + { + foreach (var m in multi.Mods.SelectMany(flattenMod)) + yield return m; + } + else + yield return mod; + } + } +} From f2fa51bf5e31e79c799fcf8fd17cd785280cde94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 19:59:28 +0900 Subject: [PATCH 029/102] Make ModSections overrideable --- osu.Game/Overlays/Mods/ModSection.cs | 18 +++++----- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 33 +++++++++++++++---- .../Mods/Sections/AutomationSection.cs | 19 ----------- .../Mods/Sections/ConversionSection.cs | 19 ----------- .../Sections/DifficultyIncreaseSection.cs | 19 ----------- .../Sections/DifficultyReductionSection.cs | 19 ----------- osu.Game/Overlays/Mods/Sections/FunSection.cs | 19 ----------- 7 files changed, 34 insertions(+), 112 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/Sections/AutomationSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/ConversionSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/FunSection.cs diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 573d1e5355..d70013602e 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -11,26 +11,23 @@ using System; using System.Linq; using System.Collections.Generic; using System.Threading; +using Humanizer; using osu.Framework.Input.Events; using osu.Game.Graphics; namespace osu.Game.Overlays.Mods { - public abstract class ModSection : Container + public class ModSection : Container { private readonly OsuSpriteText headerLabel; public FillFlowContainer ButtonsContainer { get; } public Action Action; - protected abstract Key[] ToggleKeys { get; } - public abstract ModType ModType { get; } - public string Header - { - get => headerLabel.Text; - set => headerLabel.Text = value; - } + public Key[] ToggleKeys; + + public readonly ModType ModType; public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); @@ -153,7 +150,7 @@ namespace osu.Game.Overlays.Mods button.Deselect(); } - protected ModSection() + public ModSection(ModType type) { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -168,7 +165,8 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.TopLeft, Anchor = Anchor.TopLeft, Position = new Vector2(0f, 0f), - Font = OsuFont.GetFont(weight: FontWeight.Bold) + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = type.Humanize(LetterCasing.Title) }, ButtonsContainer = new FillFlowContainer { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b93602116b..5775b08ae7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osuTK; @@ -190,13 +189,31 @@ namespace osu.Game.Overlays.Mods Width = content_width, LayoutDuration = 200, LayoutEasing = Easing.OutQuint, - Children = new ModSection[] + Children = new[] { - new DifficultyReductionSection { Action = modButtonPressed }, - new DifficultyIncreaseSection { Action = modButtonPressed }, - new AutomationSection { Action = modButtonPressed }, - new ConversionSection { Action = modButtonPressed }, - new FunSection { Action = modButtonPressed }, + CreateModSection(ModType.DifficultyReduction).With(s => + { + s.ToggleKeys = new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.DifficultyIncrease).With(s => + { + s.ToggleKeys = new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Automation).With(s => + { + s.ToggleKeys = new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Conversion).With(s => + { + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Fun).With(s => + { + s.Action = modButtonPressed; + }), } }, } @@ -455,6 +472,8 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); + #region Disposal protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs deleted file mode 100644 index a2d7fec15f..0000000000 --- a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class AutomationSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; - public override ModType ModType => ModType.Automation; - - public AutomationSection() - { - Header = @"Automation"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs deleted file mode 100644 index 24fd8c30dd..0000000000 --- a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class ConversionSection : ModSection - { - protected override Key[] ToggleKeys => null; - public override ModType ModType => ModType.Conversion; - - public ConversionSection() - { - Header = @"Conversion"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs deleted file mode 100644 index 0b7ccd1f25..0000000000 --- a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class DifficultyIncreaseSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; - public override ModType ModType => ModType.DifficultyIncrease; - - public DifficultyIncreaseSection() - { - Header = @"Difficulty Increase"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs deleted file mode 100644 index 508e92508b..0000000000 --- a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class DifficultyReductionSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; - public override ModType ModType => ModType.DifficultyReduction; - - public DifficultyReductionSection() - { - Header = @"Difficulty Reduction"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/FunSection.cs b/osu.Game/Overlays/Mods/Sections/FunSection.cs deleted file mode 100644 index af1f5836b1..0000000000 --- a/osu.Game/Overlays/Mods/Sections/FunSection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class FunSection : ModSection - { - protected override Key[] ToggleKeys => null; - public override ModType ModType => ModType.Fun; - - public FunSection() - { - Header = @"Fun"; - } - } -} From 0ff300628eff78d2d4c983d029137425b3209628 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 20:07:22 +0900 Subject: [PATCH 030/102] Fix type not being set --- osu.Game/Overlays/Mods/ModSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index d70013602e..df4d05daad 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -152,6 +152,8 @@ namespace osu.Game.Overlays.Mods public ModSection(ModType type) { + ModType = type; + AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; From 91d34d86f74ba1466323c3ee5f29c6b9541f1640 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:02:23 +0900 Subject: [PATCH 031/102] Abstractify ModSelectOverlay --- .../TestSceneModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 23 ++++++------------ .../Overlays/Mods/SoloModSelectOverlay.cs | 24 +++++++++++++++++++ osu.Game/Screens/Select/MatchSongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 5 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Overlays/Mods/SoloModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index bd4010a7f3..b03512ffde 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -265,7 +265,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); - private class TestModSelectOverlay : ModSelectOverlay + private class TestModSelectOverlay : SoloModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5775b08ae7..5709ca3b8d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,7 +27,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class ModSelectOverlay : WaveOverlayContainer + public abstract class ModSelectOverlay : WaveOverlayContainer { private readonly Func isValidMod; public const float HEIGHT = 510; @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods private SampleChannel sampleOn, sampleOff; - public ModSelectOverlay(Func isValidMod = null) + protected ModSelectOverlay(Func isValidMod = null) { this.isValidMod = isValidMod ?? (m => true); @@ -346,19 +346,6 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - /// - /// Deselect one or more mods. - /// - /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - private void deselectTypes(Type[] modTypes, bool immediate = false) - { - if (modTypes.Length == 0) return; - - foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(modTypes, immediate); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -458,7 +445,7 @@ namespace osu.Game.Overlays.Mods { if (State.Value == Visibility.Visible) sampleOn?.Play(); - deselectTypes(selectedMod.IncompatibleMods, true); + OnModSelected(selectedMod); if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show(); } @@ -470,6 +457,10 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } + protected virtual void OnModSelected(Mod mod) + { + } + private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs new file mode 100644 index 0000000000..53d0c9fce9 --- /dev/null +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -0,0 +1,24 @@ +// 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 osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class SoloModSelectOverlay : ModSelectOverlay + { + public SoloModSelectOverlay(Func isValidMod = null) + : base(isValidMod) + { + } + + protected override void OnModSelected(Mod mod) + { + base.OnModSelected(mod); + + foreach (var section in ModSectionsContainer.Children) + section.DeselectTypes(mod.IncompatibleMods, true); + } + } +} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index ed47b5d5ac..280f46f9ab 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Select item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4fca77a176..ff49dd9f7e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select } } - protected virtual ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 4019cc38e5e34e8c900bed0e61176fdfa35dbb42 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:03:51 +0900 Subject: [PATCH 032/102] Allow footer buttons to be customised --- osu.Game/Screens/Select/SongSelect.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ff49dd9f7e..4af96b7a29 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -263,9 +263,8 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); - Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); - Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); + foreach (var (button, overlay) in CreateFooterButtons()) + Footer.AddButton(button, overlay); BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); @@ -301,6 +300,13 @@ namespace osu.Game.Screens.Select } } + protected virtual IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[] + { + (new FooterButtonMods { Current = Mods }, ModSelect), + (new FooterButtonRandom { Action = triggerRandom }, null), + (new FooterButtonOptions(), BeatmapOptions) + }; + protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) From 45e41aaeacf930ce4b5e3b62f85acbc618ce73da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:15:53 +0900 Subject: [PATCH 033/102] Initial implementation of freemod selection overlay --- .../TestSceneFreeModSelectOverlay.cs | 24 +++++ osu.Game/Overlays/Mods/ModButton.cs | 8 +- osu.Game/Overlays/Mods/ModSection.cs | 30 +++--- .../Multiplayer/FreeModSelectOverlay.cs | 101 ++++++++++++++++++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 66 +++++++++++- osu.Game/Screens/Select/Footer.cs | 15 ++- 6 files changed, 216 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs new file mode 100644 index 0000000000..960402df88 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Mods; +using osu.Game.Screens.OnlinePlay.Multiplayer; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + { + private ModSelectOverlay overlay; + + [SetUp] + public new void Setup() => Schedule(() => + { + Child = overlay = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }; + }); + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ab8efdabcc..9ea4f65eb2 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -174,7 +174,7 @@ namespace osu.Game.Overlays.Mods switch (e.Button) { case MouseButton.Right: - SelectNext(-1); + OnRightClick(e); break; } } @@ -183,10 +183,14 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { SelectNext(1); - return true; } + protected virtual void OnRightClick(MouseUpEvent e) + { + SelectNext(-1); + } + /// /// Select the next available mod in a specified direction. /// diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index df4d05daad..5de629424b 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Mods { public class ModSection : Container { - private readonly OsuSpriteText headerLabel; + private readonly Drawable header; public FillFlowContainer ButtonsContainer { get; } @@ -47,10 +47,7 @@ namespace osu.Game.Overlays.Mods if (m == null) return new ModButtonEmpty(); - return new ModButton(m) - { - SelectionChanged = Action, - }; + return CreateModButton(m).With(b => b.SelectionChanged = Action); }).ToArray(); modsLoadCts?.Cancel(); @@ -58,7 +55,7 @@ namespace osu.Game.Overlays.Mods if (modContainers.Length == 0) { ModIconsLoaded = true; - headerLabel.Hide(); + header.Hide(); Hide(); return; } @@ -73,7 +70,7 @@ namespace osu.Game.Overlays.Mods buttons = modContainers.OfType().ToArray(); - headerLabel.FadeIn(200); + header.FadeIn(200); this.FadeIn(200); } } @@ -160,16 +157,9 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.TopCentre; Anchor = Anchor.TopCentre; - Children = new Drawable[] + Children = new[] { - headerLabel = new OsuSpriteText - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Position = new Vector2(0f, 0f), - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = type.Humanize(LetterCasing.Title) - }, + header = CreateHeader(type.Humanize(LetterCasing.Title)), ButtonsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -185,5 +175,13 @@ namespace osu.Game.Overlays.Mods }, }; } + + protected virtual ModButton CreateModButton(Mod mod) => new ModButton(mod); + + protected virtual Drawable CreateHeader(string text) => new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = text + }; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs new file mode 100644 index 0000000000..56e74a8460 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -0,0 +1,101 @@ +// 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.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer +{ + public class FreeModSelectOverlay : ModSelectOverlay + { + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); + + private class FreeModSection : ModSection + { + private HeaderCheckbox checkbox; + + public FreeModSection(ModType type) + : base(type) + { + } + + protected override ModButton CreateModButton(Mod mod) => new FreeModButton(mod); + + protected override Drawable CreateHeader(string text) => new Container + { + AutoSizeAxes = Axes.Y, + Width = 175, + Child = checkbox = new HeaderCheckbox + { + LabelText = text, + Changed = onCheckboxChanged + } + }; + + private void onCheckboxChanged(bool value) + { + foreach (var button in ButtonsContainer.OfType()) + { + if (value) + // Note: Buttons where only part of the group has an implementation are not fully supported. + button.SelectAt(0); + else + button.Deselect(); + } + } + + protected override void Update() + { + base.Update(); + + // If any of the buttons aren't selected, deselect the checkbox. + foreach (var button in ButtonsContainer.OfType()) + { + if (button.Mods.Any(m => m.HasImplementation) && !button.Selected) + checkbox.Current.Value = false; + } + } + } + + private class HeaderCheckbox : OsuCheckbox + { + public Action Changed; + + protected override void OnUserChange(bool value) + { + base.OnUserChange(value); + Changed?.Invoke(value); + } + } + + private class FreeModButton : ModButton + { + public FreeModButton(Mod mod) + : base(mod) + { + } + + protected override bool OnClick(ClickEvent e) + { + onClick(); + return true; + } + + protected override void OnRightClick(MouseUpEvent e) => onClick(); + + private void onClick() + { + if (Selected) + Deselect(); + else + SelectNext(1); + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index ebc06d2445..5917ed3f49 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -6,17 +6,23 @@ using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { @@ -33,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private StatefulMultiplayerClient client { get; set; } private LoadingLayer loadingLayer; + private FreeModSelectOverlay freeModSelectOverlay; private WorkingBeatmap initialBeatmap; private RulesetInfo initialRuleset; @@ -43,6 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSongSelect() { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; + + freeModSelectOverlay = new FreeModSelectOverlay(); } [BackgroundDependencyLoader] @@ -52,6 +61,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialBeatmap = Beatmap.Value; initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); + + FooterPanels.Add(freeModSelectOverlay); } protected override bool OnStart() @@ -111,8 +122,61 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); + + protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() + { + var buttons = base.CreateFooterButtons().ToList(); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods(), freeModSelectOverlay)); + return buttons; + } private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } + + public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> + { + public Bindable> Current + { + get => modDisplay.Current; + set => modDisplay.Current = value; + } + + private readonly ModDisplay modDisplay; + + public FooterButtonFreeMods() + { + ButtonContentContainer.Add(modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DisplayUnrankedText = false, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"freemods"; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateModDisplay(), true); + } + + private void updateModDisplay() + { + if (Current.Value?.Count > 0) + modDisplay.FadeIn(); + else + modDisplay.FadeOut(); + } + } } diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 689a11166a..ee13ebda44 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -28,19 +28,16 @@ namespace osu.Game.Screens.Select private readonly List overlays = new List(); - /// THe button to be added. + /// The button to be added. /// The to be toggled by this button. public void AddButton(FooterButton button, OverlayContainer overlay) { - overlays.Add(overlay); - button.Action = () => showOverlay(overlay); + if (overlay != null) + { + overlays.Add(overlay); + button.Action = () => showOverlay(overlay); + } - AddButton(button); - } - - /// Button to be added. - public void AddButton(FooterButton button) - { button.Hovered = updateModeLight; button.HoverLost = updateModeLight; From 4c256f1fb361f4a64d7cf3fa7a919467c56969b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:23:38 +0900 Subject: [PATCH 034/102] Actually populate the playlist item --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 5917ed3f49..4054d84540 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -62,6 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); + freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToList(); FooterPanels.Add(freeModSelectOverlay); } @@ -76,6 +77,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(freeModSelectOverlay.SelectedMods.Value.Select(m => m.CreateCopy())); + // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) From c408b46a2158e251d82ce99997d65d2c58c7203f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:25:14 +0900 Subject: [PATCH 035/102] Add AllowedMods to MultiplayerRoomSettings model --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 857b38ea60..ad624f18ed 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -25,13 +25,21 @@ namespace osu.Game.Online.Multiplayer [NotNull] public IEnumerable Mods { get; set; } = Enumerable.Empty(); + [NotNull] + public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum && Mods.SequenceEqual(other.Mods) + && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID && Name.Equals(other.Name, StringComparison.Ordinal); - public override string ToString() => $"Name:{Name} Beatmap:{BeatmapID} ({BeatmapChecksum}) Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; + public override string ToString() => $"Name:{Name}" + + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" + + $" Mods:{string.Join(',', Mods)}" + + $" AllowedMods:{string.Join(',', AllowedMods)}" + + $" Ruleset:{RulesetID}"; } } From ff8ee379fb9c1684cdd064a32361f27d8fb6105e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:27:31 +0900 Subject: [PATCH 036/102] Fix possible nullref --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 4054d84540..70d3d128dd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,6 +1,7 @@ // 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.Linq; using Humanizer; @@ -38,8 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private StatefulMultiplayerClient client { get; set; } + private readonly FreeModSelectOverlay freeModSelectOverlay; private LoadingLayer loadingLayer; - private FreeModSelectOverlay freeModSelectOverlay; private WorkingBeatmap initialBeatmap; private RulesetInfo initialRuleset; @@ -62,7 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToList(); + freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FooterPanels.Add(freeModSelectOverlay); } From b79d1c7b81ef900087608be126f96b6e49e6bdf0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:33:03 +0900 Subject: [PATCH 037/102] Add mods to footer --- .../OnlinePlay/Multiplayer/FreeModSelectOverlay.cs | 5 +++++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs index 56e74a8460..10b68ec5a6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -14,6 +14,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class FreeModSelectOverlay : ModSelectOverlay { + public FreeModSelectOverlay(Func isValidMod = null) + : base(isValidMod) + { + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 70d3d128dd..86b8f22d34 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private StatefulMultiplayerClient client { get; set; } + private readonly Bindable> freeMods = new Bindable>(Array.Empty()); + private readonly FreeModSelectOverlay freeModSelectOverlay; private LoadingLayer loadingLayer; @@ -52,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - freeModSelectOverlay = new FreeModSelectOverlay(); + freeModSelectOverlay = new FreeModSelectOverlay(isValidMod) { SelectedMods = { BindTarget = freeMods } }; } [BackgroundDependencyLoader] @@ -63,7 +65,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + freeMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + FooterPanels.Add(freeModSelectOverlay); } @@ -79,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); item.AllowedMods.Clear(); - item.AllowedMods.AddRange(freeModSelectOverlay.SelectedMods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. @@ -132,7 +135,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() { var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods(), freeModSelectOverlay)); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); return buttons; } From ab9a3e6dd05bd4a85ab5d1be0e7acc726148e029 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Jan 2021 18:21:22 +0900 Subject: [PATCH 038/102] Pass allowed mods and consume on server callback --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f0e11b2b8b..e5b07ddfb4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -192,7 +192,8 @@ namespace osu.Game.Online.Multiplayer BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, - Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods + Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods, + AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods }); } @@ -502,6 +503,7 @@ namespace osu.Game.Online.Multiplayer var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance(); var mods = settings.Mods.Select(m => m.ToMod(ruleset)); + var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); PlaylistItem playlistItem = new PlaylistItem { @@ -511,6 +513,7 @@ namespace osu.Game.Online.Multiplayer }; playlistItem.RequiredMods.AddRange(mods); + playlistItem.AllowedMods.AddRange(allowedMods); apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. apiRoom.Playlist.Add(playlistItem); From 230b347c1efbfc7baa7da6af8676356e47d7a4d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 12:18:11 +0900 Subject: [PATCH 039/102] Move ModSelectOverlay.IsValidMod to a property --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 29 ++++++++++++++----- .../Overlays/Mods/SoloModSelectOverlay.cs | 6 ---- .../Multiplayer/FreeModSelectOverlay.cs | 5 ---- .../Multiplayer/MultiplayerMatchSongSelect.cs | 11 +++++-- osu.Game/Screens/Select/MatchSongSelect.cs | 5 +++- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5709ca3b8d..c21b9ba409 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -29,7 +30,6 @@ namespace osu.Game.Overlays.Mods { public abstract class ModSelectOverlay : WaveOverlayContainer { - private readonly Func isValidMod; public const float HEIGHT = 510; protected readonly TriangleButton DeselectAllButton; @@ -46,6 +46,20 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; + [NotNull] + private Func isValidMod = m => true; + + [NotNull] + public Func IsValidMod + { + get => isValidMod; + set + { + isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + updateAvailableMods(); + } + } + public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); private Bindable>> availableMods; @@ -60,10 +74,8 @@ namespace osu.Game.Overlays.Mods private SampleChannel sampleOn, sampleOff; - protected ModSelectOverlay(Func isValidMod = null) + protected ModSelectOverlay() { - this.isValidMod = isValidMod ?? (m => true); - Waves.FirstWaveColour = Color4Extensions.FromHex(@"19b0e2"); Waves.SecondWaveColour = Color4Extensions.FromHex(@"2280a2"); Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774"); @@ -350,7 +362,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - availableMods.BindValueChanged(availableModsChanged, true); + availableMods.BindValueChanged(_ => updateAvailableMods(), true); SelectedMods.BindValueChanged(selectedModsChanged, true); } @@ -405,12 +417,13 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(GlobalAction action) => false; // handled by back button - private void availableModsChanged(ValueChangedEvent>> mods) + private void updateAvailableMods() { - if (mods.NewValue == null) return; + if (availableMods.Value == null) + return; foreach (var section in ModSectionsContainer.Children) - section.Mods = mods.NewValue[section.ModType].Where(isValidMod); + section.Mods = availableMods.Value[section.ModType].Where(IsValidMod); } private void selectedModsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index 53d0c9fce9..d039ad1f98 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -1,18 +1,12 @@ // 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 osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { public class SoloModSelectOverlay : ModSelectOverlay { - public SoloModSelectOverlay(Func isValidMod = null) - : base(isValidMod) - { - } - protected override void OnModSelected(Mod mod) { base.OnModSelected(mod); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs index 10b68ec5a6..56e74a8460 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -14,11 +14,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class FreeModSelectOverlay : ModSelectOverlay { - public FreeModSelectOverlay(Func isValidMod = null) - : base(isValidMod) - { - } - protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 86b8f22d34..d36ebeec0f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -54,7 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - freeModSelectOverlay = new FreeModSelectOverlay(isValidMod) { SelectedMods = { BindTarget = freeMods } }; + freeModSelectOverlay = new FreeModSelectOverlay + { + SelectedMods = { BindTarget = freeMods }, + IsValidMod = isValidMod, + }; } [BackgroundDependencyLoader] @@ -130,7 +134,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + { + IsValidMod = isValidMod + }; protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() { diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 280f46f9ab..98e02a9294 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -81,7 +81,10 @@ namespace osu.Game.Screens.Select item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + { + IsValidMod = isValidMod + }; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } From 797a8102876808174924486af644de927d7123f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:24:56 +0900 Subject: [PATCH 040/102] Allow unstacking mods --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 30 +++++++++++++++++-- .../Multiplayer/FreeModSelectOverlay.cs | 5 ++++ osu.Game/Utils/ModValidation.cs | 17 +++++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c21b9ba409..db9460c494 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Screens; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -46,9 +47,27 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; + private bool stacked = true; + + /// + /// Whether mod icons should be stacked, or appear as individual buttons. + /// + public bool Stacked + { + get => stacked; + set + { + stacked = value; + updateAvailableMods(); + } + } + [NotNull] private Func isValidMod = m => true; + /// + /// A function that checks whether a given mod is valid. + /// [NotNull] public Func IsValidMod { @@ -419,11 +438,18 @@ namespace osu.Game.Overlays.Mods private void updateAvailableMods() { - if (availableMods.Value == null) + if (availableMods?.Value == null) return; foreach (var section in ModSectionsContainer.Children) - section.Mods = availableMods.Value[section.ModType].Where(IsValidMod); + { + IEnumerable modEnumeration = availableMods.Value[section.ModType]; + + if (!stacked) + modEnumeration = ModValidation.FlattenMods(modEnumeration); + + section.Mods = modEnumeration.Where(IsValidMod); + } } private void selectedModsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs index 56e74a8460..8334df1e44 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -14,6 +14,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class FreeModSelectOverlay : ModSelectOverlay { + public FreeModSelectOverlay() + { + Stacked = false; + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection diff --git a/osu.Game/Utils/ModValidation.cs b/osu.Game/Utils/ModValidation.cs index 0c4d58ab2e..3597396ec4 100644 --- a/osu.Game/Utils/ModValidation.cs +++ b/osu.Game/Utils/ModValidation.cs @@ -42,7 +42,7 @@ namespace osu.Game.Utils var incompatibleTypes = new HashSet(); var incomingTypes = new HashSet(); - foreach (var mod in combination.SelectMany(flattenMod)) + foreach (var mod in combination.SelectMany(FlattenMod)) { // Add the new mod incompatibilities, checking whether any match the existing mod types. foreach (var t in mod.IncompatibleMods) @@ -79,7 +79,7 @@ namespace osu.Game.Utils { var allowedSet = new HashSet(allowedTypes); - return combination.SelectMany(flattenMod) + return combination.SelectMany(FlattenMod) .All(m => allowedSet.Contains(m.GetType())); } @@ -93,20 +93,27 @@ namespace osu.Game.Utils /// The set of incompatible types. /// Whether the given is incompatible. private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) - => flattenMod(mod) + => FlattenMod(mod) .SelectMany(m => m.GetType().EnumerateBaseTypes()) .Any(incompatibleTypes.Contains); + /// + /// Flattens a set of s, returning a new set with all s removed. + /// + /// The set of s to flatten. + /// The new set, containing all s in recursively with all s removed. + public static IEnumerable FlattenMods(IEnumerable mods) => mods.SelectMany(FlattenMod); + /// /// Flattens a , returning a set of s in-place of any s. /// /// The to flatten. /// A set of singular "flattened" s - private static IEnumerable flattenMod(Mod mod) + public static IEnumerable FlattenMod(Mod mod) { if (mod is MultiMod multi) { - foreach (var m in multi.Mods.SelectMany(flattenMod)) + foreach (var m in multi.Mods.SelectMany(FlattenMod)) yield return m; } else From e02e3cf19a9ad9ace7d21e949ec30e6c9269771e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:35:48 +0900 Subject: [PATCH 041/102] Disallow selecting DT/HT/WU/WD as allowable freemods --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index d36ebeec0f..98e3ca3358 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer freeModSelectOverlay = new FreeModSelectOverlay { SelectedMods = { BindTarget = freeMods }, - IsValidMod = isValidMod, + IsValidMod = isValidFreeMod, }; } @@ -147,6 +147,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + + private bool isValidFreeMod(Mod mod) => isValidMod(mod) && !(mod is ModRateAdjust) && !(mod is ModTimeRamp); } public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> From 4ae10b1e1c88f4d6b6a3fe5ec136abf1e3ead32b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:40:59 +0900 Subject: [PATCH 042/102] Add initial UI for selecting extra mods --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 29 ++++++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 69 +++++++++++++++++-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2449563c73..231fb58836 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -2,6 +2,7 @@ // 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.Audio; @@ -29,6 +30,11 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + /// + /// Any mods applied by/to the local user. + /// + protected readonly Bindable> ExtraMods = new Bindable>(Array.Empty()); + [Resolved] private MusicController music { get; set; } @@ -55,6 +61,8 @@ namespace osu.Game.Screens.OnlinePlay.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); + + ExtraMods.BindValueChanged(_ => updateMods()); } public override void OnEntering(IScreen last) @@ -95,12 +103,17 @@ namespace osu.Game.Screens.OnlinePlay.Match { updateWorkingBeatmap(); - var item = SelectedItem.Value; + if (SelectedItem.Value == null) + return; - Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty(); + // Remove any extra mods that are no longer allowed. + ExtraMods.Value = ExtraMods.Value + .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) + .ToList(); - if (item?.Ruleset != null) - Ruleset.Value = item.Ruleset.Value; + updateMods(); + + Ruleset.Value = SelectedItem.Value.Ruleset.Value; } private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); @@ -115,6 +128,14 @@ namespace osu.Game.Screens.OnlinePlay.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } + private void updateMods() + { + if (SelectedItem.Value == null) + return; + + Mods.Value = ExtraMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); + } + private void beginHandlingTrack() { Beatmap.BindValueChanged(applyLoopingToTrack, true); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7c4b6d18ec..95dda1a051 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -14,12 +14,15 @@ using osu.Framework.Screens; using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; +using osu.Game.Screens.Play.HUD; using osu.Game.Users; +using osuTK; using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.ParticipantsList; namespace osu.Game.Screens.OnlinePlay.Multiplayer @@ -37,7 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } + private ModSelectOverlay extraModSelectOverlay; private MultiplayerMatchSettingsOverlay settingsOverlay; + private Drawable extraModsSection; private IBindable isConnected; @@ -129,10 +134,39 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 5 }, - Children = new Drawable[] + Spacing = new Vector2(0, 10), + Children = new[] { - new OverlinedHeader("Beatmap"), - new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OverlinedHeader("Beatmap"), + new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } + } + }, + extraModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new ModDisplay + { + DisplayUnrankedText = false, + Current = ExtraMods + }, + new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Text = "Select", + Action = () => extraModSelectOverlay.Show() + } + } + } } } } @@ -174,6 +208,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, + extraModSelectOverlay = new SoloModSelectOverlay + { + SelectedMods = { BindTarget = ExtraMods }, + Stacked = false, + IsValidMod = _ => false + }, settingsOverlay = new MultiplayerMatchSettingsOverlay { RelativeSizeAxes = Axes.Both, @@ -219,10 +259,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } + if (extraModSelectOverlay.State.Value == Visibility.Visible) + { + extraModSelectOverlay.Hide(); + return true; + } + return base.OnBackButton(); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SelectedItem.Value = Playlist.FirstOrDefault(); + + if (SelectedItem.Value?.AllowedMods.Any() != true) + { + extraModsSection.Hide(); + extraModSelectOverlay.Hide(); + extraModSelectOverlay.IsValidMod = _ => false; + } + else + { + extraModsSection.Show(); + extraModSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + } + } private void onReadyClick() { From b846146f163a0dfe87d8d5e02f00730387b8de04 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:58:44 +0900 Subject: [PATCH 043/102] Update mods when resuming room subscreen --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 231fb58836..3c4c6ce040 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.OnResuming(last); beginHandlingTrack(); + updateMods(); } public override bool OnExiting(IScreen next) From 426569c2a93244a7a31688a0d96529d752858ebc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 14:57:39 +0900 Subject: [PATCH 044/102] Move common song select implementation for online play --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 91 +------------ .../OnlinePlay/OnlinePlaySongSelect.cs | 124 ++++++++++++++++++ osu.Game/Screens/Select/MatchSongSelect.cs | 34 +---- 3 files changed, 130 insertions(+), 119 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 98e3ca3358..e892570066 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,25 +1,18 @@ // 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.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; @@ -27,67 +20,21 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public class MultiplayerMatchSongSelect : SongSelect, IOnlinePlaySubScreen + public class MultiplayerMatchSongSelect : OnlinePlaySongSelect { - public string ShortTitle => "song selection"; - - public override string Title => ShortTitle.Humanize(); - - [Resolved(typeof(Room), nameof(Room.Playlist))] - private BindableList playlist { get; set; } - [Resolved] private StatefulMultiplayerClient client { get; set; } - private readonly Bindable> freeMods = new Bindable>(Array.Empty()); - - private readonly FreeModSelectOverlay freeModSelectOverlay; private LoadingLayer loadingLayer; - private WorkingBeatmap initialBeatmap; - private RulesetInfo initialRuleset; - private IReadOnlyList initialMods; - - private bool itemSelected; - - public MultiplayerMatchSongSelect() - { - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - - freeModSelectOverlay = new FreeModSelectOverlay - { - SelectedMods = { BindTarget = freeMods }, - IsValidMod = isValidFreeMod, - }; - } - [BackgroundDependencyLoader] private void load() { AddInternal(loadingLayer = new LoadingLayer(true)); - initialBeatmap = Beatmap.Value; - initialRuleset = Ruleset.Value; - initialMods = Mods.Value.ToList(); - - freeMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - - FooterPanels.Add(freeModSelectOverlay); } - protected override bool OnStart() + protected override void OnSetItem(PlaylistItem item) { - itemSelected = true; - var item = new PlaylistItem(); - - item.Beatmap.Value = Beatmap.Value.BeatmapInfo; - item.Ruleset.Value = Ruleset.Value; - - item.RequiredMods.Clear(); - item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); - - item.AllowedMods.Clear(); - item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); - // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) @@ -112,43 +59,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } else { - playlist.Clear(); - playlist.Add(item); + Playlist.Clear(); + Playlist.Add(item); this.Exit(); } - - return true; - } - - public override bool OnExiting(IScreen next) - { - if (!itemSelected) - { - Beatmap.Value = initialBeatmap; - Ruleset.Value = initialRuleset; - Mods.Value = initialMods; - } - - return base.OnExiting(next); } protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay - { - IsValidMod = isValidMod - }; - - protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() - { - var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); - return buttons; - } - - private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; - - private bool isValidFreeMod(Mod mod) => isValidMod(mod) && !(mod is ModRateAdjust) && !(mod is ModTimeRamp); } public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs new file mode 100644 index 0000000000..7c64e00dc4 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -0,0 +1,124 @@ +// 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.Linq; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.Select; + +namespace osu.Game.Screens.OnlinePlay +{ + public abstract class OnlinePlaySongSelect : SongSelect, IOnlinePlaySubScreen + { + public string ShortTitle => "song selection"; + + public override string Title => ShortTitle.Humanize(); + + public override bool AllowEditing => false; + + [Resolved(typeof(Room), nameof(Room.Playlist))] + protected BindableList Playlist { get; private set; } + + private readonly Bindable> freeMods = new Bindable>(Array.Empty()); + private readonly FreeModSelectOverlay freeModSelectOverlay; + + private WorkingBeatmap initialBeatmap; + private RulesetInfo initialRuleset; + private IReadOnlyList initialMods; + private bool itemSelected; + + protected OnlinePlaySongSelect() + { + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; + + freeModSelectOverlay = new FreeModSelectOverlay + { + SelectedMods = { BindTarget = freeMods }, + IsValidMod = IsValidFreeMod, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + initialBeatmap = Beatmap.Value; + initialRuleset = Ruleset.Value; + initialMods = Mods.Value.ToList(); + + freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + FooterPanels.Add(freeModSelectOverlay); + } + + protected sealed override bool OnStart() + { + itemSelected = true; + + var item = new PlaylistItem(); + + item.Beatmap.Value = Beatmap.Value.BeatmapInfo; + item.Ruleset.Value = Ruleset.Value; + + item.RequiredMods.Clear(); + item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); + + OnSetItem(item); + return true; + } + + protected abstract void OnSetItem(PlaylistItem item); + + public override bool OnBackButton() + { + if (freeModSelectOverlay.State.Value == Visibility.Visible) + { + freeModSelectOverlay.Hide(); + return true; + } + + return base.OnBackButton(); + } + + public override bool OnExiting(IScreen next) + { + if (!itemSelected) + { + Beatmap.Value = initialBeatmap; + Ruleset.Value = initialRuleset; + Mods.Value = initialMods; + } + + return base.OnExiting(next); + } + + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + { + IsValidMod = IsValidMod + }; + + protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() + { + var buttons = base.CreateFooterButtons().ToList(); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); + return buttons; + } + + protected virtual bool IsValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + + protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod); + } +} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 98e02a9294..23fe9620fe 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -1,48 +1,27 @@ // 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.Linq; -using Humanizer; using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; namespace osu.Game.Screens.Select { - public class MatchSongSelect : SongSelect, IOnlinePlaySubScreen + public class MatchSongSelect : OnlinePlaySongSelect { - public Action Selected; - - public string ShortTitle => "song selection"; - public override string Title => ShortTitle.Humanize(); - - public override bool AllowEditing => false; - - [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } - [Resolved] private BeatmapManager beatmaps { get; set; } - public MatchSongSelect() - { - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - } - protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea { CreateNewItem = createNewItem }; - protected override bool OnStart() + protected override void OnSetItem(PlaylistItem item) { switch (Playlist.Count) { @@ -56,8 +35,6 @@ namespace osu.Game.Screens.Select } this.Exit(); - - return true; } private void createNewItem() @@ -80,12 +57,5 @@ namespace osu.Game.Screens.Select item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay - { - IsValidMod = isValidMod - }; - - private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } } From b43e529964b31ac40d5230aa41e68876985579d7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 15:06:50 +0900 Subject: [PATCH 045/102] Fix allowed mods being copied into required mods --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e892570066..0c22813e56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,7 +1,9 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -33,6 +35,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AddInternal(loadingLayer = new LoadingLayer(true)); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + } + protected override void OnSetItem(PlaylistItem item) { // If the client is already in a room, update via the client. From 0909c73ead3d4c45495c8bb5d55dfac16386038a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 15:07:56 +0900 Subject: [PATCH 046/102] Once again disallow DT/etc as allowable mods --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 0c22813e56..80bb7c7ac2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -75,6 +75,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); + + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> From fabb0eeb29a35c7e0d212d9c7be9f1aad8e90c35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 17:27:14 +0900 Subject: [PATCH 047/102] Add signalr messagepack key attribute --- osu.Game/Online/Rooms/BeatmapAvailability.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index 4ce797e583..2adeb9b959 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -22,6 +22,7 @@ namespace osu.Game.Online.Rooms /// /// The beatmap's downloading progress, null when not in state. /// + [Key(1)] public readonly float? DownloadProgress; [JsonConstructor] From 1d8de2f718916b907b24bf1b411ebb627258cbd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 17:32:54 +0900 Subject: [PATCH 048/102] Rename class to better match purpose --- ... TestSceneMultiplayerBeatmapAvailabilityTracker.cs} | 10 +++++----- ...er.cs => MultiplayerBeatmapAvailablilityTracker.cs} | 7 +++---- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) rename osu.Game.Tests/Online/{TestSceneMultiplayerBeatmapTracker.cs => TestSceneMultiplayerBeatmapAvailabilityTracker.cs} (94%) rename osu.Game/Online/Rooms/{MultiplayerBeatmapTracker.cs => MultiplayerBeatmapAvailablilityTracker.cs} (93%) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs similarity index 94% rename from osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs rename to osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index 9caecc198a..3c3793670a 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -28,7 +28,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public class TestSceneMultiplayerBeatmapTracker : OsuTestScene + public class TestSceneMultiplayerBeatmapAvailabilityTracker : OsuTestScene { private RulesetStore rulesets; private TestBeatmapManager beatmaps; @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Online private BeatmapSetInfo testBeatmapSet; private readonly Bindable selectedItem = new Bindable(); - private MultiplayerBeatmapTracker tracker; + private MultiplayerBeatmapAvailablilityTracker availablilityTracker; [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Online Ruleset = { Value = testBeatmapInfo.Ruleset }, }; - Child = tracker = new MultiplayerBeatmapTracker + Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem, } }; @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("recreate tracker", () => Child = tracker = new MultiplayerBeatmapTracker + AddStep("recreate tracker", () => Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Online { // In DownloadTrackingComposite, state changes are scheduled one frame later, wait one step. AddWaitStep("wait for potential change", 1); - AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); + AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke())); } private static BeatmapInfo getTestBeatmapInfo(string archiveFile) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs similarity index 93% rename from osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs rename to osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs index 28e4872ad3..578c4db2f8 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public class MultiplayerBeatmapTracker : DownloadTrackingComposite + public class MultiplayerBeatmapAvailablilityTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); @@ -26,11 +26,10 @@ namespace osu.Game.Online.Rooms private readonly Bindable availability = new Bindable(); - public MultiplayerBeatmapTracker() + public MultiplayerBeatmapAvailablilityTracker() { State.BindValueChanged(_ => updateAvailability()); - Progress.BindValueChanged(_ => updateAvailability()); - updateAvailability(); + Progress.BindValueChanged(_ => updateAvailability(), true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c049d4be20..f2cb1120cb 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,11 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected readonly MultiplayerBeatmapTracker BeatmapTracker; + protected readonly MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker; protected RoomSubScreen() { - InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker + InternalChild = BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = SelectedItem }, }; From ac2a995041590370e31b2b5ef9fb440dcb20d43c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 17:54:56 +0900 Subject: [PATCH 049/102] Add user and panel states --- .../TestSceneMultiplayerParticipantsList.cs | 25 ++++++++++++++ .../Online/Multiplayer/IMultiplayerClient.cs | 4 +++ .../Multiplayer/IMultiplayerRoomServer.cs | 4 +++ .../Online/Multiplayer/MultiplayerRoomUser.cs | 7 ++++ .../Multiplayer/StatefulMultiplayerClient.cs | 26 +++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 11 +++++++ .../Participants/ParticipantPanel.cs | 33 ++++++++++++++++++- osu.Game/Screens/Play/HUD/ModDisplay.cs | 14 +++++--- .../Multiplayer/TestMultiplayerClient.cs | 17 ++++++++++ 9 files changed, 136 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 968a869532..9aa1f2cf99 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Users; using osuTK; @@ -123,5 +125,28 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); } + + [Test] + public void TestUserWithMods() + { + AddStep("add user", () => + { + Client.AddUser(new User + { + Id = 0, + Username = $"User 0", + CurrentModeRank = RNG.Next(1, 100000), + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }); + + Client.ChangeUserExtraMods(0, new Mod[] + { + new OsuModHardRock(), + new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } } + }); + }); + + AddToggleStep("toggle ready state", v => Client.ChangeUserState(0, v ? MultiplayerUserState.Ready : MultiplayerUserState.Idle)); + } } } diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 19dd473230..37f60ab036 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Threading.Tasks; +using osu.Game.Online.API; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -55,6 +57,8 @@ namespace osu.Game.Online.Multiplayer /// The new beatmap availability state of the user. Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability); + Task UserExtraModsChanged(int userId, IEnumerable mods); + /// /// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point. /// diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 09816974a7..484acfe957 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Threading.Tasks; +using osu.Game.Online.API; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -47,6 +49,8 @@ namespace osu.Game.Online.Multiplayer /// The proposed new beatmap availability state. Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + Task ChangeExtraMods(IEnumerable newMods); + /// /// As the host of a room, start the match. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 2590acbc81..d7f7f9135e 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -4,7 +4,11 @@ #nullable enable using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Users; @@ -22,6 +26,9 @@ namespace osu.Game.Online.Multiplayer /// public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); + [NotNull] + public IEnumerable ExtraMods { get; set; } = Enumerable.Empty(); + public User? User { get; set; } [JsonConstructor] diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index e5b07ddfb4..33dcf1e8b4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -22,6 +22,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Users; using osu.Game.Utils; @@ -231,6 +232,10 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + public Task ChangeExtraMods(IEnumerable newMods) => ChangeExtraMods(newMods.Select(m => new APIMod(m)).ToList()); + + public abstract Task ChangeExtraMods(IEnumerable newMods); + public abstract Task StartMatch(); Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) @@ -379,6 +384,27 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } + public Task UserExtraModsChanged(int userId, IEnumerable mods) + { + if (Room == null) + return Task.CompletedTask; + + Scheduler.Add(() => + { + var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); + + // errors here are not critical - user mods is mostly for display. + if (user == null) + return; + + user.ExtraMods = mods; + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; + } + Task IMultiplayerClient.LoadRequested() { if (Room == null) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 95dda1a051..c1025e73f8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; @@ -15,6 +16,7 @@ using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -240,6 +242,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); + ExtraMods.BindValueChanged(onExtraModsChanged); client.LoadRequested += onLoadRequested; @@ -285,6 +288,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private void onExtraModsChanged(ValueChangedEvent> extraMods) + { + if (client.Room == null) + return; + + client.ChangeExtraMods(extraMods.NewValue).CatchUnobservedExceptions(); + } + private void onReadyClick() { Debug.Assert(readyClickOperation == null); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f99655e305..059e9e518d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -16,6 +17,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -30,6 +33,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + + private ModDisplay extraModsDisplay; private StateDisplay userStateDisplay; private SpriteIcon crown; @@ -122,11 +129,32 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } } }, - userStateDisplay = new StateDisplay + new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Margin = new MarginPadding { Right = 10 }, + Spacing = new Vector2(10), + Children = new Drawable[] + { + extraModsDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + ExpansionMode = ExpansionMode.AlwaysContracted, + DisplayUnrankedText = false, + ExpandOnAppear = false + }, + userStateDisplay = new StateDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AlwaysPresent = true + } + } } } } @@ -143,7 +171,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); + userStateDisplay.Status = User.State; + extraModsDisplay.Current.Value = User.ExtraMods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 68d019bf71..2d5b07f056 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.Play.HUD public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; + public bool ExpandOnAppear = true; + private readonly Bindable> current = new Bindable>(); public Bindable> Current @@ -108,10 +110,14 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - expand(); - - using (iconsContainer.BeginDelayedSequence(1200)) - contract(); + if (ExpandOnAppear) + { + expand(); + using (iconsContainer.BeginDelayedSequence(1200)) + contract(); + } + else + iconsContainer.TransformSpacingTo(new Vector2(-25, 0)); } private void expand() diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7fbc770351..e699e7fb34 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -11,6 +12,7 @@ using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer @@ -122,6 +124,21 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } + public void ChangeUserExtraMods(int userId, IEnumerable newMods) + => ChangeUserExtraMods(userId, newMods.Select(m => new APIMod(m)).ToList()); + + public void ChangeUserExtraMods(int userId, IEnumerable newMods) + { + Debug.Assert(Room != null); + ((IMultiplayerClient)this).UserExtraModsChanged(userId, newMods.ToList()); + } + + public override Task ChangeExtraMods(IEnumerable newMods) + { + ChangeUserExtraMods(api.LocalUser.Value.Id, newMods); + return Task.CompletedTask; + } + public override Task StartMatch() { Debug.Assert(Room != null); From f53896360715341641dfc7971cb04f66d19fd91d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 17:57:32 +0900 Subject: [PATCH 050/102] Extra mods -> user mods --- .../TestSceneMultiplayerParticipantsList.cs | 2 +- .../Online/Multiplayer/IMultiplayerClient.cs | 2 +- .../Multiplayer/IMultiplayerRoomServer.cs | 2 +- .../Online/Multiplayer/MultiplayerClient.cs | 10 ++++++ .../Online/Multiplayer/MultiplayerRoomUser.cs | 2 +- .../Multiplayer/StatefulMultiplayerClient.cs | 8 ++--- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 +++---- .../Multiplayer/MultiplayerMatchSubScreen.cs | 34 +++++++++---------- .../Participants/ParticipantPanel.cs | 6 ++-- .../Multiplayer/TestMultiplayerClient.cs | 12 +++---- 10 files changed, 50 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 9aa1f2cf99..8caba5d9c8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); - Client.ChangeUserExtraMods(0, new Mod[] + Client.ChangeUserMods(0, new Mod[] { new OsuModHardRock(), new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } } diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 37f60ab036..f22b0e4e28 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.Multiplayer /// The new beatmap availability state of the user. Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability); - Task UserExtraModsChanged(int userId, IEnumerable mods); + Task UserModsChanged(int userId, IEnumerable mods); /// /// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point. diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 484acfe957..71555ae23d 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -49,7 +49,7 @@ namespace osu.Game.Online.Multiplayer /// The proposed new beatmap availability state. Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); - Task ChangeExtraMods(IEnumerable newMods); + Task ChangeUserMods(IEnumerable newMods); /// /// As the host of a room, start the match. diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..ecf314c1e5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -84,6 +85,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); + connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); connection.Closed += async ex => { @@ -182,6 +184,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); } + public override Task ChangeUserMods(IEnumerable newMods) + { + if (!isConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); + } + public override Task StartMatch() { if (!isConnected.Value) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index d7f7f9135e..4c9643bfce 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online.Multiplayer public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); [NotNull] - public IEnumerable ExtraMods { get; set; } = Enumerable.Empty(); + public IEnumerable UserMods { get; set; } = Enumerable.Empty(); public User? User { get; set; } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 33dcf1e8b4..a0e903e89a 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -232,9 +232,9 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); - public Task ChangeExtraMods(IEnumerable newMods) => ChangeExtraMods(newMods.Select(m => new APIMod(m)).ToList()); + public Task ChangeUserMods(IEnumerable newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList()); - public abstract Task ChangeExtraMods(IEnumerable newMods); + public abstract Task ChangeUserMods(IEnumerable newMods); public abstract Task StartMatch(); @@ -384,7 +384,7 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public Task UserExtraModsChanged(int userId, IEnumerable mods) + public Task UserModsChanged(int userId, IEnumerable mods) { if (Room == null) return Task.CompletedTask; @@ -397,7 +397,7 @@ namespace osu.Game.Online.Multiplayer if (user == null) return; - user.ExtraMods = mods; + user.UserMods = mods; RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 3c4c6ce040..6367aa54a7 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Match /// /// Any mods applied by/to the local user. /// - protected readonly Bindable> ExtraMods = new Bindable>(Array.Empty()); + protected readonly Bindable> UserMods = new Bindable>(Array.Empty()); [Resolved] private MusicController music { get; set; } @@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - ExtraMods.BindValueChanged(_ => updateMods()); + UserMods.BindValueChanged(_ => updateMods()); } public override void OnEntering(IScreen last) @@ -108,9 +108,9 @@ namespace osu.Game.Screens.OnlinePlay.Match return; // Remove any extra mods that are no longer allowed. - ExtraMods.Value = ExtraMods.Value - .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) - .ToList(); + UserMods.Value = UserMods.Value + .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) + .ToList(); updateMods(); @@ -134,7 +134,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null) return; - Mods.Value = ExtraMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); + Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); } private void beginHandlingTrack() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c1025e73f8..a31a3e51ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -42,9 +42,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } - private ModSelectOverlay extraModSelectOverlay; + private ModSelectOverlay userModsSelectOverlay; private MultiplayerMatchSettingsOverlay settingsOverlay; - private Drawable extraModsSection; + private Drawable userModsSection; private IBindable isConnected; @@ -149,7 +149,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } } }, - extraModsSection = new FillFlowContainer + userModsSection = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -159,13 +159,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new ModDisplay { DisplayUnrankedText = false, - Current = ExtraMods + Current = UserMods }, new PurpleTriangleButton { RelativeSizeAxes = Axes.X, Text = "Select", - Action = () => extraModSelectOverlay.Show() + Action = () => userModsSelectOverlay.Show() } } } @@ -210,9 +210,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - extraModSelectOverlay = new SoloModSelectOverlay + userModsSelectOverlay = new SoloModSelectOverlay { - SelectedMods = { BindTarget = ExtraMods }, + SelectedMods = { BindTarget = UserMods }, Stacked = false, IsValidMod = _ => false }, @@ -242,7 +242,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); - ExtraMods.BindValueChanged(onExtraModsChanged); + UserMods.BindValueChanged(onUserModsChanged); client.LoadRequested += onLoadRequested; @@ -262,9 +262,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - if (extraModSelectOverlay.State.Value == Visibility.Visible) + if (userModsSelectOverlay.State.Value == Visibility.Visible) { - extraModSelectOverlay.Hide(); + userModsSelectOverlay.Hide(); return true; } @@ -277,23 +277,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (SelectedItem.Value?.AllowedMods.Any() != true) { - extraModsSection.Hide(); - extraModSelectOverlay.Hide(); - extraModSelectOverlay.IsValidMod = _ => false; + userModsSection.Hide(); + userModsSelectOverlay.Hide(); + userModsSelectOverlay.IsValidMod = _ => false; } else { - extraModsSection.Show(); - extraModSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSection.Show(); + userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); } } - private void onExtraModsChanged(ValueChangedEvent> extraMods) + private void onUserModsChanged(ValueChangedEvent> mods) { if (client.Room == null) return; - client.ChangeExtraMods(extraMods.NewValue).CatchUnobservedExceptions(); + client.ChangeUserMods(mods.NewValue).CatchUnobservedExceptions(); } private void onReadyClick() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 059e9e518d..a782da4c39 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private RulesetStore rulesets { get; set; } - private ModDisplay extraModsDisplay; + private ModDisplay userModsDisplay; private StateDisplay userStateDisplay; private SpriteIcon crown; @@ -139,7 +139,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Spacing = new Vector2(10), Children = new Drawable[] { - extraModsDisplay = new ModDisplay + userModsDisplay = new ModDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -174,7 +174,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); userStateDisplay.Status = User.State; - extraModsDisplay.Current.Value = User.ExtraMods.Select(m => m.ToMod(ruleset)).ToList(); + userModsDisplay.Current.Value = User.UserMods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index e699e7fb34..d0d41e56c3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -124,18 +124,18 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public void ChangeUserExtraMods(int userId, IEnumerable newMods) - => ChangeUserExtraMods(userId, newMods.Select(m => new APIMod(m)).ToList()); + public void ChangeUserMods(int userId, IEnumerable newMods) + => ChangeUserMods(userId, newMods.Select(m => new APIMod(m)).ToList()); - public void ChangeUserExtraMods(int userId, IEnumerable newMods) + public void ChangeUserMods(int userId, IEnumerable newMods) { Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserExtraModsChanged(userId, newMods.ToList()); + ((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList()); } - public override Task ChangeExtraMods(IEnumerable newMods) + public override Task ChangeUserMods(IEnumerable newMods) { - ChangeUserExtraMods(api.LocalUser.Value.Id, newMods); + ChangeUserMods(api.LocalUser.Value.Id, newMods); return Task.CompletedTask; } From 3cd30d284eb01689fab740342244ad33e37d66f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:08:49 +0900 Subject: [PATCH 051/102] Renamespace --- .../TestSceneFreeModSelectOverlay.cs | 2 +- .../OnlinePlay/Match/FooterButtonFreeMods.cs | 63 +++++++++++++++++++ .../FreeModSelectOverlay.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 54 ---------------- .../OnlinePlay/OnlinePlaySongSelect.cs | 2 +- 5 files changed, 66 insertions(+), 57 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs rename osu.Game/Screens/OnlinePlay/{Multiplayer => Match}/FreeModSelectOverlay.cs (98%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 960402df88..b1700d5b6e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Mods; -using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Match; namespace osu.Game.Tests.Visual.Multiplayer { diff --git a/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs new file mode 100644 index 0000000000..ca2db877c3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Match +{ + public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> + { + public Bindable> Current + { + get => modDisplay.Current; + set => modDisplay.Current = value; + } + + private readonly ModDisplay modDisplay; + + public FooterButtonFreeMods() + { + ButtonContentContainer.Add(modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DisplayUnrankedText = false, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"freemods"; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateModDisplay(), true); + } + + private void updateModDisplay() + { + if (Current.Value?.Count > 0) + modDisplay.FadeIn(); + else + modDisplay.FadeOut(); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs similarity index 98% rename from osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs rename to osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 8334df1e44..aba86a3d72 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; -namespace osu.Game.Screens.OnlinePlay.Multiplayer +namespace osu.Game.Screens.OnlinePlay.Match { public class FreeModSelectOverlay : ModSelectOverlay { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 80bb7c7ac2..e6b7656986 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -2,23 +2,15 @@ // 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.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { @@ -78,50 +70,4 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } - - public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> - { - public Bindable> Current - { - get => modDisplay.Current; - set => modDisplay.Current = value; - } - - private readonly ModDisplay modDisplay; - - public FooterButtonFreeMods() - { - ButtonContentContainer.Add(modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - DisplayUnrankedText = false, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SelectedColour = colours.Yellow; - DeselectedColour = SelectedColour.Opacity(0.5f); - Text = @"freemods"; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(_ => updateModDisplay(), true); - } - - private void updateModDisplay() - { - if (Current.Value?.Count > 0) - modDisplay.FadeIn(); - else - modDisplay.FadeOut(); - } - } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 7c64e00dc4..b75bf93204 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -15,7 +15,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.Select; namespace osu.Game.Screens.OnlinePlay From 3e74f8fd9e260c48b4240a98bd7bfd2eef2c0598 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:11:20 +0900 Subject: [PATCH 052/102] Disable customisation of freemods, move stacking to property --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 28 ++++++++----------- .../OnlinePlay/Match/FreeModSelectOverlay.cs | 7 ++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++++-- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index db9460c494..56246a031d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -39,6 +39,16 @@ namespace osu.Game.Overlays.Mods protected readonly OsuSpriteText MultiplierLabel; + /// + /// Whether to allow customisation of mod settings. + /// + protected virtual bool AllowCustomisation => true; + + /// + /// Whether mod icons should be stacked, or appear as individual buttons. + /// + protected virtual bool Stacked => true; + protected override bool BlockNonPositionalInput => false; protected override bool DimMainContent => false; @@ -47,21 +57,6 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; - private bool stacked = true; - - /// - /// Whether mod icons should be stacked, or appear as individual buttons. - /// - public bool Stacked - { - get => stacked; - set - { - stacked = value; - updateAvailableMods(); - } - } - [NotNull] private Func isValidMod = m => true; @@ -307,6 +302,7 @@ namespace osu.Game.Overlays.Mods CustomiseButton = new TriangleButton { Width = 180, + Alpha = AllowCustomisation ? 1 : 0, Text = "Customisation", Action = () => ModSettingsContainer.ToggleVisibility(), Enabled = { Value = false }, @@ -445,7 +441,7 @@ namespace osu.Game.Overlays.Mods { IEnumerable modEnumeration = availableMods.Value[section.ModType]; - if (!stacked) + if (!Stacked) modEnumeration = ModValidation.FlattenMods(modEnumeration); section.Mods = modEnumeration.Where(IsValidMod); diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index aba86a3d72..0d62cc3d37 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -14,10 +14,9 @@ namespace osu.Game.Screens.OnlinePlay.Match { public class FreeModSelectOverlay : ModSelectOverlay { - public FreeModSelectOverlay() - { - Stacked = false; - } + protected override bool AllowCustomisation => false; + + protected override bool Stacked => false; protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a31a3e51ee..22a58add70 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -210,10 +210,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - userModsSelectOverlay = new SoloModSelectOverlay + userModsSelectOverlay = new UserModSelectOverlay { SelectedMods = { BindTarget = UserMods }, - Stacked = false, IsValidMod = _ => false }, settingsOverlay = new MultiplayerMatchSettingsOverlay @@ -352,5 +351,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; } + + private class UserModSelectOverlay : SoloModSelectOverlay + { + protected override bool Stacked => false; + } } } From e134af82f52373cd3b360ab31139271c5d91007a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:16:38 +0900 Subject: [PATCH 053/102] Stack freemods for the local user --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++++++++++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 7 +------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 56246a031d..2e69f15ff5 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -444,10 +444,20 @@ namespace osu.Game.Overlays.Mods if (!Stacked) modEnumeration = ModValidation.FlattenMods(modEnumeration); - section.Mods = modEnumeration.Where(IsValidMod); + section.Mods = modEnumeration.Select(validModOrNull).Where(m => m != null); } } + [CanBeNull] + private Mod validModOrNull([NotNull] Mod mod) + { + if (!(mod is MultiMod multi)) + return IsValidMod(mod) ? mod : null; + + var validSubset = multi.Mods.Select(validModOrNull).Where(m => m != null).ToArray(); + return validSubset.Length == 0 ? null : new MultiMod(validSubset); + } + private void selectedModsChanged(ValueChangedEvent> mods) { foreach (var section in ModSectionsContainer.Children) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 22a58add70..659551abfd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -210,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - userModsSelectOverlay = new UserModSelectOverlay + userModsSelectOverlay = new SoloModSelectOverlay { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false @@ -351,10 +351,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; } - - private class UserModSelectOverlay : SoloModSelectOverlay - { - protected override bool Stacked => false; - } } } From 51cb2887172fb330a8b5ff40961e458c08ef7024 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:18:59 +0900 Subject: [PATCH 054/102] Reduce mod selection height --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 659551abfd..87ae723c63 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -210,10 +210,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - userModsSelectOverlay = new SoloModSelectOverlay + new Container { - SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Child = userModsSelectOverlay = new SoloModSelectOverlay + { + SelectedMods = { BindTarget = UserMods }, + IsValidMod = _ => false + } }, settingsOverlay = new MultiplayerMatchSettingsOverlay { From 3a906a89fce84a1dd7195af89aa7808276c1c70a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:25:09 +0900 Subject: [PATCH 055/102] Pin mod position in participant panels --- .../TestSceneMultiplayerParticipantsList.cs | 6 +++- .../Participants/ParticipantPanel.cs | 33 ++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 8caba5d9c8..768dc6512c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -146,7 +146,11 @@ namespace osu.Game.Tests.Visual.Multiplayer }); }); - AddToggleStep("toggle ready state", v => Client.ChangeUserState(0, v ? MultiplayerUserState.Ready : MultiplayerUserState.Idle)); + for (var i = MultiplayerUserState.Idle; i < MultiplayerUserState.Results; i++) + { + var state = i; + AddStep($"set state: {state}", () => Client.ChangeUserState(0, state)); + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index a782da4c39..f6a60c8f57 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -129,32 +129,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } } }, - new FillFlowContainer + new Container { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Right = 10 }, - Spacing = new Vector2(10), - Children = new Drawable[] + Margin = new MarginPadding { Right = 70 }, + Child = userModsDisplay = new ModDisplay { - userModsDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f), - ExpansionMode = ExpansionMode.AlwaysContracted, - DisplayUnrankedText = false, - ExpandOnAppear = false - }, - userStateDisplay = new StateDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AlwaysPresent = true - } + Scale = new Vector2(0.5f), + ExpansionMode = ExpansionMode.AlwaysContracted, + DisplayUnrankedText = false, + ExpandOnAppear = false } + }, + userStateDisplay = new StateDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, } } } From 89a42d60fbc8f33c6cd28d2baa617b33c57b1312 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:50:32 +0900 Subject: [PATCH 056/102] General cleanup --- osu.Game.Tests/Mods/ModValidationTest.cs | 14 ++++----- .../TestSceneFreeModSelectOverlay.cs | 5 +-- .../Online/Multiplayer/IMultiplayerClient.cs | 5 +++ .../Multiplayer/IMultiplayerRoomServer.cs | 4 +++ .../Online/Multiplayer/MultiplayerRoomUser.cs | 3 ++ .../Multiplayer/StatefulMultiplayerClient.cs | 6 +++- osu.Game/Overlays/Mods/ModButton.cs | 8 ++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++++-- .../OnlinePlay/Match/FreeModSelectOverlay.cs | 31 ++----------------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 18 +++++++++-- osu.Game/Screens/Play/HUD/ModDisplay.cs | 3 ++ osu.Game/Screens/Select/MatchSongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 4 +++ .../Utils/{ModValidation.cs => ModUtils.cs} | 24 +++----------- 16 files changed, 71 insertions(+), 72 deletions(-) rename osu.Game/Utils/{ModValidation.cs => ModUtils.cs} (79%) diff --git a/osu.Game.Tests/Mods/ModValidationTest.cs b/osu.Game.Tests/Mods/ModValidationTest.cs index c7a7e242e9..991adc221e 100644 --- a/osu.Game.Tests/Mods/ModValidationTest.cs +++ b/osu.Game.Tests/Mods/ModValidationTest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Mods public void TestModIsCompatibleByItself() { var mod = new Mock(); - Assert.That(ModValidation.CheckCompatible(new[] { mod.Object })); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); } [Test] @@ -27,8 +27,8 @@ namespace osu.Game.Tests.Mods mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); // Test both orderings. - Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, mod2.Object }), Is.False); - Assert.That(ModValidation.CheckCompatible(new[] { mod2.Object, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); } [Test] @@ -43,22 +43,22 @@ namespace osu.Game.Tests.Mods var multiMod = new MultiMod(new MultiMod(mod2.Object)); // Test both orderings. - Assert.That(ModValidation.CheckCompatible(new[] { multiMod, mod1.Object }), Is.False); - Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, multiMod }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); } [Test] public void TestAllowedThroughMostDerivedType() { var mod = new Mock(); - Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); + Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); } [Test] public void TestNotAllowedThroughBaseType() { var mod = new Mock(); - Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); + Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index b1700d5b6e..e3342eb6a0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -3,19 +3,16 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Mods; using osu.Game.Screens.OnlinePlay.Match; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneFreeModSelectOverlay : MultiplayerTestScene { - private ModSelectOverlay overlay; - [SetUp] public new void Setup() => Schedule(() => { - Child = overlay = new FreeModSelectOverlay + Child = new FreeModSelectOverlay { State = { Value = Visibility.Visible } }; diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index f22b0e4e28..6d7b9d24d6 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -57,6 +57,11 @@ namespace osu.Game.Online.Multiplayer /// The new beatmap availability state of the user. Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability); + /// + /// Signals that a user in this room changed their local mods. + /// + /// The ID of the user whose mods have changed. + /// The user's new local mods. Task UserModsChanged(int userId, IEnumerable mods); /// diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 71555ae23d..3527ce6314 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -49,6 +49,10 @@ namespace osu.Game.Online.Multiplayer /// The proposed new beatmap availability state. Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + /// + /// Change the local user's mods in the currently joined room. + /// + /// The proposed new mods, excluding any required by the room itself. Task ChangeUserMods(IEnumerable newMods); /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 4c9643bfce..7de24826dd 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -26,6 +26,9 @@ namespace osu.Game.Online.Multiplayer /// public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); + /// + /// Any mods applicable only to the local user. + /// [NotNull] public IEnumerable UserMods { get; set; } = Enumerable.Empty(); diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index a0e903e89a..597bee2764 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -232,6 +232,10 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + /// + /// Change the local user's mods in the currently joined room. + /// + /// The proposed new mods, excluding any required by the room itself. public Task ChangeUserMods(IEnumerable newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList()); public abstract Task ChangeUserMods(IEnumerable newMods); @@ -393,7 +397,7 @@ namespace osu.Game.Online.Multiplayer { var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); - // errors here are not critical - user mods is mostly for display. + // errors here are not critical - user mods are mostly for display. if (user == null) return; diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 9ea4f65eb2..ab8efdabcc 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -174,7 +174,7 @@ namespace osu.Game.Overlays.Mods switch (e.Button) { case MouseButton.Right: - OnRightClick(e); + SelectNext(-1); break; } } @@ -183,12 +183,8 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { SelectNext(1); - return true; - } - protected virtual void OnRightClick(MouseUpEvent e) - { - SelectNext(-1); + return true; } /// diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 2e69f15ff5..2950dc7489 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = m => true; /// - /// A function that checks whether a given mod is valid. + /// A function that checks whether a given mod is selectable. /// [NotNull] public Func IsValidMod @@ -442,12 +442,20 @@ namespace osu.Game.Overlays.Mods IEnumerable modEnumeration = availableMods.Value[section.ModType]; if (!Stacked) - modEnumeration = ModValidation.FlattenMods(modEnumeration); + modEnumeration = ModUtils.FlattenMods(modEnumeration); section.Mods = modEnumeration.Select(validModOrNull).Where(m => m != null); } } + /// + /// Returns a valid form of a given if possible, or null otherwise. + /// + /// + /// This is a recursive process during which any invalid mods are culled while preserving structures where possible. + /// + /// The to check. + /// A valid form of if exists, or null otherwise. [CanBeNull] private Mod validModOrNull([NotNull] Mod mod) { diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 0d62cc3d37..3cdf25fad0 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -5,13 +5,15 @@ using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.OnlinePlay.Match { + /// + /// A used for free-mod selection in online play. + /// public class FreeModSelectOverlay : ModSelectOverlay { protected override bool AllowCustomisation => false; @@ -29,8 +31,6 @@ namespace osu.Game.Screens.OnlinePlay.Match { } - protected override ModButton CreateModButton(Mod mod) => new FreeModButton(mod); - protected override Drawable CreateHeader(string text) => new Container { AutoSizeAxes = Axes.Y, @@ -47,7 +47,6 @@ namespace osu.Game.Screens.OnlinePlay.Match foreach (var button in ButtonsContainer.OfType()) { if (value) - // Note: Buttons where only part of the group has an implementation are not fully supported. button.SelectAt(0); else button.Deselect(); @@ -77,29 +76,5 @@ namespace osu.Game.Screens.OnlinePlay.Match Changed?.Invoke(value); } } - - private class FreeModButton : ModButton - { - public FreeModButton(Mod mod) - : base(mod) - { - } - - protected override bool OnClick(ClickEvent e) - { - onClick(); - return true; - } - - protected override void OnRightClick(MouseUpEvent e) => onClick(); - - private void onClick() - { - if (Selected) - Deselect(); - else - SelectNext(1); - } - } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6367aa54a7..f4136c895f 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null) return; - // Remove any extra mods that are no longer allowed. + // Remove any user mods that are no longer allowed. UserMods.Value = UserMods.Value .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) .ToList(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e6b7656986..08c00e8372 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); } - protected override void OnSetItem(PlaylistItem item) + protected override void SelectItem(PlaylistItem item) { // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b75bf93204..46be5591a8 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -76,11 +76,15 @@ namespace osu.Game.Screens.OnlinePlay item.AllowedMods.Clear(); item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); - OnSetItem(item); + SelectItem(item); return true; } - protected abstract void OnSetItem(PlaylistItem item); + /// + /// Invoked when the user has requested a selection of a beatmap. + /// + /// The resultant . This item has not yet been added to the 's. + protected abstract void SelectItem(PlaylistItem item); public override bool OnBackButton() { @@ -117,8 +121,18 @@ namespace osu.Game.Screens.OnlinePlay return buttons; } + /// + /// Checks whether a given is valid for global selection. + /// + /// The to check. + /// Whether is a valid mod for online play. protected virtual bool IsValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + /// + /// Checks whether a given is valid for per-player free-mod selection. + /// + /// The to check. + /// Whether is a selectable free-mod. protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod); } } diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 2d5b07f056..ce1a8a3205 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -26,6 +26,9 @@ namespace osu.Game.Screens.Play.HUD public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; + /// + /// Whether the mods should initially appear expanded, before potentially contracting into their final expansion state (depending on ). + /// public bool ExpandOnAppear = true; private readonly Bindable> current = new Bindable>(); diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 23fe9620fe..1de3e0e989 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select CreateNewItem = createNewItem }; - protected override void OnSetItem(PlaylistItem item) + protected override void SelectItem(PlaylistItem item) { switch (Playlist.Count) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4af96b7a29..ed6e0a1028 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -300,6 +300,10 @@ namespace osu.Game.Screens.Select } } + /// + /// Creates the buttons to be displayed in the footer. + /// + /// A set of and an optional which the button opens when pressed. protected virtual IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[] { (new FooterButtonMods { Current = Mods }, ModSelect), diff --git a/osu.Game/Utils/ModValidation.cs b/osu.Game/Utils/ModUtils.cs similarity index 79% rename from osu.Game/Utils/ModValidation.cs rename to osu.Game/Utils/ModUtils.cs index 3597396ec4..808dba2900 100644 --- a/osu.Game/Utils/ModValidation.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -12,9 +12,9 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Utils { /// - /// A set of utilities to validate combinations. + /// A set of utilities to handle combinations. /// - public static class ModValidation + public static class ModUtils { /// /// Checks that all s are compatible with each-other, and that all appear within a set of allowed types. @@ -25,11 +25,11 @@ namespace osu.Game.Utils /// The s to check. /// The set of allowed types. /// Whether all s are compatible with each-other and appear in the set of allowed types. - public static bool CheckCompatibleAndAllowed(IEnumerable combination, IEnumerable allowedTypes) + public static bool CheckCompatibleSetAndAllowed(IEnumerable combination, IEnumerable allowedTypes) { // Prevent multiple-enumeration. var combinationList = combination as ICollection ?? combination.ToArray(); - return CheckCompatible(combinationList) && CheckAllowed(combinationList, allowedTypes); + return CheckCompatibleSet(combinationList) && CheckAllowed(combinationList, allowedTypes); } /// @@ -37,7 +37,7 @@ namespace osu.Game.Utils /// /// The combination to check. /// Whether all s in the combination are compatible with each-other. - public static bool CheckCompatible(IEnumerable combination) + public static bool CheckCompatibleSet(IEnumerable combination) { var incompatibleTypes = new HashSet(); var incomingTypes = new HashSet(); @@ -83,20 +83,6 @@ namespace osu.Game.Utils .All(m => allowedSet.Contains(m.GetType())); } - /// - /// Determines whether a is in a set of incompatible types. - /// - /// - /// A can be incompatible through its most-declared type or any of its base types. - /// - /// The to test. - /// The set of incompatible types. - /// Whether the given is incompatible. - private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) - => FlattenMod(mod) - .SelectMany(m => m.GetType().EnumerateBaseTypes()) - .Any(incompatibleTypes.Contains); - /// /// Flattens a set of s, returning a new set with all s removed. /// From ee92ec0a5c2e6120f45b36f14b9a678ecab78e8a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:54:47 +0900 Subject: [PATCH 057/102] Disallow local user mod customisation --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 87ae723c63..f09c7168b3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, Height = 0.5f, - Child = userModsSelectOverlay = new SoloModSelectOverlay + Child = userModsSelectOverlay = new UserModSelectOverlay { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false @@ -358,5 +358,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; } + + private class UserModSelectOverlay : ModSelectOverlay + { + protected override bool AllowCustomisation => false; + } } } From 76ebb3811a03ea45fb5279fab26c9951fe199ba9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:05:02 +0900 Subject: [PATCH 058/102] Fix mod icons potentially having incorrect colours --- osu.Game/Rulesets/UI/ModIcon.cs | 49 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 8ea6c74349..76fcb8e080 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - private readonly ModType type; - public virtual string TooltipText => mod.IconTooltip; private Mod mod; @@ -38,16 +36,22 @@ namespace osu.Game.Rulesets.UI set { mod = value; - updateMod(value); + + if (LoadState >= LoadState.Ready) + updateMod(value); } } + [Resolved] + private OsuColour colours { get; set; } + + private Color4 backgroundColour; + private Color4 highlightedColour; + public ModIcon(Mod mod) { this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); - type = mod.Type; - Size = new Vector2(size); Children = new Drawable[] @@ -79,10 +83,20 @@ namespace osu.Game.Rulesets.UI Icon = FontAwesome.Solid.Question }, }; + } + [BackgroundDependencyLoader] + private void load() + { updateMod(mod); } + protected override void LoadComplete() + { + base.LoadComplete(); + Selected.BindValueChanged(_ => updateColour(), true); + } + private void updateMod(Mod value) { modAcronym.Text = value.Acronym; @@ -92,20 +106,14 @@ namespace osu.Game.Rulesets.UI { modIcon.FadeOut(); modAcronym.FadeIn(); - return; + } + else + { + modIcon.FadeIn(); + modAcronym.FadeOut(); } - modIcon.FadeIn(); - modAcronym.FadeOut(); - } - - private Color4 backgroundColour; - private Color4 highlightedColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (type) + switch (value.Type) { default: case ModType.DifficultyIncrease: @@ -139,12 +147,13 @@ namespace osu.Game.Rulesets.UI modIcon.Colour = colours.Yellow; break; } + + updateColour(); } - protected override void LoadComplete() + private void updateColour() { - base.LoadComplete(); - Selected.BindValueChanged(selected => background.Colour = selected.NewValue ? highlightedColour : backgroundColour, true); + background.Colour = Selected.Value ? highlightedColour : backgroundColour; } } } From e5ca9b1e500c06e1cc084bcf4f8d1cc8994a3474 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:28:33 +0900 Subject: [PATCH 059/102] Remove usage of removed method --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3322c8ede1..9e1e9f3d69 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.Room == null) return; - client.ChangeUserMods(mods.NewValue).CatchUnobservedExceptions(); + client.ChangeUserMods(mods.NewValue); } private void onReadyClick() From 97247b7a673fa552b9f8f5b66aa87e9dfe2293c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:59:18 +0900 Subject: [PATCH 060/102] Fix unset key --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index a25c332b47..d0e19d9f37 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -33,6 +33,7 @@ namespace osu.Game.Online.Multiplayer public IEnumerable Mods { get; set; } = Enumerable.Empty(); [NotNull] + [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); public bool Equals(MultiplayerRoomSettings other) From 97e3023df9cb680feedadd089b2eb326078a2e39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 20:16:58 +0900 Subject: [PATCH 061/102] Renamespace/rename MatchSongSelect -> PlaylistsSongSelect --- .../Visual/Multiplayer/TestSceneMatchSongSelect.cs | 8 ++++---- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 3 +-- .../Playlists/PlaylistsSongSelect.cs} | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) rename osu.Game/Screens/{Select/MatchSongSelect.cs => OnlinePlay/Playlists/PlaylistsSongSelect.cs} (91%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index e0fd7d9874..86429ac50e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; -using osu.Game.Screens.Select; +using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Multiplayer { @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private RulesetStore rulesets; - private TestMatchSongSelect songSelect; + private TestPlaylistsSongSelect songSelect; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Beatmap.SetDefault(); }); - AddStep("create song select", () => LoadScreen(songSelect = new TestMatchSongSelect())); + AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect())); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); } @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); } - private class TestMatchSongSelect : MatchSongSelect + private class TestPlaylistsSongSelect : PlaylistsSongSelect { public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 22580f0537..cedde373b3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -13,7 +13,6 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; -using osu.Game.Screens.Select; using osu.Game.Users; using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer; @@ -188,7 +187,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists settingsOverlay = new PlaylistsMatchSettingsOverlay { RelativeSizeAxes = Axes.Both, - EditPlaylist = () => this.Push(new MatchSongSelect()), + EditPlaylist = () => this.Push(new PlaylistsSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } }; diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs similarity index 91% rename from osu.Game/Screens/Select/MatchSongSelect.cs rename to osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 1de3e0e989..0e8db6dfe5 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -6,12 +6,12 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.Select; -namespace osu.Game.Screens.Select +namespace osu.Game.Screens.OnlinePlay.Playlists { - public class MatchSongSelect : OnlinePlaySongSelect + public class PlaylistsSongSelect : OnlinePlaySongSelect { [Resolved] private BeatmapManager beatmaps { get; set; } From f166c4c4148553e381f694cbdb7cca3fb65d7cf1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 11:04:09 +0900 Subject: [PATCH 062/102] Rename test --- ...tSceneMatchSongSelect.cs => TestScenePlaylistsSongSelect.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneMatchSongSelect.cs => TestScenePlaylistsSongSelect.cs} (99%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs similarity index 99% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs rename to osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 86429ac50e..2f7e59f800 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -23,7 +23,7 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchSongSelect : RoomTestScene + public class TestScenePlaylistsSongSelect : RoomTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } From 4cf52077b6dbb51bfb48f4589ed88f47cab886ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 11:11:28 +0900 Subject: [PATCH 063/102] Make checkbox also respond to all mods selected --- osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 3cdf25fad0..9a3c87f1ff 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -57,12 +57,8 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.Update(); - // If any of the buttons aren't selected, deselect the checkbox. - foreach (var button in ButtonsContainer.OfType()) - { - if (button.Mods.Any(m => m.HasImplementation) && !button.Selected) - checkbox.Current.Value = false; - } + var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + checkbox.Current.Value = validButtons.All(b => b.Selected); } } From b54f65c28279ffcc4d53b9eebdac6c1b73ac4d30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 12:48:15 +0900 Subject: [PATCH 064/102] Exclude more mods from multiplayer --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 08c00e8372..f7f0402555 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -68,6 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust) && !mod.RequiresConfiguration; } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 46be5591a8..755dbdb55e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; /// /// Checks whether a given is valid for per-player free-mod selection. From 7a14e14e67db572f8ec1c8154d6e2e0111a0429b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 12:49:49 +0900 Subject: [PATCH 065/102] Refactor condition This won't make any noticeable difference, but is the more correct way to handle MultiMod because flattening works through infinite recursion levels. --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 755dbdb55e..b0062720e0 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.Select; +using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay { @@ -126,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !ModUtils.FlattenMod(mod).Any(m => m is ModAutoplay); /// /// Checks whether a given is valid for per-player free-mod selection. From 9c3c0895cf2a63d3a7517d53fecf8a0d64c149eb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:03:46 +0900 Subject: [PATCH 066/102] Hide customise button + multiplier label --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 ++- osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25cb75f73a..a7bfac4088 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,6 +37,7 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; + protected readonly Drawable MultiplierSection; protected readonly OsuSpriteText MultiplierLabel; /// @@ -316,7 +317,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - new FillFlowContainer + MultiplierSection = new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(footer_button_spacing / 2, 0), diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 9a3c87f1ff..f22c6603e5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -20,6 +20,12 @@ namespace osu.Game.Screens.OnlinePlay.Match protected override bool Stacked => false; + public FreeModSelectOverlay() + { + CustomiseButton.Alpha = 0; + MultiplierSection.Alpha = 0; + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection From 87f9e46b164c540d9f7a531510521087f3840a0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:37:25 +0900 Subject: [PATCH 067/102] Add option to select all --- osu.Game/Overlays/Mods/ModSection.cs | 8 +++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 +++--- .../OnlinePlay/Match/FreeModSelectOverlay.cs | 40 +++++++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 5de629424b..993f4ef9d7 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -91,7 +91,13 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + public void SelectAll() + { + foreach (var button in buttons.Where(b => !b.Selected)) + button.SelectAt(0); + } + + public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); /// /// Deselect one or more mods in this section. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a7bfac4088..087990d3f8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -33,6 +33,7 @@ namespace osu.Game.Overlays.Mods { public const float HEIGHT = 510; + protected readonly FillFlowContainer FooterContainer; protected readonly TriangleButton DeselectAllButton; protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; @@ -85,8 +86,6 @@ namespace osu.Game.Overlays.Mods private const float content_width = 0.8f; private const float footer_button_spacing = 20; - private readonly FillFlowContainer footerContainer; - private SampleChannel sampleOn, sampleOff; protected ModSelectOverlay() @@ -275,7 +274,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - footerContainer = new FillFlowContainer + FooterContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -385,8 +384,8 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - footerContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); foreach (var section in ModSectionsContainer.Children) { @@ -400,8 +399,8 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); foreach (var section in ModSectionsContainer.Children) { diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index f22c6603e5..9a676930a0 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -24,6 +24,46 @@ namespace osu.Game.Screens.OnlinePlay.Match { CustomiseButton.Alpha = 0; MultiplierSection.Alpha = 0; + DeselectAllButton.Alpha = 0; + + Drawable selectAllButton; + Drawable deselectAllButton; + + FooterContainer.AddRange(new[] + { + selectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Select All", + Action = selectAll, + }, + // Unlike the base mod select overlay, this button deselects mods instantaneously. + deselectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Deselect All", + Action = deselectAll, + }, + }); + + FooterContainer.SetLayoutPosition(selectAllButton, -2); + FooterContainer.SetLayoutPosition(deselectAllButton, -1); + } + + private void selectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.SelectAll(); + } + + private void deselectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.DeselectAll(true); } protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); From 1d3dff8c75988867dce9fd6124fdb08bcb84e344 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:41:01 +0900 Subject: [PATCH 068/102] Refactor ModDisplay flag usage --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index ce1a8a3205..052484afc4 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -113,14 +113,10 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - if (ExpandOnAppear) - { - expand(); - using (iconsContainer.BeginDelayedSequence(1200)) - contract(); - } - else - iconsContainer.TransformSpacingTo(new Vector2(-25, 0)); + expand(); + + using (iconsContainer.BeginDelayedSequence(ExpandOnAppear ? 1200 : 0)) + contract(); } private void expand() From 53cfc3bc6ea4ddda9b8faf32a97b954e5a09c4f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:42:45 +0900 Subject: [PATCH 069/102] Make ModIcon a bit more safe --- osu.Game/Rulesets/UI/ModIcon.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 981fe7f4a6..2ff59f4d1a 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.UI { mod = value; - if (LoadState >= LoadState.Ready) + if (IsLoaded) updateMod(value); } } @@ -98,13 +98,15 @@ namespace osu.Game.Rulesets.UI [BackgroundDependencyLoader] private void load() { - updateMod(mod); } protected override void LoadComplete() { base.LoadComplete(); - Selected.BindValueChanged(_ => updateColour(), true); + + Selected.BindValueChanged(_ => updateColour()); + + updateMod(mod); } private void updateMod(Mod value) From 173e20938cd0b87fc47747cc13b8deba60bb9bea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:49:58 +0900 Subject: [PATCH 070/102] Revert changes to ModDisplay --- .../Multiplayer/Participants/ParticipantPanel.cs | 1 - osu.Game/Screens/Play/HUD/ModDisplay.cs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8b907066a8..8036e5f702 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -139,7 +139,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Scale = new Vector2(0.5f), ExpansionMode = ExpansionMode.AlwaysContracted, DisplayUnrankedText = false, - ExpandOnAppear = false } }, userStateDisplay = new StateDisplay diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 052484afc4..68d019bf71 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -26,11 +26,6 @@ namespace osu.Game.Screens.Play.HUD public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; - /// - /// Whether the mods should initially appear expanded, before potentially contracting into their final expansion state (depending on ). - /// - public bool ExpandOnAppear = true; - private readonly Bindable> current = new Bindable>(); public Bindable> Current @@ -115,7 +110,7 @@ namespace osu.Game.Screens.Play.HUD expand(); - using (iconsContainer.BeginDelayedSequence(ExpandOnAppear ? 1200 : 0)) + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } From 4194c9308eedc24e5ad6cc6315eb945e0838a7a8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:50:05 +0900 Subject: [PATCH 071/102] Add xmldoc --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 087990d3f8..b20c2d9d19 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -288,7 +288,7 @@ namespace osu.Game.Overlays.Mods Vertical = 15, Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, - Children = new Drawable[] + Children = new[] { DeselectAllButton = new TriangleButton { @@ -509,6 +509,10 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } + /// + /// Invoked when a new has been selected. + /// + /// The that has been selected. protected virtual void OnModSelected(Mod mod) { } From 0bce9d68335d78f9c9286af6520e330f3d3f5c59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:54:27 +0900 Subject: [PATCH 072/102] Clear freemods when ruleset is changed --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b0062720e0..c58632c500 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -60,6 +60,13 @@ namespace osu.Game.Screens.OnlinePlay freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FooterPanels.Add(freeModSelectOverlay); + + Ruleset.BindValueChanged(onRulesetChanged); + } + + private void onRulesetChanged(ValueChangedEvent ruleset) + { + freeMods.Value = Array.Empty(); } protected sealed override bool OnStart() From 6fdaf025182f29e39243240dff05d85ac6820810 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 17:57:23 +0900 Subject: [PATCH 073/102] Hook up room-level max attempts to UI --- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 ++ .../PlaylistsMatchSettingsOverlay.cs | 45 +++++++------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 64792a32f3..b2f3e4a1d9 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -39,6 +39,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable MaxParticipants { get; private set; } + [Resolved(typeof(Room))] + protected Bindable MaxAttempts { get; private set; } + [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 01f9920609..bf85ecf13d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -42,15 +42,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public Action EditPlaylist; - public OsuTextBox NameField, MaxParticipantsField; + public OsuTextBox NameField, MaxParticipantsField, MaxAttemptsField; public OsuDropdown DurationField; public RoomAvailabilityPicker AvailabilityPicker; - public GameTypePicker TypePicker; public TriangleButton ApplyButton; public OsuSpriteText ErrorText; - private OsuSpriteText typeLabel; private LoadingLayer loadingLayer; private DrawableRoomPlaylist playlist; private OsuSpriteText playlistLength; @@ -134,6 +132,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } } }, + new Section("Allowed attempts (across all playlist items)") + { + Child = MaxAttemptsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + PlaceholderText = "Unlimited", + }, + }, new Section("Room visibility") { Alpha = disabled_alpha, @@ -142,30 +149,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Enabled = { Value = false } }, }, - new Section("Game type") - { - Alpha = disabled_alpha, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(7), - Children = new Drawable[] - { - TypePicker = new GameTypePicker - { - RelativeSizeAxes = Axes.X, - Enabled = { Value = false } - }, - typeLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14), - Colour = colours.Yellow - }, - }, - }, - }, new Section("Max participants") { Alpha = disabled_alpha, @@ -294,10 +277,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); - Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); @@ -326,13 +307,17 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RoomName.Value = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; - Type.Value = TypePicker.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) MaxParticipants.Value = max; else MaxParticipants.Value = null; + if (int.TryParse(MaxAttemptsField.Text, out max)) + MaxAttempts.Value = max; + else + MaxAttempts.Value = null; + Duration.Value = DurationField.Current.Value; manager?.CreateRoom(currentRoom.Value, onSuccess, onError); From 90acdd4361550d251ede8fc93db8edda2c5a38d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:06:52 +0900 Subject: [PATCH 074/102] Display the correct error message on score submission failure The server will return a valid error message in most cases here. We may eventually want to add some fallback message for cases an error may occur that isn't of [InvariantException](https://github.com/ppy/osu-web/blob/3169b33ccc4c540be5f20136393ad5f00d635ff9/app/Exceptions/InvariantException.php#L9-L10) type, but I'm not 100% sure how to identify these just yet. --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 2c3e7a12e2..7936ab8ecd 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { failed = true; - Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); + Logger.Log($"You are not able to submit a score: {e.Message}", LoggingTarget.Information, LogLevel.Important); Schedule(() => { From 96d20bf6072aa181b6fb5662ddc32024be2d61bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:24:51 +0900 Subject: [PATCH 075/102] Reduce height of ModeTypeInfo to match adjacent text sections --- osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs index 03b27b605c..2026106c42 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { public class ModeTypeInfo : OnlinePlayComposite { - private const float height = 30; + private const float height = 28; private const float transition_duration = 100; private Container drawableRuleset; From 9b209d67dc83568751994c1af8a0713f19339979 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:44:13 +0900 Subject: [PATCH 076/102] Match size of participants text with host display --- osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs index 0d5ce65d5a..bc4506b78e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components summary = new OsuSpriteText { Text = "0 participants", - Font = OsuFont.GetFont(size: 14) } }, }, From fc3adaf6123e9f697b85d7c8fa566e111f1eb05b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:44:35 +0900 Subject: [PATCH 077/102] Show maximum attempt count in room display (when not unlimited) --- .../Components/RoomLocalUserInfo.cs | 50 +++++++++++++++++++ .../OnlinePlay/Lounge/Components/RoomInfo.cs | 39 ++++++--------- 2 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs new file mode 100644 index 0000000000..f52e59b0c8 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.OnlinePlay.Components +{ + public class RoomLocalUserInfo : OnlinePlayComposite + { + private OsuSpriteText attemptDisplay; + + public RoomLocalUserInfo() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + attemptDisplay = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14) + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + MaxAttempts.BindValueChanged(attempts => + { + attemptDisplay.Text = attempts.NewValue == null + ? string.Empty + : $"Maximum attempts: {attempts.NewValue:N0}"; + }, true); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs index 0a17702f2a..a0a7f2dc28 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs @@ -20,41 +20,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { AutoSizeAxes = Axes.Y; + RoomLocalUserInfo localUserInfo; RoomStatusInfo statusInfo; ModeTypeInfo typeInfo; ParticipantInfo participantInfo; InternalChild = new FillFlowContainer { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, + Spacing = new Vector2(0, 10), AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 4), Children = new Drawable[] { + roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + participantInfo = new ParticipantInfo(), new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - statusInfo = new RoomStatusInfo(), - } - }, + statusInfo = new RoomStatusInfo(), typeInfo = new ModeTypeInfo { Anchor = Anchor.BottomRight, @@ -62,20 +55,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } }, - participantInfo = new ParticipantInfo(), + localUserInfo = new RoomLocalUserInfo(), } }; - statusElements.AddRange(new Drawable[] { statusInfo, typeInfo, participantInfo }); + statusElements.AddRange(new Drawable[] + { + statusInfo, typeInfo, participantInfo, localUserInfo + }); } protected override void LoadComplete() { base.LoadComplete(); - if (RoomID.Value == null) statusElements.ForEach(e => e.FadeOut()); - RoomID.BindValueChanged(id => { if (id.NewValue == null) @@ -83,7 +77,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components else statusElements.ForEach(e => e.FadeIn(100)); }, true); - RoomName.BindValueChanged(name => { roomName.Text = name.NewValue ?? "No room selected"; From a2e3b1c0e454e81c6836b44ca6f29426040b6ac1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:56:57 +0900 Subject: [PATCH 078/102] Move Mods reset code to OnlinePlaySongSelect --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 9 --------- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 11 ++++++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index f7f0402555..84e8849726 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,8 +1,6 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; @@ -27,13 +25,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AddInternal(loadingLayer = new LoadingLayer(true)); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - } - protected override void SelectItem(PlaylistItem item) { // If the client is already in a room, update via the client. diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index c58632c500..1c345b883f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -58,8 +58,17 @@ namespace osu.Game.Screens.OnlinePlay initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FooterPanels.Add(freeModSelectOverlay); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. + // Similarly, freeMods is currently empty but should only contain the allowed mods. + Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); Ruleset.BindValueChanged(onRulesetChanged); } From 2dece12a7c6041fb8ff9e5a4896c77b78865de3a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 19:57:42 +0900 Subject: [PATCH 079/102] Disable/disallow freemods on incompatible/selected mods --- osu.Game/Overlays/Mods/ModSection.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 993f4ef9d7..728c726b82 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -130,13 +130,13 @@ namespace osu.Game.Overlays.Mods /// Updates all buttons with the given list of selected mods. /// /// The new list of selected mods to select. - public void UpdateSelectedMods(IReadOnlyList newSelectedMods) + public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) { foreach (var button in buttons) - updateButtonMods(button, newSelectedMods); + updateButtonSelection(button, newSelectedMods); } - private void updateButtonMods(ModButton button, IReadOnlyList newSelectedMods) + private void updateButtonSelection(ModButton button, IReadOnlyList newSelectedMods) { foreach (var mod in newSelectedMods) { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b20c2d9d19..56d6008b00 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -377,7 +377,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); availableMods.BindValueChanged(_ => updateAvailableMods(), true); - SelectedMods.BindValueChanged(selectedModsChanged, true); + SelectedMods.BindValueChanged(_ => updateSelectedButtons(), true); } protected override void PopOut() @@ -445,6 +445,8 @@ namespace osu.Game.Overlays.Mods section.Mods = modEnumeration.Select(validModOrNull).Where(m => m != null); } + + updateSelectedButtons(); } /// @@ -465,10 +467,13 @@ namespace osu.Game.Overlays.Mods return validSubset.Length == 0 ? null : new MultiMod(validSubset); } - private void selectedModsChanged(ValueChangedEvent> mods) + private void updateSelectedButtons() { + // Enumeration below may update the bindable list. + var selectedMods = SelectedMods.Value.ToList(); + foreach (var section in ModSectionsContainer.Children) - section.UpdateSelectedMods(mods.NewValue); + section.UpdateSelectedButtons(selectedMods); updateMods(); } From aeb3ed8bb3bb12253eb2b4cf2092634e3c4c62ac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:46:22 +0900 Subject: [PATCH 080/102] Renamespace footer button --- osu.Game/Screens/OnlinePlay/{Match => }/FooterButtonFreeMods.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename osu.Game/Screens/OnlinePlay/{Match => }/FooterButtonFreeMods.cs (97%) diff --git a/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs similarity index 97% rename from osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs rename to osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index ca2db877c3..a3cc383b67 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; using osuTK; -namespace osu.Game.Screens.OnlinePlay.Match +namespace osu.Game.Screens.OnlinePlay { public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 1c345b883f..0baa663578 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -15,7 +15,6 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.Select; using osu.Game.Utils; From 8e96ffd1e6ea4d848766c72e703503de6b23cffd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Feb 2021 16:54:40 +0300 Subject: [PATCH 081/102] Fix "wait for import" until step potentially finishing early If not obvious, the issue with previous code is that it was checking for `IsAvailableLocally`, while the import is happening on a different thread, so that method could return `true` before the importing has finished and `ItemUpdated` event is called. --- .../TestSceneMultiplayerBeatmapAvailabilityTracker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index 3c3793670a..646c4139af 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Online addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddUntilStep("wait for import", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } @@ -127,8 +127,6 @@ namespace osu.Game.Tests.Online private void addAvailabilityCheckStep(string description, Func expected) { - // In DownloadTrackingComposite, state changes are scheduled one frame later, wait one step. - AddWaitStep("wait for potential change", 1); AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke())); } @@ -157,6 +155,8 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); + public Task CurrentImportTask { get; private set; } + protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => new TestDownloadRequest(set); @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Online public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) { await AllowImport.Task; - return await base.Import(item, archive, cancellationToken); + return await (CurrentImportTask = base.Import(item, archive, cancellationToken)); } } From 50d57a39317c32d59e30006679a030bedfbdb10b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Feb 2021 17:07:16 +0300 Subject: [PATCH 082/102] Move tracker loading into BDL --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index f2cb1120cb..cdf889e4f1 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,11 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected readonly MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker; + protected MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } protected RoomSubScreen() { - InternalChild = BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker + BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = SelectedItem }, }; @@ -54,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [BackgroundDependencyLoader] private void load(AudioManager audio) { + AddInternal(BeatmapAvailablilityTracker); + sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); } From 62d0036c819c0516005f9b5b3938d359e0cac987 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Feb 2021 17:45:07 +0300 Subject: [PATCH 083/102] Fix using private constructor on MessagePack object --- osu.Game/Online/Rooms/BeatmapAvailability.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index 2adeb9b959..a83327aad5 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.Rooms public readonly float? DownloadProgress; [JsonConstructor] - private BeatmapAvailability(DownloadState state, float? downloadProgress = null) + public BeatmapAvailability(DownloadState state, float? downloadProgress = null) { State = state; DownloadProgress = downloadProgress; From 21d5f842fccb799ad83ae47ca868b4b3a9827cbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 14:52:36 +0900 Subject: [PATCH 084/102] Re-layout to reduce movement --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 95 +++++++++++-------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 86982e2794..4664ac6bfe 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -97,18 +97,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, new Drawable[] { - new GridContainer + new Container { RelativeSizeAxes = Axes.Both, - Content = new[] + Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - new Container + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + // Main left column + new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = new[] @@ -126,45 +127,57 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, } } - } - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 5 }, - Spacing = new Vector2(0, 10), - Children = new[] + }, + // Main right column + new FillFlowContainer { - new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new FillFlowContainer { - new OverlinedHeader("Beatmap"), - new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } - } - }, - userModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OverlinedHeader("Beatmap"), + new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } + } + }, + userModsSection = new FillFlowContainer { - new OverlinedHeader("Extra mods"), - new ModDisplay + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Children = new Drawable[] { - DisplayUnrankedText = false, - Current = UserMods - }, - new PurpleTriangleButton - { - RelativeSizeAxes = Axes.X, - Text = "Select", - Action = () => userModsSelectOverlay.Show() + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new PurpleTriangleButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = () => userModsSelectOverlay.Show() + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } } } } From 8bb13915152ef6c5ac4a96f684bdbece349409c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 14:53:13 +0900 Subject: [PATCH 085/102] Fix inspection --- .../Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 768dc6512c..e2c98c0aad 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.AddUser(new User { Id = 0, - Username = $"User 0", + Username = "User 0", CurrentModeRank = RNG.Next(1, 100000), CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); From 3fe190cfbe7c5fb3c6d2863ea53a33b370ea03cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 22:00:16 +0900 Subject: [PATCH 086/102] Show original error message on web exceptions (or is no message is returned) --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 7936ab8ecd..dc98eb8687 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -65,7 +66,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { failed = true; - Logger.Log($"You are not able to submit a score: {e.Message}", LoggingTarget.Information, LogLevel.Important); + if (e is WebException || string.IsNullOrEmpty(e.Message)) + Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); + else + Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); Schedule(() => { From 9d7164816cf073be96b07353d6d996c50086ad71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 22:02:40 +0900 Subject: [PATCH 087/102] Add reverse binding for max attempts (currently unused but good for safety) --- .../OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index bf85ecf13d..56d3143f59 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -280,6 +280,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); + MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); playlist.Items.BindTo(Playlist); From db3f9e7cbee3ff3c719d80fc2943420b82a828a2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Feb 2021 02:20:18 +0300 Subject: [PATCH 088/102] Apply documentation suggestion --- osu.Game/Online/DownloadTrackingComposite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 188cb9be7a..69c6ebd07c 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -67,10 +67,10 @@ namespace osu.Game.Online } /// - /// Verifies that the given databased model is in a correct state to be considered available. + /// Checks that a database model matches the one expected to be downloaded. /// /// - /// In the case of multiplayer/playlists, this has to verify that the databased beatmap set with the selected beatmap matches what's online. + /// In the case of multiplayer/playlists, this has to check that the databased beatmap set with the selected beatmap matches what's online. /// /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; From d62bbbb7627673b4dc03729639a54ad9b864079e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Feb 2021 00:38:56 +0300 Subject: [PATCH 089/102] Enhance documentation Co-authored-by: Dan Balasescu --- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 69c6ebd07c..52042c266b 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online /// Checks that a database model matches the one expected to be downloaded. /// /// - /// In the case of multiplayer/playlists, this has to check that the databased beatmap set with the selected beatmap matches what's online. + /// For online play, this could be used to check that the databased model matches the online beatmap. /// /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; From a2fdba3e5173441147761ace92894f54cc6f309f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:24:38 +0900 Subject: [PATCH 090/102] Rename to OnlinePlayBeatmapAvailabilityTracker --- ...s => TestSceneOnlinePlayBeatmapAvailabilityTracker.cs} | 8 ++++---- ...racker.cs => OnlinePlayBeatmapAvailablilityTracker.cs} | 4 ++-- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Tests/Online/{TestSceneMultiplayerBeatmapAvailabilityTracker.cs => TestSceneOnlinePlayBeatmapAvailabilityTracker.cs} (96%) rename osu.Game/Online/Rooms/{MultiplayerBeatmapAvailablilityTracker.cs => OnlinePlayBeatmapAvailablilityTracker.cs} (95%) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs similarity index 96% rename from osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs rename to osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 646c4139af..d3475de157 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -28,7 +28,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public class TestSceneMultiplayerBeatmapAvailabilityTracker : OsuTestScene + public class TestSceneOnlinePlayBeatmapAvailabilityTracker : OsuTestScene { private RulesetStore rulesets; private TestBeatmapManager beatmaps; @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Online private BeatmapSetInfo testBeatmapSet; private readonly Bindable selectedItem = new Bindable(); - private MultiplayerBeatmapAvailablilityTracker availablilityTracker; + private OnlinePlayBeatmapAvailablilityTracker availablilityTracker; [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Online Ruleset = { Value = testBeatmapInfo.Ruleset }, }; - Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker + Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem, } }; @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("recreate tracker", () => Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker + AddStep("recreate tracker", () => Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }); diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs similarity index 95% rename from osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs rename to osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index 578c4db2f8..ad4b3c5151 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public class MultiplayerBeatmapAvailablilityTracker : DownloadTrackingComposite + public class OnlinePlayBeatmapAvailablilityTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); @@ -26,7 +26,7 @@ namespace osu.Game.Online.Rooms private readonly Bindable availability = new Bindable(); - public MultiplayerBeatmapAvailablilityTracker() + public OnlinePlayBeatmapAvailablilityTracker() { State.BindValueChanged(_ => updateAvailability()); Progress.BindValueChanged(_ => updateAvailability(), true); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index cdf889e4f1..b1b3dde26e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,11 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } + protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } protected RoomSubScreen() { - BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker + BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = SelectedItem }, }; From 85e63afcb48288f7838409c5b14380507ea3443d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:36:25 +0900 Subject: [PATCH 091/102] Rename Mods -> RequiredMods --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 6 +++--- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index d0e19d9f37..4fb9d724b5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.Multiplayer [NotNull] [Key(4)] - public IEnumerable Mods { get; set; } = Enumerable.Empty(); + public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); [NotNull] [Key(5)] @@ -39,14 +39,14 @@ namespace osu.Game.Online.Multiplayer public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum - && Mods.SequenceEqual(other.Mods) + && RequiredMods.SequenceEqual(other.RequiredMods) && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID && Name.Equals(other.Name, StringComparison.Ordinal); public override string ToString() => $"Name:{Name}" + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" - + $" Mods:{string.Join(',', Mods)}" + + $" RequiredMods:{string.Join(',', RequiredMods)}" + $" AllowedMods:{string.Join(',', AllowedMods)}" + $" Ruleset:{RulesetID}"; } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 69df2a69cc..aedbe37d56 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -192,7 +192,7 @@ namespace osu.Game.Online.Multiplayer BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, - Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods, + RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods }); } @@ -531,7 +531,7 @@ namespace osu.Game.Online.Multiplayer beatmap.MD5Hash = settings.BeatmapChecksum; var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance(); - var mods = settings.Mods.Select(m => m.ToMod(ruleset)); + var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); PlaylistItem playlistItem = new PlaylistItem From 2e85ce5b824eeafe85f3b9b058b89601690314ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:40:16 +0900 Subject: [PATCH 092/102] Rename UserMods -> Mods for MultiplayerRoomUser --- osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs | 2 +- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 +- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 3271133bcd..c654127b94 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -36,7 +36,7 @@ namespace osu.Game.Online.Multiplayer /// [Key(3)] [NotNull] - public IEnumerable UserMods { get; set; } = Enumerable.Empty(); + public IEnumerable Mods { get; set; } = Enumerable.Empty(); [IgnoreMember] public User? User { get; set; } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index aedbe37d56..f7c9193dfe 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.Multiplayer if (user == null) return; - user.UserMods = mods; + user.Mods = mods; RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8036e5f702..2983d1268d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); userStateDisplay.Status = User.State; - userModsDisplay.Current.Value = User.UserMods.Select(m => m.ToMod(ruleset)).ToList(); + userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); From 8004c19a8037bb4faece8646d604b1cb3c13ba97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:40:42 +0900 Subject: [PATCH 093/102] Remove ModValidationTest --- osu.Game.Tests/Mods/ModValidationTest.cs | 64 ------------------------ 1 file changed, 64 deletions(-) delete mode 100644 osu.Game.Tests/Mods/ModValidationTest.cs diff --git a/osu.Game.Tests/Mods/ModValidationTest.cs b/osu.Game.Tests/Mods/ModValidationTest.cs deleted file mode 100644 index 991adc221e..0000000000 --- a/osu.Game.Tests/Mods/ModValidationTest.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using Moq; -using NUnit.Framework; -using osu.Game.Rulesets.Mods; -using osu.Game.Utils; - -namespace osu.Game.Tests.Mods -{ - [TestFixture] - public class ModValidationTest - { - [Test] - public void TestModIsCompatibleByItself() - { - var mod = new Mock(); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); - } - - [Test] - public void TestIncompatibleThroughTopLevel() - { - var mod1 = new Mock(); - var mod2 = new Mock(); - - mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); - - // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); - } - - [Test] - public void TestIncompatibleThroughMultiMod() - { - var mod1 = new Mock(); - - // The nested mod. - var mod2 = new Mock(); - mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); - - var multiMod = new MultiMod(new MultiMod(mod2.Object)); - - // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); - } - - [Test] - public void TestAllowedThroughMostDerivedType() - { - var mod = new Mock(); - Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); - } - - [Test] - public void TestNotAllowedThroughBaseType() - { - var mod = new Mock(); - Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); - } - } -} From df2da5950f2f7b1fd233f75900e3979a362204e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 13:05:11 +0900 Subject: [PATCH 094/102] Add back vertical spacer --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4664ac6bfe..0466c8209f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -104,6 +104,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Child = new GridContainer { RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), + new Dimension(), + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), + }, Content = new[] { new Drawable[] @@ -128,6 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } }, + // Spacer + null, // Main right column new FillFlowContainer { From fc37d8b7df27a398f0343fcefbb6f15ce3b9450f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 14:25:19 +0900 Subject: [PATCH 095/102] Refactor content redirection logic to be easier to parse --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 19b68ee6d6..722b167387 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -45,18 +45,21 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } - private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; + private readonly Container content; protected RoomSubScreen() { - BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + InternalChildren = new Drawable[] { - SelectedItem = { BindTarget = SelectedItem } + BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + { + SelectedItem = { BindTarget = SelectedItem } + }, + content = new Container { RelativeSizeAxes = Axes.Both }, }; - - base.AddInternal(content); } + // Forward all internal management to content to ensure locally added components are not removed unintentionally. // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) protected override void AddInternal(Drawable drawable) => content.Add(drawable); protected override bool RemoveInternal(Drawable drawable) => content.Remove(drawable); @@ -65,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [BackgroundDependencyLoader] private void load(AudioManager audio) { - base.AddInternal(BeatmapAvailablilityTracker); - sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); } From de8724b1f6b7d714260589dab5f5619afea0ca6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 14:39:25 +0900 Subject: [PATCH 096/102] Use AddRangeInternal for simplicity, but disallow ClearInternal for safety --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 21 +++++-------------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- .../Playlists/PlaylistsRoomSubScreen.cs | 4 ++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 722b167387..8be0e89665 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -7,8 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -45,25 +43,16 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } - private readonly Container content; - protected RoomSubScreen() { - InternalChildren = new Drawable[] + AddInternal(BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { - BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker - { - SelectedItem = { BindTarget = SelectedItem } - }, - content = new Container { RelativeSizeAxes = Axes.Both }, - }; + SelectedItem = { BindTarget = SelectedItem } + }); } - // Forward all internal management to content to ensure locally added components are not removed unintentionally. - // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) - protected override void AddInternal(Drawable drawable) => content.Add(drawable); - protected override bool RemoveInternal(Drawable drawable) => content.Remove(drawable); - protected override void ClearInternal(bool disposeChildren = true) => content.Clear(disposeChildren); + protected override void ClearInternal(bool disposeChildren = true) => + throw new InvalidOperationException($"{nameof(RoomSubScreen)}'s children should not be cleared as it will remove required components"); [BackgroundDependencyLoader] private void load(AudioManager audio) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 8cc2e9061b..e1524067da 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { mainContent = new GridContainer { @@ -177,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); if (client.Room == null) { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 745e5a58bf..0b8026044b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { mainContent = new GridContainer { @@ -190,7 +190,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); if (roomId.Value == null) { From 3e750feaa49cd9b0f7dea85965f657181b1c2eeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:42:35 +0900 Subject: [PATCH 097/102] Subclass LocalPlayerModSelectOverlay to correctly deselect incompatible mods on free mod selection --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 2 +- .../{SoloModSelectOverlay.cs => LocalPlayerModSelectOverlay.cs} | 2 +- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Overlays/Mods/{SoloModSelectOverlay.cs => LocalPlayerModSelectOverlay.cs} (88%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 44605f4994..37ebc72984 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -333,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class TestModSelectOverlay : SoloModSelectOverlay + private class TestModSelectOverlay : LocalPlayerModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 3c889bdec4..89f9b7381b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded); } - private class TestModSelectOverlay : SoloModSelectOverlay + private class TestModSelectOverlay : LocalPlayerModSelectOverlay { public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs similarity index 88% rename from osu.Game/Overlays/Mods/SoloModSelectOverlay.cs rename to osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs index d039ad1f98..78cd9bdae5 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class SoloModSelectOverlay : ModSelectOverlay + public class LocalPlayerModSelectOverlay : ModSelectOverlay { protected override void OnModSelected(Mod mod) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 061e3b4d3f..f030879625 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -373,7 +373,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.LoadRequested -= onLoadRequested; } - private class UserModSelectOverlay : ModSelectOverlay + private class UserModSelectOverlay : LocalPlayerModSelectOverlay { public UserModSelectOverlay() { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 0baa663578..b201c62b7f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay return base.OnExiting(next); } - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + protected override ModSelectOverlay CreateModSelectOverlay() => new LocalPlayerModSelectOverlay { IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ed6e0a1028..b20effc67d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -311,7 +311,7 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new LocalPlayerModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 630c5bb74700ae3599013eddf70e2a1cda4a3642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:46:21 +0900 Subject: [PATCH 098/102] Avoid potential crashes when lease is held on SelectedMods --- osu.Game/OsuGame.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a00cd5e6a0..1a1f7bd233 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -469,6 +469,10 @@ namespace osu.Game { updateModDefaults(); + // a lease may be taken on the mods bindable, at which point we can't really ensure valid mods. + if (SelectedMods.Disabled) + return; + if (!ModUtils.CheckValidForGameplay(mods.NewValue, out var invalid)) { // ensure we always have a valid set of mods. From 95ad7ea8f7667a5e3faacbcbe696ef09b5b38354 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 18:44:26 +0900 Subject: [PATCH 099/102] Fix mods on participant panels flashing when changed --- .../Multiplayer/Participants/ParticipantPanel.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 2983d1268d..0ee1b6d684 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -162,15 +162,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - userStateDisplay.Status = User.State; - userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); else crown.FadeOut(fade_time); + + // 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(() => + { + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); + userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); + }); } public MenuItem[] ContextMenuItems From 0679901e4d3048552b9e89ace1896958189c6d4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 22:53:40 +0900 Subject: [PATCH 100/102] Update error handling --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index dc98eb8687..33200ca076 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { failed = true; - if (e is WebException || string.IsNullOrEmpty(e.Message)) - Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); + if (string.IsNullOrEmpty(e.Message)) + Logger.Error(e, "Failed to retrieve a score submission token."); else Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); From 7f82a06a61284b89a99a5b95cbae09cf428fa159 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 23:08:31 +0900 Subject: [PATCH 101/102] Remove no longer used using directive --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 33200ca076..38eae2346a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; From d74a1437beddf07f471254588a4609130a361cd2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 7 Feb 2021 15:14:08 -0800 Subject: [PATCH 102/102] Fix player loader metadata not being centred --- .../Screens/Play/BeatmapMetadataDisplay.cs | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index b53141e8fb..eff06e26ee 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -23,32 +23,6 @@ namespace osu.Game.Screens.Play /// public class BeatmapMetadataDisplay : Container { - private class MetadataLine : Container - { - public MetadataLine(string left, string right) - { - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = 5 }, - Colour = OsuColour.Gray(0.8f), - Text = left, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopLeft, - Margin = new MarginPadding { Left = 5 }, - Text = string.IsNullOrEmpty(right) ? @"-" : right, - } - }; - } - } - private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; private readonly Drawable facade; @@ -144,15 +118,34 @@ namespace osu.Game.Screens.Play Bottom = 40 }, }, - new MetadataLine("Source", metadata.Source) + new GridContainer { - Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - }, - new MetadataLine("Mapper", metadata.AuthorString) - { Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new MetadataLineLabel("Source"), + new MetadataLineInfo(metadata.Source) + }, + new Drawable[] + { + new MetadataLineLabel("Mapper"), + new MetadataLineInfo(metadata.AuthorString) + } + } }, new ModDisplay { @@ -168,5 +161,26 @@ namespace osu.Game.Screens.Play Loading = true; } + + private class MetadataLineLabel : OsuSpriteText + { + public MetadataLineLabel(string text) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + Margin = new MarginPadding { Right = 5 }; + Colour = OsuColour.Gray(0.8f); + Text = text; + } + } + + private class MetadataLineInfo : OsuSpriteText + { + public MetadataLineInfo(string text) + { + Margin = new MarginPadding { Left = 5 }; + Text = string.IsNullOrEmpty(text) ? @"-" : text; + } + } } }