From 80ce78e05dc2e97947c21520fb9fc7264259a9ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 19 Oct 2023 13:59:07 +0300 Subject: [PATCH 01/40] Remove animation support from osu! hit circle overlay --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 3ec914596a..d8d86d1802 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -62,12 +63,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // otherwise fall back to the default prefix "hitcircle". string circleName = (priorityLookupPrefix != null && skin.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : @"hitcircle"; + Vector2 maxSize = OsuHitObject.OBJECT_DIMENSIONS * 2; + // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. // the conditional above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png. InternalChildren = new[] { - CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2) }) + CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -76,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d, maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2)) + Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 2a70c331b9f9730c6e775fe3962cf1bf1c1e5fd1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 19 Oct 2023 14:01:13 +0300 Subject: [PATCH 02/40] Remove animation support from osu! slider reverse arrow --- .../Skinning/Legacy/LegacyReverseArrow.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index a535fbdbc3..780084115d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -41,11 +42,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); - InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2) ?? Empty()).With(d => + InternalChild = arrow = new Sprite { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - }); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = skin?.GetTexture(lookupName)?.WithMaximumSize(maxSize: OsuHitObject.OBJECT_DIMENSIONS * 2), + }; textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin; From badb1310a38484955250757f78c3103be93d38c2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 19 Oct 2023 14:00:13 +0300 Subject: [PATCH 03/40] Adjust frame length of osu!mania judgement animation --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 446dfae0f6..73c521b2ed 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value ?? default_hit_result_skin_filenames[result]; - var animation = this.GetAnimation(filename, true, true); + var animation = this.GetAnimation(filename, true, true, frameLength: 1000 / 20d); return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); } From 517d0f65b646daa784423dfdf1aae528f8448aaa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 21 Oct 2023 00:22:29 +0300 Subject: [PATCH 04/40] Adjust frame length of osu!mania hold-note body animation --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index 66e67136df..ee274fc45e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy direction.BindTo(scrollingInfo.Direction); isHitting.BindTo(holdNote.IsHitting); - bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true).With(d => + bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30).With(d => { if (d == null) return; From 491f119988f37e6264ccb7ebe3dfe3bb9e9d4b36 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 18 Oct 2023 20:56:17 +0300 Subject: [PATCH 05/40] Add animation support for legacy osu!mania column lights --- .../special-skin/mania/stage-light-0.png | Bin 0 -> 5122 bytes .../special-skin/mania/stage-light-1.png | Bin 0 -> 5124 bytes .../special-skin/mania/stage-light-2.png | Bin 0 -> 5126 bytes .../special-skin/mania/stage-light-3.png | Bin 0 -> 5125 bytes .../Resources/special-skin/skin.ini | 2 ++ .../Skinning/Legacy/LegacyColumnBackground.cs | 22 +++++++++--------- .../Skinning/LegacyManiaSkinConfiguration.cs | 1 + .../LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 5 ++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 10 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-0.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-1.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-2.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-3.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-0.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-0.png new file mode 100644 index 0000000000000000000000000000000000000000..5044c2d15f038bd606159113b8d44da9b86557e4 GIT binary patch literal 5122 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 HJumaW3u|fS literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-3.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-3.png new file mode 100644 index 0000000000000000000000000000000000000000..8f9155b8f770ee62fa9cbe53ffc6f7fb191a561d GIT binary patch literal 5125 zcmeHLc~}$I77vT42nYy@NHIn%0!}iSWCDpqf=PskQDhZ`*2!c7QL>OsAmV}|%BCP7 zDvQFiDrze}A8r*~sQS5!yrQ&dm4~&Ix=@$atv-7bP~pA)q2E{k<9^>HlY7tkopXNo zH|NWp-0+zp_O|Y}6bi*&5-N%S-w3!~46y;9x3gT5C=|L zq$Q|?QK_I%jBWQz!~W&rV<+zX>YFfu^HdpkNpa52V8(HVSM;*z+H-Xw1xZ7u_+&Zq z1gr1f%1}I8A-c%)LYpRZ=B%IY8{hoUSaW1yp0aiF&?;tnX?9!_Md%gw_`gq-Lw+AKMv?A?VZY$~P&ktRxt>-`PhO4up zb5+-8jqkp7&hh9!9Bm&@@NznEHboFqL^asoYuiX`JCeP7=aq-n7SCO35`F!?yKwc% z?5@%cOLqMB^QyGDxrKQRgSbK03m*wyl%02YwXw9M%JcYnY4_-H$HW<>-0Y^KiTAed zSQFk^x9dzy>hN^?ut`GC_PGVCx5C)uQSvjp9#nH=h4x=ZjM@YLm!>D8S z3uZUpY+aS?VKu~QLrj3s(z1Hm*7ZK_bxz~fIS5>9>%7!d!S29zFYh8d&SA+M_sBH| zef@+U)HAq^vu~VTa$eMl>5`OXlem||qno$wj*}+jUz@}wE^+65RwzUFxy*nn%I59+ zrQ_Vyx{CTydspSRH@6h$yE3-7vxFvY~wSh6+@({c6GOZzEf`)Em0D zw>AoGZWb10+m9t$^64Ji{SwmeT%aF6AMIVxwjTbrrL0C;MO^bnd9A!uMH@&)-9(pdelma5 zvrmtledTm$e%Gooujya^^UBha8+&g|nLXyNe~bUVmm_Pdr#0TM__yEUJJ%#T4jvBr z)$XP$N0WI=_1gbQtedk(>2FQ1hsWs|Rq?}r%A`;%vXnw$xI`#?qk5qA%B4m8(31kU z)wg!!Mfhf>IvUP~Z=RHs7qGxJFMM2!?H=By_!onv?!_*nLK+XWw>QbIHlB`)9UL3% z_=iP`Atiz=ay}kr3&|R8AI^Sy?CG5clLb3BmMIKttjg6DyF~F9<*}!0W3#S=jBqP1 zBQ8oJoR8hU>uGW9)@kF0j327L2)PtzHzem&-9=c{Vc*duz24q1%L5a~O_<{C$?D2H zRTv)|xa@YkHm<$QiM6@hyTCDj6(^=`ldE*CF)v{E0^XfrqYI-i&#OG3D@D7{eeQO- z%kM&b4Z6m96WYA!#+jg>Urf;X{OG%)2)A%eT9**?Z0sKo z_LNpt{#yJ^O7-K@DO08`uT7|)t*`9H&nPP?jfw5kHP8JF^M5LNGB+&W)8gp$6Q>Pf zk-sm$@X7r4bL*tvmuBwITHpv8V14RkC&>ZOu9)6BR$aBrGs(5Pk;av`5KM;cBQYIFpZO=r_#NNiN5BD5*C)IgnF!HW=0>xBTn z__QRF)bbdNw6rvO8jG&cB{G;?E|&o#3&?TG8k>d;^nNR^!J&4Ne7cxX54S(xlq99SJ(we;h z+5Id@rQ#h~{cP;vl;a{N(Tj6Q;9zfKB_t1JwX# zF_TaMfY}2S!xQQVjMV6&G@4{S&6E?>o`Gbg zd5KCj>z0S%JwfzXDj_!q0)9P9coL>gB)|jStEe~g%D>nxf`!SM1i^whI0`5x6kLd_ z;1G~PA%_EG{&E(A{|~xeqaf2T9TA)eDh1U51(~a%PVC{*r++eONrZ_g7-2z77-9xQ zAutc+@=(+dX7ON{#`yR(0vQn)#tDF!CM~gnv>_RcA`nOlh?|9=1pDuO+Jfy*z;;%G zL%tbuE7WeOsJwx-GP0wpwd5-od5u@iAbrH6x$jrX>s3+7?qA02;B6gGw|vj>XlYKV z&ye>YXQ5NhW31nEY;%**hav0o4n?GnvgkKPHx1kJ*`VF=lTXr3>`BBkMYU7sXZ{I_ C94z$! literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 7c51036d69..3a9d465f8d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -4,6 +4,7 @@ Version: 2.5 [Mania] Keys: 4 ColumnLineWidth: 3,1,3,1,1 +LightFramePerSecond: 15 // some skins found in the wild had configuration keys where the @2x suffix was included in the values. // the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything // if @2x assets are present. @@ -15,5 +16,6 @@ Hit300: mania/hit300@2x Hit300g: mania/hit300g@2x StageLeft: mania/stage-left StageRight: mania/stage-right +StageLight: mania/stage-light NoteImage0L: LongNoteTailWang NoteImage1L: LongNoteTailWang diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs index ab996519a7..4c65707d70 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.UI.Scrolling; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private readonly IBindable direction = new Bindable(); private Container lightContainer = null!; - private Sprite light = null!; + private Drawable? light = null!; public LegacyColumnBackground() { @@ -39,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value ?? Color4.White; + int lightFramePerSecond = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightFramePerSecond)?.Value ?? 60; + InternalChildren = new[] { lightContainer = new Container @@ -46,16 +47,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = lightPosition }, - Child = light = new Sprite + Child = light = skin.GetAnimation(lightImage, true, true, frameLength: 1000d / lightFramePerSecond)?.With(l => { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour), - Texture = skin.GetTexture(lightImage), - RelativeSizeAxes = Axes.X, - Width = 1, - Alpha = 0 - } + l.Anchor = Anchor.BottomCentre; + l.Origin = Anchor.BottomCentre; + l.Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour); + l.RelativeSizeAxes = Axes.X; + l.Width = 1; + l.Alpha = 0; + }) ?? Empty(), } }; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index f1c99a315d..9acb29a793 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -40,6 +40,7 @@ namespace osu.Game.Skinning public float ScorePosition = 300 * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; public bool KeysUnderNotes; + public int LightFramePerSecond = 60; public LegacyNoteBodyStyle? NoteBodyStyle; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 784e57708e..cacca0de23 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -74,6 +74,7 @@ namespace osu.Game.Skinning Hit50, Hit0, KeysUnderNotes, - NoteBodyStyle + NoteBodyStyle, + LightFramePerSecond } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index e880e3c1ed..b472afb74f 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -123,6 +123,11 @@ namespace osu.Game.Skinning currentConfig.WidthForNoteHeightScale = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; + case "LightFramePerSecond": + int lightFramePerSecond = int.Parse(pair.Value, CultureInfo.InvariantCulture); + currentConfig.LightFramePerSecond = lightFramePerSecond > 0 ? lightFramePerSecond : 24; + break; + case string when pair.Key.StartsWith("Colour", StringComparison.Ordinal): HandleColours(currentConfig, line, true); break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 76190d0abe..dc683f1dae 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -273,6 +273,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.KeysUnderNotes: return SkinUtils.As(new Bindable(existing.KeysUnderNotes)); + + case LegacyManiaSkinConfigurationLookups.LightFramePerSecond: + return SkinUtils.As(new Bindable(existing.LightFramePerSecond)); } return null; From ab3b51e4a0a9042d1b20d08d64b2c47b2b6848bb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 19 Oct 2023 14:02:46 +0300 Subject: [PATCH 06/40] Refactor osu!taiko circle piece logic to read better --- .../Skinning/Legacy/LegacyCirclePiece.cs | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index 4dce3f1d4a..b512a46ce1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private static readonly Vector2 max_circle_sprite_size = new Vector2(160); private Drawable backgroundLayer = null!; - private Drawable? foregroundLayer; + private TextureAnimation? foregroundLayer; private Bindable currentCombo { get; } = new BindableInt(); @@ -48,40 +48,44 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableHitObject, IBeatSyncProvider? beatSyncProvider) { - Drawable? getDrawableFor(string lookup) + Drawable? getDrawableFor(string lookup, bool animatable) { const string normal_hit = "taikohit"; const string big_hit = "taikobig"; string prefix = ((drawableHitObject.HitObject as TaikoStrongableHitObject)?.IsStrong ?? false) ? big_hit : normal_hit; - return skin.GetAnimation($"{prefix}{lookup}", true, false, maxSize: max_circle_sprite_size) ?? + return skin.GetAnimation($"{prefix}{lookup}", animatable, false, maxSize: max_circle_sprite_size) ?? // fallback to regular size if "big" version doesn't exist. - skin.GetAnimation($"{normal_hit}{lookup}", true, false, maxSize: max_circle_sprite_size); + skin.GetAnimation($"{normal_hit}{lookup}", animatable, false, maxSize: max_circle_sprite_size); } // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. - AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle"))); + AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle", false)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + foregroundLayer = (TextureAnimation?)getDrawableFor("circleoverlay", true); - foregroundLayer = getDrawableFor("circleoverlay"); if (foregroundLayer != null) + { + foregroundLayer.Anchor = Anchor.Centre; + foregroundLayer.Origin = Anchor.Centre; + + // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). + // For now just stop at first frame for sanity. + foregroundLayer.Stop(); + AddInternal(foregroundLayer); + } drawableHitObject.StartTimeBindable.BindValueChanged(startTime => { timingPoint = beatSyncProvider?.ControlPoints?.TimingPointAt(startTime.NewValue) ?? TimingControlPoint.DEFAULT; }, true); - // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). - // For now just stop at first frame for sanity. - foreach (var c in InternalChildren) - { - (c as IFramedAnimation)?.Stop(); - - c.Anchor = Anchor.Centre; - c.Origin = Anchor.Centre; - } - if (gameplayState != null) currentCombo.BindTo(gameplayState.ScoreProcessor.Combo); } @@ -101,12 +105,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy foreach (var c in InternalChildren) c.Scale = new Vector2(DrawHeight / circle_piece_size.Y); - if (foregroundLayer is IFramedAnimation animatableForegroundLayer) - animateForegroundLayer(animatableForegroundLayer); + animateForegroundLayer(); } - private void animateForegroundLayer(IFramedAnimation animatableForegroundLayer) + private void animateForegroundLayer() { + if (foregroundLayer == null) + return; + int multiplier; if (currentCombo.Value >= 150) @@ -119,12 +125,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } else { - animatableForegroundLayer.GotoFrame(0); + foregroundLayer.GotoFrame(0); return; } animationFrame = Math.Abs(Time.Current - timingPoint.Time) % ((timingPoint.BeatLength * 2) / multiplier) >= timingPoint.BeatLength / multiplier ? 0 : 1; - animatableForegroundLayer.GotoFrame(animationFrame); + foregroundLayer.GotoFrame(animationFrame); } private Color4 accentColour; From 976ae62214a2314f2d2baa1249f6276374327e7e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 21 Oct 2023 01:10:06 +0300 Subject: [PATCH 07/40] Fix incorrect nullability specification --- .../Skinning/Legacy/LegacyColumnBackground.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs index 4c65707d70..914ed79234 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private readonly IBindable direction = new Bindable(); private Container lightContainer = null!; - private Drawable? light = null!; + private Drawable light = null!; public LegacyColumnBackground() { From bcbb77d3839028edf18535ac7976668c02cc7c6f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 24 Oct 2023 02:29:07 +0300 Subject: [PATCH 08/40] Fix incorrect assumption in taiko circle piece logic --- .../Skinning/Legacy/LegacyCirclePiece.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index b512a46ce1..dbc8718f02 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private static readonly Vector2 max_circle_sprite_size = new Vector2(160); private Drawable backgroundLayer = null!; - private TextureAnimation? foregroundLayer; + private Drawable? foregroundLayer; private Bindable currentCombo { get; } = new BindableInt(); @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Origin = Anchor.Centre }); - foregroundLayer = (TextureAnimation?)getDrawableFor("circleoverlay", true); + foregroundLayer = getDrawableFor("circleoverlay", true); if (foregroundLayer != null) { @@ -76,7 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat). // For now just stop at first frame for sanity. - foregroundLayer.Stop(); + if (foregroundLayer is IFramedAnimation animatedForegroundLayer) + animatedForegroundLayer.Stop(); AddInternal(foregroundLayer); } @@ -105,14 +106,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy foreach (var c in InternalChildren) c.Scale = new Vector2(DrawHeight / circle_piece_size.Y); - animateForegroundLayer(); + if (foregroundLayer is IFramedAnimation animatedForegroundLayer) + animateForegroundLayer(animatedForegroundLayer); } - private void animateForegroundLayer() + private void animateForegroundLayer(IFramedAnimation animation) { - if (foregroundLayer == null) - return; - int multiplier; if (currentCombo.Value >= 150) @@ -125,12 +124,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } else { - foregroundLayer.GotoFrame(0); + animation.GotoFrame(0); return; } animationFrame = Math.Abs(Time.Current - timingPoint.Time) % ((timingPoint.BeatLength * 2) / multiplier) >= timingPoint.BeatLength / multiplier ? 0 : 1; - foregroundLayer.GotoFrame(animationFrame); + animation.GotoFrame(animationFrame); } private Color4 accentColour; From e949b1f613a3015c4a8ae91a76e87e2229711570 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Oct 2023 10:36:57 +0300 Subject: [PATCH 09/40] Fix failing tests --- osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs index baaa24959f..5ad268a77b 100644 --- a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests Child = piece = new TestLegacyMainCirclePiece(priorityLookup), }; - var sprites = this.ChildrenOfType().Where(s => !string.IsNullOrEmpty(s.Texture.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray(); + var sprites = this.ChildrenOfType().Where(s => !string.IsNullOrEmpty(s.Texture?.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray(); Debug.Assert(sprites.Length <= 2); }); @@ -103,8 +103,8 @@ namespace osu.Game.Rulesets.Osu.Tests private partial class TestLegacyMainCirclePiece : LegacyMainCirclePiece { - public new Sprite? CircleSprite => base.CircleSprite.ChildrenOfType().DistinctBy(s => s.Texture.AssetName).SingleOrDefault(); - public new Sprite? OverlaySprite => base.OverlaySprite.ChildrenOfType().DistinctBy(s => s.Texture.AssetName).SingleOrDefault(); + public new Sprite? CircleSprite => base.CircleSprite.ChildrenOfType().Where(s => !string.IsNullOrEmpty(s.Texture?.AssetName)).DistinctBy(s => s.Texture.AssetName).SingleOrDefault(); + public new Sprite? OverlaySprite => base.OverlaySprite.ChildrenOfType().Where(s => !string.IsNullOrEmpty(s.Texture?.AssetName)).DistinctBy(s => s.Texture.AssetName).SingleOrDefault(); public TestLegacyMainCirclePiece(string? priorityLookupPrefix) : base(priorityLookupPrefix, false) From cfc0520481df18553b7252f1f63149ee89e4c9e9 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 28 Oct 2023 12:13:13 +0200 Subject: [PATCH 10/40] Add failing test --- .../SongSelect/TestScenePlaySongSelect.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6737ec9739..7313bde8fe 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -1111,6 +1112,23 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); } + [Test] + public void TestCutInFilterTextBox() + { + createSongSelect(); + + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("select all", () => InputManager.Keys(PlatformAction.SelectAll)); + AddStep("press ctrl-x", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.X); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType().First().Text, () => Is.Empty); + } + private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); From 366e41f11182c1220b2c8e33bc74172fa7543986 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 28 Oct 2023 12:23:23 +0200 Subject: [PATCH 11/40] Use local workaround instead of disabling clipboard entirely --- osu.Game/Screens/Select/FilterControl.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index b7dc18e46a..c15bd76ef8 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Collections; @@ -23,6 +24,7 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -254,9 +256,6 @@ namespace osu.Game.Screens.Select public OsuSpriteText FilterText { get; private set; } - // clipboard is disabled because one of the "cut" platform key bindings (shift-delete) conflicts with the beatmap deletion action. - protected override bool AllowClipboardExport => false; - public FilterControlTextBox() { Height += filter_text_size; @@ -277,6 +276,15 @@ namespace osu.Game.Screens.Select Colour = colours.Yellow }); } + + public override bool OnPressed(KeyBindingPressEvent e) + { + // the "cut" platform key binding (shift-delete) conflicts with the beatmap deletion action. + if (e.Action == PlatformAction.Cut && e.ShiftPressed && e.CurrentState.Keyboard.Keys.IsPressed(Key.Delete)) + return false; + + return base.OnPressed(e); + } } } } From c38c8e933ab0aba67d7a36ccff1413cde465dd8e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Oct 2023 16:52:33 +0300 Subject: [PATCH 12/40] Change tournament date text box parsing to use invariant culture info --- osu.Game.Tournament/Components/DateTextBox.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index ab643a5cb5..dd70d5856d 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -23,13 +24,13 @@ namespace osu.Game.Tournament.Components base.Current = new Bindable(string.Empty); current.BindValueChanged(dto => - base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); + base.Current.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", DateTimeFormatInfo.InvariantInfo), true); ((OsuTextBox)Control).OnCommit += (sender, _) => { try { - current.Value = DateTimeOffset.Parse(sender.Text); + current.Value = DateTimeOffset.Parse(sender.Text, DateTimeFormatInfo.InvariantInfo); } catch { From d877536dc0ec8d1f8f1889be73740a58ef29f869 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:03:38 +0300 Subject: [PATCH 13/40] Select all text content in `SearchTextBox` on focus --- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index a2e0ab6482..b554c2bbd8 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -18,6 +18,12 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = HomeStrings.SearchPlaceholder; } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + SelectAll(); + } + public override bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) From 31c6973bb646272b3ddcb2b3405d1802c8bf770c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:03:45 +0300 Subject: [PATCH 14/40] Add test coverage --- .../UserInterface/TestSceneSearchTextBox.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs new file mode 100644 index 0000000000..153525d24a --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs @@ -0,0 +1,38 @@ +// 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; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneSearchTextBox : OsuTestScene + { + private SearchTextBox textBox = null!; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = textBox = new SearchTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 400, + Scale = new Vector2(2f), + HoldFocus = true, + }; + }); + + [Test] + public void TestSelectionOnFocus() + { + AddStep("set text", () => textBox.Text = "some text"); + AddAssert("no text selected", () => textBox.SelectedText == string.Empty); + AddStep("hide text box", () => textBox.Hide()); + AddStep("show text box", () => textBox.Show()); + AddAssert("search text selected", () => textBox.SelectedText == textBox.Text); + } + } +} From ec9ae12bbd880f2f9a1c31adca3d09e516d0dbb4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:43:49 +0300 Subject: [PATCH 15/40] Update API response model to accept array of tournament banners --- .../Online/TestSceneUserProfileOverlay.cs | 27 +++++++++++++++---- .../Online/API/Requests/Responses/APIUser.cs | 5 ++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index b57b0b7312..a321a194a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -121,12 +121,29 @@ namespace osu.Game.Tests.Visual.Online Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() }, }, - TournamentBanner = new TournamentBanner + TournamentBanners = new[] { - Id = 13926, - TournamentId = 35, - ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg", - Image = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US@2x.jpg", + new TournamentBanner + { + Id = 15588, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN@2x.jpg" + }, + new TournamentBanner + { + Id = 15589, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH@2x.jpg" + }, + new TournamentBanner + { + Id = 15590, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL@2x.jpg" + } }, Badges = new[] { diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index d9208d0662..7c4093006d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -234,9 +234,8 @@ namespace osu.Game.Online.API.Requests.Responses set => Statistics.RankHistory = value; } - [JsonProperty(@"active_tournament_banner")] - [CanBeNull] - public TournamentBanner TournamentBanner; + [JsonProperty(@"active_tournament_banners")] + public TournamentBanner[] TournamentBanners; [JsonProperty("badges")] public Badge[] Badges; From 922ad80cfc9faf6fd63a0a5f01a266247924c4f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Oct 2023 01:44:21 +0300 Subject: [PATCH 16/40] Update user profile overlay to show more than one tournament banner --- .../Profile/Header/BannerHeaderContainer.cs | 15 ++++++++------- .../Header/Components/DrawableTournamentBanner.cs | 9 ++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs index 8e6648dc4b..7ed58200ec 100644 --- a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.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 System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +12,7 @@ using osu.Game.Overlays.Profile.Header.Components; namespace osu.Game.Overlays.Profile.Header { - public partial class BannerHeaderContainer : CompositeDrawable + public partial class BannerHeaderContainer : FillFlowContainer { public readonly Bindable User = new Bindable(); @@ -19,9 +20,9 @@ namespace osu.Game.Overlays.Profile.Header private void load() { Alpha = 0; - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; - FillAspectRatio = 1000 / 60f; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; } protected override void LoadComplete() @@ -40,13 +41,13 @@ namespace osu.Game.Overlays.Profile.Header ClearInternal(); - var banner = user?.TournamentBanner; + var banners = user?.TournamentBanners; - if (banner != null) + if (banners?.Length > 0) { Show(); - LoadComponentAsync(new DrawableTournamentBanner(banner), AddInternal, cancellationTokenSource.Token); + LoadComponentsAsync(banners.Select(b => new DrawableTournamentBanner(b)), AddRangeInternal, cancellationTokenSource.Token); } else { diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs index 26d333ff95..c099009ca4 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs @@ -15,12 +15,13 @@ namespace osu.Game.Overlays.Profile.Header.Components [LongRunningLoad] public partial class DrawableTournamentBanner : OsuClickableContainer { + private const float banner_aspect_ratio = 60 / 1000f; private readonly TournamentBanner banner; public DrawableTournamentBanner(TournamentBanner banner) { this.banner = banner; - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] @@ -41,6 +42,12 @@ namespace osu.Game.Overlays.Profile.Header.Components this.FadeInFromZero(200); } + protected override void Update() + { + base.Update(); + Height = DrawWidth * banner_aspect_ratio; + } + public override LocalisableString TooltipText => "view in browser"; } } From 204ebfade7ecaf5a426bfc21a6b89cfa6393fed8 Mon Sep 17 00:00:00 2001 From: Rowe Wilson Frederisk Holme Date: Sun, 29 Oct 2023 21:25:15 +0800 Subject: [PATCH 17/40] Small update to README of Templates --- Templates/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/README.md b/Templates/README.md index cf25a89273..28aaee3290 100644 --- a/Templates/README.md +++ b/Templates/README.md @@ -7,7 +7,7 @@ Templates for use when creating osu! dependent projects. Create a fully-testable ```bash # install (or update) templates package. # this only needs to be done once -dotnet new -i ppy.osu.Game.Templates +dotnet new install ppy.osu.Game.Templates # create an empty freeform ruleset dotnet new ruleset -n MyCoolRuleset From af10dbb76c4b2ab6217655f7b0eeee124acc3635 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 06:19:52 +0300 Subject: [PATCH 18/40] Add test case with many tournament banners --- .../Online/TestSceneUserProfileHeader.cs | 266 +++++++++++++++++- 1 file changed, 265 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 4f28baa849..c9e5a3315c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -5,8 +5,10 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; @@ -28,7 +30,14 @@ namespace osu.Game.Tests.Visual.Online [SetUpSteps] public void SetUpSteps() { - AddStep("create header", () => Child = header = new ProfileHeader()); + AddStep("create header", () => + { + Child = new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.Both, + Child = header = new ProfileHeader() + }; + }); } [Test] @@ -136,5 +145,260 @@ namespace osu.Game.Tests.Visual.Online PreviousUsernames = new[] { "tsrk.", "quoicoubeh", "apagnan", "epita" } }, new OsuRuleset().RulesetInfo)); } + + [Test] + public void TestManyTournamentBanners() + { + AddStep("Show user w/ many tournament banners", () => header.User.Value = new UserProfileData(new APIUser + { + Id = 728, + Username = "Certain Guy", + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + Statistics = new UserStatistics + { + IsRanked = false, + // web will sometimes return non-empty rank history even for unranked users. + RankHistory = new APIRankHistory + { + Mode = @"osu", + Data = Enumerable.Range(2345, 85).ToArray() + }, + }, + TournamentBanners = new[] + { + new TournamentBanner + { + Id = 15329, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK@2x.jpg" + }, + new TournamentBanner + { + Id = 15588, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CN@2x.jpg" + }, + new TournamentBanner + { + Id = 15589, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PH@2x.jpg" + }, + new TournamentBanner + { + Id = 15590, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CL@2x.jpg" + }, + new TournamentBanner + { + Id = 15591, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_JP.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_JP@2x.jpg" + }, + new TournamentBanner + { + Id = 15592, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RU.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RU@2x.jpg" + }, + new TournamentBanner + { + Id = 15593, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KR@2x.jpg" + }, + new TournamentBanner + { + Id = 15594, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NZ.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NZ@2x.jpg" + }, + new TournamentBanner + { + Id = 15595, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TH.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TH@2x.jpg" + }, + new TournamentBanner + { + Id = 15596, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TW.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_TW@2x.jpg" + }, + new TournamentBanner + { + Id = 15603, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ID.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ID@2x.jpg" + }, + new TournamentBanner + { + Id = 15604, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KZ.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_KZ@2x.jpg" + }, + new TournamentBanner + { + Id = 15605, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AR@2x.jpg" + }, + new TournamentBanner + { + Id = 15606, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_BR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_BR@2x.jpg" + }, + new TournamentBanner + { + Id = 15607, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL@2x.jpg" + }, + new TournamentBanner + { + Id = 15639, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_MX.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_MX@2x.jpg" + }, + new TournamentBanner + { + Id = 15640, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AU.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_AU@2x.jpg" + }, + new TournamentBanner + { + Id = 15641, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_IT.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_IT@2x.jpg" + }, + new TournamentBanner + { + Id = 15642, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_UA.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_UA@2x.jpg" + }, + new TournamentBanner + { + Id = 15643, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NL@2x.jpg" + }, + new TournamentBanner + { + Id = 15644, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FI.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FI@2x.jpg" + }, + new TournamentBanner + { + Id = 15645, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RO.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_RO@2x.jpg" + }, + new TournamentBanner + { + Id = 15646, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SG.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SG@2x.jpg" + }, + new TournamentBanner + { + Id = 15647, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_DE.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_DE@2x.jpg" + }, + new TournamentBanner + { + Id = 15648, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ES.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_ES@2x.jpg" + }, + new TournamentBanner + { + Id = 15649, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SE.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_SE@2x.jpg" + }, + new TournamentBanner + { + Id = 15650, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CA.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_CA@2x.jpg" + }, + new TournamentBanner + { + Id = 15651, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NO.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_NO@2x.jpg" + }, + new TournamentBanner + { + Id = 15652, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_GB.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_GB@2x.jpg" + }, + new TournamentBanner + { + Id = 15653, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_US.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_US@2x.jpg" + }, + new TournamentBanner + { + Id = 15654, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_PL@2x.jpg" + }, + new TournamentBanner + { + Id = 15655, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FR.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_FR@2x.jpg" + }, + new TournamentBanner + { + Id = 15686, + TournamentId = 41, + ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK.jpg", + Image = "https://assets.ppy.sh/tournament-banners/official/owc2023/profile/supporter_HK@2x.jpg" + } + } + }, new OsuRuleset().RulesetInfo)); + } } } From 984c30ded662bc6091c980dc6d6fcf1da880479e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 06:20:13 +0300 Subject: [PATCH 19/40] Load each tournament banner as soon as it is loaded --- .../Overlays/Profile/Header/BannerHeaderContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs index 7ed58200ec..424ab4a529 100644 --- a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs @@ -1,7 +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.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -47,7 +46,15 @@ namespace osu.Game.Overlays.Profile.Header { Show(); - LoadComponentsAsync(banners.Select(b => new DrawableTournamentBanner(b)), AddRangeInternal, cancellationTokenSource.Token); + for (int index = 0; index < banners.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableTournamentBanner(banners[index]), asyncBanner => + { + // load in stable order regardless of async load order. + Insert(displayIndex, asyncBanner); + }, cancellationTokenSource.Token); + } } else { From c7bc8e686543e4fe6e823d06b5d79be144d8ad6e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 06:41:01 +0300 Subject: [PATCH 20/40] Move behaviour to settings search text box only --- .../Visual/Settings/TestSceneSettingsPanel.cs | 11 +++++++++++ .../Graphics/UserInterface/SearchTextBox.cs | 6 ------ osu.Game/Overlays/SettingsPanel.cs | 2 +- osu.Game/Overlays/SettingsSearchTextBox.cs | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/SettingsSearchTextBox.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 24c2eee783..69e489b247 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -140,6 +140,17 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } + [Test] + public void TestSearchTextBoxSelectedOnShow() + { + SearchTextBox searchTextBox = null!; + + AddStep("set text", () => (searchTextBox = settings.SectionsContainer.ChildrenOfType().First()).Current.Value = "some text"); + AddAssert("no text selected", () => searchTextBox.SelectedText == string.Empty); + AddRepeatStep("toggle visibility", () => settings.ToggleVisibility(), 2); + AddAssert("search text selected", () => searchTextBox.SelectedText == searchTextBox.Current.Value); + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index b554c2bbd8..a2e0ab6482 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -18,12 +18,6 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = HomeStrings.SearchPlaceholder; } - protected override void OnFocus(FocusEvent e) - { - base.OnFocus(e); - SelectAll(); - } - public override bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 2517a58491..3bac6c400f 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -135,7 +135,7 @@ namespace osu.Game.Overlays }, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Child = searchTextBox = new SeekLimitedSearchTextBox + Child = searchTextBox = new SettingsSearchTextBox { RelativeSizeAxes = Axes.X, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/SettingsSearchTextBox.cs b/osu.Game/Overlays/SettingsSearchTextBox.cs new file mode 100644 index 0000000000..bafa6e26eb --- /dev/null +++ b/osu.Game/Overlays/SettingsSearchTextBox.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + public partial class SettingsSearchTextBox : SeekLimitedSearchTextBox + { + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + SelectAll(); + } + } +} From d90f29a5ff4a4618955b7ac7c4e743d39b4aa03a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:07:26 +0900 Subject: [PATCH 21/40] Improve log output surrounding score submission --- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5fa6508a31..a75546f835 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -188,7 +188,10 @@ namespace osu.Game.Screens.Play { // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). if (token == null) + { + Logger.Log("No token, skipping score submission"); return Task.CompletedTask; + } if (scoreSubmissionSource != null) return scoreSubmissionSource.Task; @@ -197,6 +200,8 @@ namespace osu.Game.Screens.Play if (!score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0)) return Task.CompletedTask; + Logger.Log($"Beginning score submission (token:{token.Value})..."); + scoreSubmissionSource = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, token.Value); @@ -206,11 +211,12 @@ namespace osu.Game.Screens.Play score.ScoreInfo.Position = s.Position; scoreSubmissionSource.SetResult(true); + Logger.Log($"Score submission completed! (token:{token.Value} id:{s.ID})"); }; request.Failure += e => { - Logger.Error(e, $"Failed to submit score ({e.Message})"); + Logger.Error(e, $"Failed to submit score (token:{token.Value}): {e.Message}"); scoreSubmissionSource.SetResult(false); }; From a8c3f598457ee9079ba97d2f381f75f7774890dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:07:35 +0900 Subject: [PATCH 22/40] Clean up type display for web requests in logs --- osu.Game/Online/API/APIRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index cd6e8df754..6b6b222043 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -7,6 +7,7 @@ using System; using System.Globalization; using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Game.Extensions; @@ -46,7 +47,7 @@ namespace osu.Game.Online.API if (WebRequest != null) { Response = ((OsuJsonWebRequest)WebRequest).ResponseObject; - Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); + Logger.Log($"{GetType().ReadableName()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); } } From a91b704d21df8adeca8068dd448d2ac9424010c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:10:10 +0900 Subject: [PATCH 23/40] Fix some new nullable inspections --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++------ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 ++ osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 ++ osu.Game/Screens/Play/SquareGraph.cs | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index c62659d67a..3cb9b96090 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -49,13 +49,9 @@ namespace osu.Game.Rulesets.Osu.Objects set { path.ControlPoints.Clear(); - path.ExpectedDistance.Value = null; + path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type))); - if (value != null) - { - path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type))); - path.ExpectedDistance.Value = value.ExpectedDistance.Value; - } + path.ExpectedDistance.Value = value.ExpectedDistance.Value; } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 158c3c102c..3ed7558bcb 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -5,6 +5,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Edit private SelectionState state; + [CanBeNull] public event Action StateChanged; public SelectionState State diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 1506b884b4..85ea881006 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -166,6 +166,8 @@ namespace osu.Game.Screens.Backgrounds public override void Add(Drawable drawable) { + ArgumentNullException.ThrowIfNull(drawable); + if (drawable is Background) throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index b53e86a41b..0c7b485755 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -190,6 +191,7 @@ namespace osu.Game.Screens.Play private const float padding = 2; public const float WIDTH = cube_size + padding; + [CanBeNull] public event Action StateChanged; private readonly List drawableRows = new List(); From 96dd7b3333014cb5e8e404d129b163de1f72f6e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:44:16 +0900 Subject: [PATCH 24/40] Update the last played date of a beatmap when importing a replay by the local user --- osu.Game.Tests/ImportTest.cs | 4 ++ osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 59 +++++++++++++++++++++ osu.Game/Online/API/DummyAPIAccess.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 3 ++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 3f0f8a4f14..27b8d3f21e 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Online.API; using osu.Game.Tests.Resources; namespace osu.Game.Tests @@ -46,12 +47,15 @@ namespace osu.Game.Tests public partial class TestOsuGameBase : OsuGameBase { public RealmAccess Realm => Dependencies.Get(); + public new IAPIProvider API => base.API; private readonly bool withBeatmap; public TestOsuGameBase(bool withBeatmap) { this.withBeatmap = withBeatmap; + + base.API = new DummyAPIAccess(); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 892ceea185..d1bacaaf69 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -67,6 +68,64 @@ namespace osu.Game.Tests.Scores.IO } } + [TestCase(false)] + [TestCase(true)] + public void TestLastPlayedUpdate(bool isLocalUser) + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + if (!isLocalUser) + osu.API.Logout(); + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var beatmapInfo = beatmap.Beatmaps.First(); + + DateTimeOffset replayDate = DateTimeOffset.Now; + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser + { + Username = "Test user", + Id = DummyAPIAccess.DUMMY_USER_ID, + }, + Date = replayDate, + OnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapInfo + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + + if (isLocalUser) + Assert.That(imported.BeatmapInfo!.LastPlayed, Is.EqualTo(replayDate)); + else + Assert.That(imported.BeatmapInfo!.LastPlayed, Is.Null); + } + finally + { + host.Exit(); + } + } + } + [Test] public void TestImportMods() { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 2764247f5c..d585124db6 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -112,7 +112,7 @@ namespace osu.Game.Online.API LocalUser.Value = new APIUser { Username = username, - Id = 1001, + Id = DUMMY_USER_ID, }; state.Value = APIState.Online; diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 7473d887c3..32a528f218 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -87,6 +87,9 @@ namespace osu.Game.Scoring if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName)!; + if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed)) + model.BeatmapInfo.LastPlayed = model.Date; + // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. ArgumentNullException.ThrowIfNull(model.BeatmapInfo); From c1c8e2968f1f620851c4e608043051f07b944d8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 15:46:09 +0900 Subject: [PATCH 25/40] Move operation to after user population --- osu.Game/Scoring/ScoreImporter.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 32a528f218..b216c0897e 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -87,9 +87,6 @@ namespace osu.Game.Scoring if (!model.Ruleset.IsManaged) model.Ruleset = realm.Find(model.Ruleset.ShortName)!; - if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed)) - model.BeatmapInfo.LastPlayed = model.Date; - // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. ArgumentNullException.ThrowIfNull(model.BeatmapInfo); @@ -185,6 +182,12 @@ namespace osu.Game.Scoring base.PostImport(model, realm, parameters); populateUserDetails(model); + + Debug.Assert(model.BeatmapInfo != null); + + // This needs to be run after user detail population to ensure we have a valid user id. + if (api.IsLoggedIn && api.LocalUser.Value.OnlineID == model.UserID && (model.BeatmapInfo.LastPlayed == null || model.Date > model.BeatmapInfo.LastPlayed)) + model.BeatmapInfo.LastPlayed = model.Date; } /// From 0dfb41b7966de7823982f3f18b2a5adfa38ac1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 09:28:37 +0100 Subject: [PATCH 26/40] Add test coverage for not updating `LastPlayed` due to newer plays --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index d1bacaaf69..dd724d268e 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -11,6 +11,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -126,6 +128,58 @@ namespace osu.Game.Tests.Scores.IO } } + [Test] + public void TestLastPlayedNotUpdatedDueToNewerPlays() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = LoadOsuIntoHost(host, true); + + var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); + var beatmapInfo = beatmap.Beatmaps.First(); + + var realmAccess = osu.Dependencies.Get(); + realmAccess.Write(r => r.Find(beatmapInfo.ID)!.LastPlayed = new DateTimeOffset(2023, 10, 30, 0, 0, 0, TimeSpan.Zero)); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new APIUser + { + Username = "Test user", + Id = DummyAPIAccess.DUMMY_USER_ID, + }, + Date = new DateTimeOffset(2023, 10, 27, 0, 0, 0, TimeSpan.Zero), + OnlineID = 12345, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapInfo + }; + + var imported = LoadScoreIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); + + Assert.That(imported.BeatmapInfo!.LastPlayed, Is.EqualTo(new DateTimeOffset(2023, 10, 30, 0, 0, 0, TimeSpan.Zero))); + } + finally + { + host.Exit(); + } + } + } + [Test] public void TestImportMods() { From 39abb8e4085d17bc8ff64a80f19e268a3d4a5b7f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Oct 2023 11:54:19 +0300 Subject: [PATCH 27/40] Only run "select all on focus" behaviour on desktop platforms --- osu.Game/Overlays/SettingsSearchTextBox.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsSearchTextBox.cs b/osu.Game/Overlays/SettingsSearchTextBox.cs index bafa6e26eb..84cff1b508 100644 --- a/osu.Game/Overlays/SettingsSearchTextBox.cs +++ b/osu.Game/Overlays/SettingsSearchTextBox.cs @@ -12,7 +12,11 @@ namespace osu.Game.Overlays protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - SelectAll(); + + // on mobile platforms, focus is not held by the search text box, and the select all feature + // will not make sense on it, and might annoy the user when they try to focus manually. + if (HoldFocus) + SelectAll(); } } } From 6be02966b9e79dfbb94ad372b932decc78ee65f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 18:06:09 +0900 Subject: [PATCH 28/40] Add test coverage of failing context menu display --- .../Editing/TestSceneTimelineSelection.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 50eeb9a54b..0051488029 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -44,6 +46,47 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestContextMenuWithObjectBehind() + { + TimelineHitObjectBlueprint blueprint; + + AddStep("add object", () => + { + EditorBeatmap.Add(new HitCircle { StartTime = 3000 }); + }); + + AddStep("enter slider placement", () => + { + InputManager.Key(Key.Number3); + InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre); + }); + + AddStep("start conflicting slider", () => + { + InputManager.Click(MouseButton.Left); + + blueprint = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(blueprint.ScreenSpaceDrawQuad.TopLeft - new Vector2(10, 0)); + }); + + AddStep("end conflicting slider", () => + { + InputManager.Click(MouseButton.Right); + }); + + AddStep("click object", () => + { + InputManager.Key(Key.Number1); + blueprint = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(blueprint); + InputManager.Click(MouseButton.Left); + }); + + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => this.ChildrenOfType().SingleOrDefault()?.State == MenuState.Open); + } + [Test] public void TestNudgeSelection() { From 57d88a0ac459398a14aafd8923238ce1bc012e7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 17:46:04 +0900 Subject: [PATCH 29/40] Fix right clicks on timeline objects potentially getting eaten by playfield area `SelectionHandler` is receiving input from anywhere out of necessity: https://github.com/ppy/osu/blob/19f892687a0607afbe4e0d010366dc2a66236073/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs#L119-L125 Also important is that `BlueprintContainer` will selectively not block right clicks to make sure they fall through to the `ContextMenuContainer`: https://github.com/ppy/osu/blob/19f892687a0607afbe4e0d010366dc2a66236073/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs#L122-L126 But because the whole editor is sharing a `ContextMenuContainer` and it's at a higher level than both components, we observe here the playfield's `SelectionHandler` intercepting the right click before it can reach the `ContextMenuContainer`. The fix here is similar to what we're already doing in `TimelineBlueprintContaienr`. --- .../Compose/Components/ComposeBlueprintContainer.cs | 5 ++++- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 13 +++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index c8cfac454a..ba570a9251 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -40,11 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public PlacementBlueprint CurrentPlacement { get; private set; } + [Resolved] + private EditorScreenWithTimeline editorScreen { get; set; } + /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. /// - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen.MainContent.ReceivePositionalInputAt(screenSpacePos); public ComposeBlueprintContainer(HitObjectComposer composer) : base(composer) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index ea2790b50a..e1ec1ad4ac 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -11,13 +11,14 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit { + [Cached] public abstract partial class EditorScreenWithTimeline : EditorScreen { public const float PADDING = 10; - private Container timelineContainer = null!; + public Container TimelineContent = null!; - private Container mainContent = null!; + public Container MainContent = null!; private LoadingSpinner spinner = null!; @@ -70,7 +71,7 @@ namespace osu.Game.Screens.Edit { new Drawable[] { - timelineContainer = new Container + TimelineContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit }, new Drawable[] { - mainContent = new Container + MainContent = new Container { Name = "Main content", RelativeSizeAxes = Axes.Both, @@ -116,10 +117,10 @@ namespace osu.Game.Screens.Edit { spinner.State.Value = Visibility.Hidden; - mainContent.Add(content); + MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(new TimelineArea(CreateTimelineContent()), timelineContainer.Add); + LoadComponentAsync(new TimelineArea(CreateTimelineContent()), TimelineContent.Add); }); } From 63e6eaf53880e326d43a75efec66134bb36bfe30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Oct 2023 17:55:21 +0900 Subject: [PATCH 30/40] Fix failing tests --- osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs | 2 +- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 0051488029..d8219ff36e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click away", () => { - InputManager.MoveMouseTo(Editor.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft + Vector2.One); + InputManager.MoveMouseTo(Editor.ChildrenOfType().First().ScreenSpaceDrawQuad.TopLeft + new Vector2(5)); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index ba570a9251..c7c7c4aa83 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -40,14 +40,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public PlacementBlueprint CurrentPlacement { get; private set; } - [Resolved] + [Resolved(canBeNull: true)] private EditorScreenWithTimeline editorScreen { get; set; } /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. /// - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen.MainContent.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => editorScreen?.MainContent.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); public ComposeBlueprintContainer(HitObjectComposer composer) : base(composer) From 0ed5f274f6e45ea22401a50e905e2a1d3406f170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:48:31 +0100 Subject: [PATCH 31/40] Enable NRT in `TestSceneSliderVelocityAdjust` --- .../Editor/TestSceneSliderVelocityAdjust.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index bb8c52bdfc..d92c6ebb60 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.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. -#nullable disable - using System.Diagnostics; using System.Linq; using NUnit.Framework; @@ -24,15 +22,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneSliderVelocityAdjust : OsuGameTestScene { - private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; + private Screens.Edit.Editor? editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; - private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault(); + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault()!; - private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault(); + private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault()!; - private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); + private Slider? slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); - private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(); + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault()!; private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); @@ -66,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("ensure one slider placed", () => slider != null); - AddStep("store velocity", () => velocity = slider.Velocity); + AddStep("store velocity", () => velocity = slider!.Velocity); if (adjustVelocity) { @@ -76,10 +74,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("velocity adjusted", () => { Debug.Assert(velocity != null); - return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity); + return Precision.AlmostEquals(velocity.Value * 2, slider!.Velocity); }); - AddStep("store velocity", () => velocity = slider.Velocity); + AddStep("store velocity", () => velocity = slider!.Velocity); } AddStep("save", () => InputManager.Keys(PlatformAction.Save)); @@ -88,8 +86,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); - AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); - AddAssert("slider has correct velocity", () => slider.Velocity == velocity); + AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime)); + AddAssert("slider has correct velocity", () => slider!.Velocity == velocity); } } } From e1ff0d12c66db4c2f16bc5d88fdb568dd5341bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:55:26 +0100 Subject: [PATCH 32/40] Update tests to NUnit-style assertions --- .../Editor/TestSceneSliderVelocityAdjust.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index d92c6ebb60..979801bc41 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -1,7 +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.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Input; @@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor double? velocity = null; AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse, () => Is.True); AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); @@ -58,11 +57,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); - AddAssert("slider placed", () => slider != null); + AddAssert("slider placed", () => slider, () => Is.Not.Null); AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); - AddAssert("ensure one slider placed", () => slider != null); + AddAssert("ensure one slider placed", () => slider, () => Is.Not.Null); AddStep("store velocity", () => velocity = slider!.Velocity); @@ -71,11 +70,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); AddStep("change velocity", () => velocityTextBox.Current.Value = 2); - AddAssert("velocity adjusted", () => - { - Debug.Assert(velocity != null); - return Precision.AlmostEquals(velocity.Value * 2, slider!.Velocity); - }); + AddAssert("velocity adjusted", () => slider!.Velocity, + () => Is.EqualTo(velocity!.Value * 2).Within(Precision.DOUBLE_EPSILON)); AddStep("store velocity", () => velocity = slider!.Velocity); } @@ -84,10 +80,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("exit", () => InputManager.Key(Key.Escape)); AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse, () => Is.True); AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime)); - AddAssert("slider has correct velocity", () => slider!.Velocity == velocity); + AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocity)); } } } From b3369dbb7b188d99f226894924a70339478c21b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:57:48 +0100 Subject: [PATCH 33/40] Add failing test for slider velocity --- .../Editor/TestSceneSliderVelocityAdjust.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index 979801bc41..175cbeca6e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -85,5 +85,50 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("seek to slider", () => editorClock.Seek(slider!.StartTime)); AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocity)); } + + [Test] + public void TestVelocityUndo() + { + double? velocityBefore = null; + double? durationBefore = null; + + AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); + + AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + AddStep("start placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("end placement", () => InputManager.Click(MouseButton.Right)); + + AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); + + AddAssert("slider placed", () => slider, () => Is.Not.Null); + AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("store velocity", () => + { + velocityBefore = slider!.Velocity; + durationBefore = slider.Duration; + }); + + AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); + AddStep("change velocity", () => velocityTextBox.Current.Value = 2); + + AddAssert("velocity adjusted", () => slider!.Velocity, () => Is.EqualTo(velocityBefore!.Value * 2).Within(Precision.DOUBLE_EPSILON)); + + AddStep("undo", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Z); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocityBefore)); + AddAssert("slider has correct duration", () => slider!.Duration, () => Is.EqualTo(durationBefore)); + } } } From de89b7e53c305c0bf048b3ee42c028775d2da2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 10:59:02 +0100 Subject: [PATCH 34/40] Fix slider velocity changes not being undone correctly Closes https://github.com/ppy/osu/issues/25239. `LegacyEditorBeatmapPatcher.processHitObjectLocalData()` was already supposed to be handling changes to hitobjects that will show up neither when comparing the hitobjects themselves or the timing point with "legacy" info stripped - so, in other words, changes to slider velocity and samples. However, a change to slider velocity requires default application to take effect, so just resetting the value would visually fix the timeline marker but not change the actual object. Calling `EditorBeatmap.Update()` fixes this by way of triggering default re-application. This could probably be smarter (by only invoking the update when strictly necessary, etc.) - but I'm not sure it's worth the hassle. This is intended to be a quick fix, rather than a complete solution - the complete solution would indeed likely entail a wholesale restructuring of the editor's change handling. --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index fe0d4a7822..bb9f702cb5 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -123,6 +123,8 @@ namespace osu.Game.Screens.Edit oldWithRepeats.NodeSamples.Clear(); oldWithRepeats.NodeSamples.AddRange(newWithRepeats.NodeSamples); } + + editorBeatmap.Update(oldObject); } } From cea24298cb99c901077c6e8a23bdfa34da3ceafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 12:42:34 +0100 Subject: [PATCH 35/40] Privatise setters --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index e1ec1ad4ac..575a66d421 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -16,9 +16,9 @@ namespace osu.Game.Screens.Edit { public const float PADDING = 10; - public Container TimelineContent = null!; + public Container TimelineContent { get; private set; } = null!; - public Container MainContent = null!; + public Container MainContent { get; private set; } = null!; private LoadingSpinner spinner = null!; From 88e10dd051d33ca0e63a7cd5f82dac3d9b4e039c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 20:03:44 +0100 Subject: [PATCH 36/40] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 0575817460..2870696c03 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9b06b4a6a7..f1159f58b9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 06508d08fe1f289404bb20756f43403c209e0d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 20:22:41 +0100 Subject: [PATCH 37/40] Delete outdated test --- .../UserInterface/TestSceneSearchTextBox.cs | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs deleted file mode 100644 index 153525d24a..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSearchTextBox.cs +++ /dev/null @@ -1,38 +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 NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public partial class TestSceneSearchTextBox : OsuTestScene - { - private SearchTextBox textBox = null!; - - [SetUp] - public void SetUp() => Schedule(() => - { - Child = textBox = new SearchTextBox - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 400, - Scale = new Vector2(2f), - HoldFocus = true, - }; - }); - - [Test] - public void TestSelectionOnFocus() - { - AddStep("set text", () => textBox.Text = "some text"); - AddAssert("no text selected", () => textBox.SelectedText == string.Empty); - AddStep("hide text box", () => textBox.Hide()); - AddStep("show text box", () => textBox.Show()); - AddAssert("search text selected", () => textBox.SelectedText == textBox.Text); - } - } -} From f2c0bc821802067e8cb9c7e5bbf64215e6e4fc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 21:15:04 +0100 Subject: [PATCH 38/40] Add failing test case --- .../TestSceneSpinnerInput.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs index 5a473409a4..a6c15d5a67 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.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 NUnit.Framework; @@ -10,6 +11,8 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; @@ -47,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void Setup() => Schedule(() => { manualClock = null; + SelectedMods.Value = Array.Empty(); }); /// @@ -102,6 +106,34 @@ namespace osu.Game.Rulesets.Osu.Tests assertSpinnerHit(false); } + [Test] + public void TestVibrateWithoutSpinningOnCentreWithDoubleTime() + { + List frames = new List(); + + const int rate = 2; + // the track clock is going to be playing twice as fast, + // so the vibration time in clock time needs to be twice as long + // to keep constant speed in real time. + const int vibrate_time = 50 * rate; + + int direction = -1; + + for (double i = time_spinner_start; i <= time_spinner_end; i += vibrate_time) + { + frames.Add(new OsuReplayFrame(i, new Vector2(centre_x + direction * 50, centre_y), OsuAction.LeftButton)); + frames.Add(new OsuReplayFrame(i + vibrate_time, new Vector2(centre_x - direction * 50, centre_y), OsuAction.LeftButton)); + + direction *= -1; + } + + AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = rate } } }); + performTest(frames); + + assertTicksHit(0); + assertSpinnerHit(false); + } + /// /// Spins in a single direction. /// From e5b51f769ce72e7394131df3b993a7a840115984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 21:15:40 +0100 Subject: [PATCH 39/40] Fix incorrect assertion placement in spinner rotation tracker Checking the delta after the application of rate is not correct. The delta is in screen-space *before* the rate from rate-changing mods were applied; the point of the application of the rate is to compensate for the fact that the spinner is still judged in "track time" - but the goal is to keep the spinner's difficulty *independent* of rate, which means that with DT active the user's spin is "twice as effective" to compensate for the fact that the spinner is twice as short in real time. In another formulation, with DT active, the user gets to record replay frames "half as often" as in normal gameplay. --- .../Skinning/Default/SpinnerRotationTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index 374f3f461b..1d75663fd9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -101,11 +101,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default rotationTransferred = true; } + Debug.Assert(Math.Abs(delta) <= 180); + double rate = gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate; delta = (float)(delta * Math.Abs(rate)); - Debug.Assert(Math.Abs(delta) <= 180); - currentRotation += delta; drawableSpinner.Result.History.ReportDelta(Time.Current, delta); } From 12ef93ac3b42a86c031c37a9f89e430bf90912b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Oct 2023 21:31:34 +0100 Subject: [PATCH 40/40] Remove no-longer-valid assertion --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs index a6c15d5a67..75bcd809c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerInput.cs @@ -130,7 +130,6 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = rate } } }); performTest(frames); - assertTicksHit(0); assertSpinnerHit(false); }