diff --git a/Directory.Build.props b/Directory.Build.props index 734374c840..b08283f071 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,7 +35,7 @@ https://github.com/ppy/osu Automated release. ppy Pty Ltd - Copyright (c) 2022 ppy Pty Ltd + Copyright (c) 2024 ppy Pty Ltd osu game diff --git a/LICENCE b/LICENCE index d3e7537cef..3bb8b62d5d 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2022 ppy Pty Ltd . +Copyright (c) 2024 ppy Pty Ltd . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d5dc0723af..d7e710f392 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A few resources are available as starting points to getting involved and underst - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management). -- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc). +- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6). ## Running osu! diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index b8c3ad373a..186a6093f5 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -1,4 +1,4 @@ - + Template ppy.osu.Game.Templates @@ -8,7 +8,7 @@ https://github.com/ppy/osu/blob/master/Templates https://github.com/ppy/osu Automated release. - Copyright (c) 2022 ppy Pty Ltd + Copyright (c) 2024 ppy Pty Ltd Templates to use when creating a ruleset for consumption in osu!. dotnet-new;templates;osu netstandard2.1 diff --git a/assets/lazer-nuget.png b/assets/lazer-nuget.png index c2a587fdc2..fed2f45149 100644 Binary files a/assets/lazer-nuget.png and b/assets/lazer-nuget.png differ diff --git a/assets/lazer.png b/assets/lazer.png index 1e40e844cc..2ee44225bf 100644 Binary files a/assets/lazer.png and b/assets/lazer.png differ diff --git a/osu.Android.props b/osu.Android.props index a7376aa5a7..969fd52340 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.Android/Resources/drawable/ic_launcher_background.xml b/osu.Android/Resources/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..1af30228ec --- /dev/null +++ b/osu.Android/Resources/drawable/ic_launcher_background.xml @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Android/Resources/drawable/lazer.png b/osu.Android/Resources/drawable/lazer.png deleted file mode 100644 index fc7aa8a092..0000000000 Binary files a/osu.Android/Resources/drawable/lazer.png and /dev/null differ diff --git a/osu.Android/Resources/drawable/monochrome.xml b/osu.Android/Resources/drawable/monochrome.xml new file mode 100644 index 0000000000..e12af03bfb --- /dev/null +++ b/osu.Android/Resources/drawable/monochrome.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml b/osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..7133c9c861 --- /dev/null +++ b/osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/osu.Android/Resources/mipmap-hdpi/ic_launcher.png b/osu.Android/Resources/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..7870430484 Binary files /dev/null and b/osu.Android/Resources/mipmap-hdpi/ic_launcher.png differ diff --git a/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..b2ec3e49da Binary files /dev/null and b/osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/osu.Android/Resources/mipmap-mdpi/ic_launcher.png b/osu.Android/Resources/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..2a01d8f781 Binary files /dev/null and b/osu.Android/Resources/mipmap-mdpi/ic_launcher.png differ diff --git a/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..e22f256562 Binary files /dev/null and b/osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b5e1a9e379 Binary files /dev/null and b/osu.Android/Resources/mipmap-xhdpi/ic_launcher.png differ diff --git a/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..1cc3fa9072 Binary files /dev/null and b/osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/osu.Android/Resources/mipmap-xxhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8a37b0449e Binary files /dev/null and b/osu.Android/Resources/mipmap-xxhdpi/ic_launcher.png differ diff --git a/osu.Android/Resources/mipmap-xxhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..1b856a31b2 Binary files /dev/null and b/osu.Android/Resources/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..65751e15c9 Binary files /dev/null and b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..05c6829a47 Binary files /dev/null and b/osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico old mode 100755 new mode 100644 index a6aa8abb9f..f84866b8e9 Binary files a/osu.Desktop/lazer.ico and b/osu.Desktop/lazer.ico differ diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index db58c325bd..3b7d6cbe79 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -7,11 +7,12 @@ ppy Pty Ltd Dean Herbert https://osu.ppy.sh/ - https://puu.sh/tYyXZ/9a01a5d1b0.ico + https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true + icon.png false A free-to-win rhythm game. Rhythm is just a *click* away! testing - Copyright (c) 2022 ppy Pty Ltd + Copyright (c) 2024 ppy Pty Ltd en-AU diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 95896c7c91..148cf79337 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -215,8 +215,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (headCircleHitAction == null) timeToAcceptAnyKeyAfter = null; - var actions = slider.OsuActionInputManager?.PressedActions; - // if the head circle was hit with a specific key, tracking should only occur while that key is pressed. if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null) { @@ -227,6 +225,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables timeToAcceptAnyKeyAfter = Time.Current; } + if (slider.OsuActionInputManager == null) + return; + + lastPressedActions.Clear(); + bool validTrackingAction = false; + + foreach (OsuAction action in slider.OsuActionInputManager.PressedActions) + { + if (isValidTrackingAction(action)) + validTrackingAction = true; + + lastPressedActions.Add(action); + } + Tracking = // even in an edge case where current time has exceeded the slider's time, we may not have finished judging. // we don't want to potentially update from Tracking=true to Tracking=false at this point. @@ -234,11 +246,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // in valid position range && isValidTrackingPosition // valid action - && (actions?.Any(isValidTrackingAction) ?? false); - - lastPressedActions.Clear(); - if (actions != null) - lastPressedActions.AddRange(actions); + && validTrackingAction; } private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 3c6319ddf9..900d6523cf 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -30,8 +30,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200), - Child = new InputDrum() + Size = new Vector2(180f, 200f), + Child = new InputDrum + { + RelativeSizeAxes = Axes.Both, + } } }); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index c89e2b727b..d1a8a048ed 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -1,17 +1,19 @@ // 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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Taiko.Tests.Skinning { @@ -37,11 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Beatmap.Value.Track.Start(); }); - AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield + AddStep("Load playfield", () => SetContents(_ => new Container { - Height = 0.2f, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(2f, 1f), + Scale = new Vector2(0.5f), + Child = new TaikoPlayfieldAdjustmentContainer { Child = new TaikoPlayfield() }, })); } @@ -54,7 +59,20 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [Test] public void TestHeightChanges() { - AddRepeatStep("change height", () => this.ChildrenOfType().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50); + int value = 0; + + AddRepeatStep("change height", () => + { + value = (value + 1) % 5; + + this.ChildrenOfType().ForEach(p => + { + var parent = (Container)p.Parent.AsNonNull(); + parent.Scale = new Vector2(0.5f + 0.1f * value); + parent.Width = 1f / parent.Scale.X; + parent.Height = 0.5f / parent.Scale.Y; + }); + }, 50); } [Test] diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index 8b1a4f688c..329fff5b42 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { InternalChild = piece = new HitPiece { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT) }; } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index b0919417a4..cd52398086 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -39,15 +39,15 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { headPiece = new HitPiece { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT) }, lengthPiece = new LengthPiece { - Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT + Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT }, tailPiece = new HitPiece { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT) } }; } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index cdeaafde10..f63d6c2673 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -16,9 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false; - - var playfield = (TaikoPlayfield)drawableRuleset.Playfield; - playfield.ClassicHitTargetPosition.Value = true; } public void ApplyToDrawableHitObject(DrawableHitObject drawable) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 733772e21f..64f2f4c18a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private Vector2 adjustSizeForPlayfieldAspectRatio(float size) { - return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y); } protected override void UpdateFlashlightSize(float size) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs index f7b7105bdc..f22c7bf017 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonInputDrum.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon public ArgonInputDrum() { - RelativeSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs index 60bacf6413..3eb4f6b8a6 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default { public DefaultInputDrum() { - RelativeSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 28415bb72a..838f172186 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -17,22 +18,20 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal partial class LegacyInputDrum : Container { - private Container content = null!; private LegacyHalfDrum left = null!; private LegacyHalfDrum right = null!; public LegacyInputDrum() { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Child = content = new Container + Child = new Container { - Size = new Vector2(180, 200), + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Sprite @@ -65,33 +64,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = content.Width / ratio; + const float negative_scale_adjust = TaikoPlayfield.INPUT_DRUM_WIDTH / ratio; if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio; - right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + right.Centre.Position = new Vector2(negative_scale_adjust - 56, taiko_bar_y) * ratio; left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio; - right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + right.Rim.Position = new Vector2(negative_scale_adjust - 56, taiko_bar_y) * ratio; } else { left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio; - right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio; + right.Centre.Position = new Vector2(negative_scale_adjust - 54, taiko_bar_y + 31) * ratio; left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio; - right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio; + right.Rim.Position = new Vector2(negative_scale_adjust - 53, taiko_bar_y + 23) * ratio; } } - protected override void Update() - { - base.Update(); - - // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. - // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / content.Size.Y); - } - /// /// A half-drum. Contains one centre and one rim hit. /// diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs index 492782f0d1..0b43f1c845 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -13,47 +12,30 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public partial class TaikoLegacyHitTarget : CompositeDrawable { - private Container content = null!; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { RelativeSizeAxes = Axes.Both; - InternalChild = content = new Container + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + new Sprite { - new Sprite - { - Texture = skin.GetTexture("approachcircle"), - Scale = new Vector2(0.83f), - Alpha = 0.47f, // eyeballed to match stable - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new Sprite - { - Texture = skin.GetTexture("taikobigcircle"), - Scale = new Vector2(0.8f), - Alpha = 0.22f, // eyeballed to match stable - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } + Texture = skin.GetTexture("approachcircle"), + Scale = new Vector2(0.83f), + Alpha = 0.47f, // eyeballed to match stable + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Sprite + { + Texture = skin.GetTexture("taikobigcircle"), + Scale = new Vector2(0.8f), + Alpha = 0.22f, // eyeballed to match stable + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, }; } - - protected override void Update() - { - base.Update(); - - // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. - // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); - } } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 725857ed34..d0a8cf647d 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -14,12 +14,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal partial class InputDrum : Container { - public InputDrum() - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - } - [BackgroundDependencyLoader] private void load() { @@ -27,8 +21,7 @@ namespace osu.Game.Rulesets.Taiko.UI { new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.InputDrum), _ => new DefaultInputDrum()) { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, }, }; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7e3ed7a4d4..0510f08068 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -21,21 +20,17 @@ using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Taiko.UI { public partial class TaikoPlayfield : ScrollingPlayfield { /// - /// Default height of a when inside a . + /// Base height of a when inside a . /// - public const float DEFAULT_HEIGHT = 200; + public const float BASE_HEIGHT = 200; - /// - /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. - /// - public Bindable ClassicHitTargetPosition = new BindableBool(); + public const float INPUT_DRUM_WIDTH = 180f; public Container UnderlayElements { get; private set; } = null!; @@ -44,14 +39,12 @@ namespace osu.Game.Rulesets.Taiko.UI private JudgementContainer judgementContainer = null!; private ScrollingHitObjectContainer drumRollHitContainer = null!; internal Drawable HitTarget = null!; - private SkinnableDrawable mascot = null!; private JudgementPooler judgementPooler = null!; private readonly IDictionary explosionPools = new Dictionary(); private ProxyContainer topLevelHitContainer = null!; private InputDrum inputDrum = null!; - private Container rightArea = null!; /// /// is purposefully not called on this to prevent i.e. being able to interact @@ -59,19 +52,18 @@ namespace osu.Game.Rulesets.Taiko.UI /// private BarLinePlayfield barLinePlayfield = null!; - private Container barLineContent = null!; - private Container hitObjectContent = null!; - private Container overlayContent = null!; - [BackgroundDependencyLoader] private void load(OsuColour colours) { + const float hit_target_width = BASE_HEIGHT; + const float hit_target_offset = -24f; + inputDrum = new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, + Width = INPUT_DRUM_WIDTH, }; InternalChildren = new[] @@ -80,8 +72,8 @@ namespace osu.Game.Rulesets.Taiko.UI new Container { Name = "Left overlay", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Y, + Width = INPUT_DRUM_WIDTH, BorderColour = colours.Gray0, Children = new[] { @@ -89,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.UI inputDrum.CreateProxy(), } }, - mascot = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty()) + new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty()) { Origin = Anchor.BottomLeft, Anchor = Anchor.TopLeft, @@ -97,18 +89,19 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.None, Y = 0.2f }, - rightArea = new Container + new Container { Name = "Right area", RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, + Padding = new MarginPadding { Left = INPUT_DRUM_WIDTH }, Children = new Drawable[] { new Container { - Name = "Elements before hit objects", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, + Name = "Elements behind hit objects", + RelativeSizeAxes = Axes.Y, + Width = hit_target_width, + X = hit_target_offset, Children = new[] { new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty()) @@ -125,10 +118,11 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - barLineContent = new Container + new Container { Name = "Bar line content", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset }, Children = new Drawable[] { UnderlayElements = new Container @@ -138,17 +132,19 @@ namespace osu.Game.Rulesets.Taiko.UI barLinePlayfield = new BarLinePlayfield(), } }, - hitObjectContent = new Container + new Container { Name = "Masked hit objects content", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset }, Masking = true, Child = HitObjectContainer, }, - overlayContent = new Container + new Container { - Name = "Elements after hit objects", + Name = "Overlay content", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset }, Children = new Drawable[] { drumRollHitContainer = new DrumRollHitContainer(), @@ -222,20 +218,6 @@ namespace osu.Game.Rulesets.Taiko.UI topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); } - protected override void Update() - { - base.Update(); - - // Padding is required to be updated for elements which are based on "absolute" X sized elements. - // This is basically allowing for correct alignment as relative pieces move around them. - rightArea.Padding = new MarginPadding { Left = inputDrum.Width }; - barLineContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - hitObjectContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - overlayContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; - - mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); - } - #region Pooling support public override void Add(HitObject h) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 54608b77de..c10e505f50 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -5,23 +5,33 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Taiko.UI { public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { - private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; - public const float MAXIMUM_ASPECT = 16f / 9f; public const float MINIMUM_ASPECT = 5f / 4f; public readonly IBindable LockPlayfieldAspectRange = new BindableBool(true); + public TaikoPlayfieldAdjustmentContainer() + { + RelativeSizeAxes = Axes.X; + RelativePositionAxes = Axes.Y; + Height = TaikoPlayfield.BASE_HEIGHT; + } + protected override void Update() { base.Update(); - float height = default_relative_height; + const float base_relative_height = TaikoPlayfield.BASE_HEIGHT / 768; + // Matches stable, see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L514 + const float base_position = 135f / 480f; + + float relativeHeight = base_relative_height; // Players coming from stable expect to be able to change the aspect ratio regardless of the window size. // We originally wanted to limit this more, but there was considerable pushback from the community. @@ -33,19 +43,18 @@ namespace osu.Game.Rulesets.Taiko.UI float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y; if (currentAspect > MAXIMUM_ASPECT) - height *= currentAspect / MAXIMUM_ASPECT; + relativeHeight *= currentAspect / MAXIMUM_ASPECT; else if (currentAspect < MINIMUM_ASPECT) - height *= currentAspect / MINIMUM_ASPECT; + relativeHeight *= currentAspect / MINIMUM_ASPECT; } // Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions. - height = Math.Min(height, 1f / 3f); - Height = height; + relativeHeight = Math.Min(relativeHeight, 1f / 3f); - // Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it. - // Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered. - RelativePositionAxes = Axes.Y; - Y = height; + Y = base_position; + + Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f)); + Width = 1 / Scale.X; } } } diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index fbe5a0e4d7..98cb66a234 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -169,9 +169,9 @@ namespace osu.Game.Tests.NonVisual.Skinning public IRenderer Renderer => new DummyRenderer(); public AudioManager AudioManager => null; - public IResourceStore Files => null; - public IResourceStore Resources => null; - public RealmAccess RealmAccess => null; + public IResourceStore Files => null!; + public IResourceStore Resources => null!; + public RealmAccess RealmAccess => null!; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => textureStore; } } diff --git a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs index 706b05f5ad..4143a6d76d 100644 --- a/osu.Game/Graphics/Backgrounds/TrianglesV2.cs +++ b/osu.Game/Graphics/Backgrounds/TrianglesV2.cs @@ -1,17 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Utils; -using osuTK; using System; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Allocation; using System.Collections.Generic; -using osu.Framework.Graphics.Rendering; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osuTK; namespace osu.Game.Graphics.Backgrounds { @@ -27,6 +27,8 @@ namespace osu.Game.Graphics.Backgrounds public float Thickness { get; set; } = 0.02f; // No need for invalidation since it's happening in Update() + public float ScaleAdjust { get; set; } = 1; + /// /// Whether we should create new triangles as others expire. /// @@ -106,7 +108,7 @@ namespace osu.Game.Graphics.Backgrounds parts[i] = newParticle; - float bottomPos = parts[i].Position.Y + triangle_size * equilateral_triangle_ratio / DrawHeight; + float bottomPos = parts[i].Position.Y + triangle_size * ScaleAdjust * equilateral_triangle_ratio / DrawHeight; if (bottomPos < 0) parts.RemoveAt(i); } @@ -149,7 +151,7 @@ namespace osu.Game.Graphics.Backgrounds if (randomY) { // since triangles are drawn from the top - allow them to be positioned a bit above the screen - float maxOffset = triangle_size * equilateral_triangle_ratio / DrawHeight; + float maxOffset = triangle_size * ScaleAdjust * equilateral_triangle_ratio / DrawHeight; y = Interpolation.ValueAt(nextRandom(), -maxOffset, 1f, 0f, 1f); } @@ -188,7 +190,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); - private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; + private Vector2 triangleSize; private Vector2 size; private float thickness; @@ -209,6 +211,7 @@ namespace osu.Game.Graphics.Backgrounds size = Source.DrawSize; thickness = Source.Thickness; clampAxes = Source.ClampAxes; + triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size * Source.ScaleAdjust; Quad triangleQuad = new Quad( Vector2Extensions.Transform(Vector2.Zero, DrawInfo.Matrix), diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 435c100c9a..4962838bd9 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -183,7 +183,7 @@ namespace osu.Game.Online.API public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; - public IChatClient GetChatClient() => new PollingChatClientConnector(this); + public IChatClient GetChatClient() => new TestChatClientConnector(this); public RegistrationRequest.RegistrationRequestErrors? CreateAccount(string email, string username, string password) { diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 75ef8be02e..25101730e7 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -31,15 +32,13 @@ namespace osu.Game.Screens.Menu /// public partial class OsuLogo : BeatSyncedContainer { - public readonly Color4 OsuPink = Color4Extensions.FromHex(@"e967a1"); - private const double transition_length = 300; /// /// The osu! logo sprite has a shadow included in its texture. /// This adjustment vector is used to match the precise edge of the border of the logo. /// - public static readonly Vector2 SCALE_ADJUST = new Vector2(0.96f); + public static readonly Vector2 SCALE_ADJUST = new Vector2(0.94f); private readonly Sprite logo; private readonly CircularContainer logoContainer; @@ -58,7 +57,7 @@ namespace osu.Game.Screens.Menu private Sample sampleDownbeat; private readonly Container colourAndTriangles; - private readonly Triangles triangles; + private readonly TrianglesV2 triangles; /// /// Return value decides whether the logo should play its own sample for the click action. @@ -184,13 +183,16 @@ namespace osu.Game.Screens.Menu new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuPink, + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"cc5289")), }, - triangles = new Triangles + triangles = new TrianglesV2 { - TriangleScale = 4, - ColourLight = Color4Extensions.FromHex(@"ff7db7"), - ColourDark = Color4Extensions.FromHex(@"de5b95"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Thickness = 0.009f, + ScaleAdjust = 3, + SpawnRatio = 1.4f, + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"b6346f")), RelativeSizeAxes = Axes.Both, }, } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index cb38854bca..7db3f9fd3c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -114,12 +114,7 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { - bar.TrackTime = GameplayClock.CurrentTime; - - if (isIntro) - bar.CurrentTime = 0; - else - bar.CurrentTime = FrameStableClock.CurrentTime; + bar.Progress = isIntro ? 0 : progress; } } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index beaee0e9ee..7a7870a775 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -3,96 +3,59 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics; using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class ArgonSongProgressBar : SliderBar + public partial class ArgonSongProgressBar : SongProgressBar { - public Action? OnSeek { get; set; } - // Parent will handle restricting the area of valid input. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private readonly float barHeight; private readonly RoundedBar playfieldBar; - private readonly RoundedBar catchupBar; + private readonly RoundedBar audioBar; private readonly Box background; private readonly ColourInfo mainColour; private ColourInfo catchUpColour; - public double StartTime - { - private get => CurrentNumber.MinValue; - set => CurrentNumber.MinValue = value; - } + public double Progress { get; set; } - public double EndTime - { - private get => CurrentNumber.MaxValue; - set => CurrentNumber.MaxValue = value; - } - - public double CurrentTime - { - private get => CurrentNumber.Value; - set => CurrentNumber.Value = value; - } - - public double TrackTime - { - private get => currentTrackTime.Value; - set => currentTrackTime.Value = value; - } - - private double length => EndTime - StartTime; - - private readonly BindableNumber currentTrackTime; - - public bool Interactive { get; set; } + private double trackTime => (EndTime - StartTime) * Progress; public ArgonSongProgressBar(float barHeight) { - currentTrackTime = new BindableDouble(); - setupAlternateValue(); - - StartTime = 0; - EndTime = 1; - RelativeSizeAxes = Axes.X; Height = this.barHeight = barHeight; CornerRadius = 5; Masking = true; - Children = new Drawable[] + InternalChildren = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, Colour = OsuColour.Gray(0.2f), + Depth = float.MaxValue, }, - catchupBar = new RoundedBar + audioBar = new RoundedBar { Name = "Audio bar", Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, CornerRadius = 5, - AlwaysPresent = true, RelativeSizeAxes = Axes.Both }, playfieldBar = new RoundedBar @@ -107,24 +70,6 @@ namespace osu.Game.Screens.Play.HUD }; } - private void setupAlternateValue() - { - CurrentNumber.MaxValueChanged += v => currentTrackTime.MaxValue = v; - CurrentNumber.MinValueChanged += v => currentTrackTime.MinValue = v; - CurrentNumber.PrecisionChanged += v => currentTrackTime.Precision = v; - } - - private float normalizedReference - { - get - { - if (EndTime - StartTime == 0) - return 1; - - return (float)((TrackTime - StartTime) / length); - } - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -153,47 +98,28 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } - protected override void UpdateValue(float value) - { - // Handled in Update - } - protected override void Update() { base.Update(); - playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1)); - catchupBar.Length = (float)Interpolation.Lerp(catchupBar.Length, normalizedReference, Math.Clamp(Time.Elapsed / 40, 0, 1)); + playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); + audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1)); - if (TrackTime < CurrentTime) - ChangeChildDepth(catchupBar, -1); + if (trackTime > AudioTime) + ChangeInternalChildDepth(audioBar, -1); else - ChangeChildDepth(catchupBar, 0); + ChangeInternalChildDepth(audioBar, 1); - float timeDelta = (float)(Math.Abs(CurrentTime - TrackTime)); + float timeDelta = (float)Math.Abs(AudioTime - trackTime); const float colour_transition_threshold = 20000; - catchupBar.AccentColour = Interpolation.ValueAt( + audioBar.AccentColour = Interpolation.ValueAt( Math.Min(timeDelta, colour_transition_threshold), mainColour, catchUpColour, 0, colour_transition_threshold, Easing.OutQuint); - - catchupBar.Alpha = Math.Max(1, catchupBar.Length); - } - - private ScheduledDelegate? scheduledSeek; - - protected override void OnUserChange(double value) - { - scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => - { - if (Interactive) - OnSeek?.Invoke(value); - }); } private partial class RoundedBar : Container diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 48809796f3..f01c11855c 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -98,12 +98,7 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { - bar.CurrentTime = GameplayClock.CurrentTime; - - if (isIntro) - graph.Progress = 0; - else - graph.Progress = (int)(graph.ColumnCount * progress); + graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress); } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs index 0e16067dcc..d5a6a75793 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressBar.cs @@ -7,71 +7,27 @@ using osuTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; -using osu.Framework.Threading; namespace osu.Game.Screens.Play.HUD { - public partial class DefaultSongProgressBar : SliderBar + public partial class DefaultSongProgressBar : SongProgressBar { - /// - /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. - /// - public Action? OnSeek { get; set; } - - /// - /// Whether the progress bar should allow interaction, ie. to perform seek operations. - /// - public bool Interactive - { - get => showHandle; - set - { - if (value == showHandle) - return; - - showHandle = value; - - handleBase.FadeTo(showHandle ? 1 : 0, 200); - } - } - public Color4 FillColour { set => fill.Colour = value; } - public double StartTime - { - set => CurrentNumber.MinValue = value; - } - - public double EndTime - { - set => CurrentNumber.MaxValue = value; - } - - public double CurrentTime - { - set => CurrentNumber.Value = value; - } - private readonly Box fill; private readonly Container handleBase; private readonly Container handleContainer; - private bool showHandle; - public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) { - CurrentNumber.MinValue = 0; - CurrentNumber.MaxValue = 1; - RelativeSizeAxes = Axes.X; Height = barHeight + handleBarHeight + handleSize.Y; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { @@ -130,9 +86,14 @@ namespace osu.Game.Screens.Play.HUD }; } - protected override void UpdateValue(float value) + public override bool Interactive { - // handled in update + get => base.Interactive; + set + { + base.Interactive = value; + handleBase.FadeTo(value ? 1 : 0, 200); + } } protected override void Update() @@ -140,22 +101,10 @@ namespace osu.Game.Screens.Play.HUD base.Update(); handleBase.Height = Height - handleContainer.Height; - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); + float newX = (float)Interpolation.Lerp(handleBase.X, AudioProgress * DrawWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; } - - private ScheduledDelegate? scheduledSeek; - - protected override void OnUserChange(double value) - { - scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => - { - if (showHandle) - OnSeek?.Invoke(value); - }); - } } } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 4391193df8..296306ec89 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -70,7 +71,13 @@ namespace osu.Game.Screens.Play.HUD protected double LastHitTime { get; private set; } + /// + /// Called every update frame with current progress information. + /// + /// Current (visual) progress through the beatmap (0..1). + /// If true, progress is (0..1) through the intro. protected abstract void UpdateProgress(double progress, bool isIntro); + protected virtual void UpdateObjects(IEnumerable objects) { } [BackgroundDependencyLoader] @@ -96,7 +103,7 @@ namespace osu.Game.Screens.Play.HUD if (objects == null) return; - double currentTime = FrameStableClock.CurrentTime; + double currentTime = Math.Min(FrameStableClock.CurrentTime, LastHitTime); bool isInIntro = currentTime < FirstHitTime; diff --git a/osu.Game/Screens/Play/HUD/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs new file mode 100644 index 0000000000..40c4e587b9 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Threading; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public abstract partial class SongProgressBar : CompositeDrawable + { + /// + /// The current seek position of the audio, on a (0..1) range. + /// This is generally the seek target, which will eventually match the gameplay clock when it catches up. + /// + protected double AudioProgress => length == 0 ? 1 : AudioTime / length; + + /// + /// The current (non-frame-stable) audio time. + /// + protected double AudioTime => Math.Clamp(GameplayClock.CurrentTime - StartTime, 0.0, length); + + [Resolved] + protected IGameplayClock GameplayClock { get; private set; } = null!; + + /// + /// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation. + /// + public Action? OnSeek { get; set; } + + /// + /// Whether the progress bar should allow interaction, ie. to perform seek operations. + /// + public virtual bool Interactive { get; set; } + + public double StartTime { get; set; } + + public double EndTime { get; set; } = 1.0; + + private double length => EndTime - StartTime; + + private bool handleClick; + + protected override bool OnMouseDown(MouseDownEvent e) + { + handleClick = true; + return base.OnMouseDown(e); + } + + protected override bool OnClick(ClickEvent e) + { + if (handleClick) + handleMouseInput(e); + + return true; + } + + protected override void OnDrag(DragEvent e) + { + handleMouseInput(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + Vector2 posDiff = e.MouseDownPosition - e.MousePosition; + + if (Math.Abs(posDiff.X) < Math.Abs(posDiff.Y)) + { + handleClick = false; + return false; + } + + handleMouseInput(e); + return true; + } + + private void handleMouseInput(UIEvent e) + { + if (!Interactive) + return; + + double relativeX = Math.Clamp(ToLocalSpace(e.ScreenSpaceMousePosition).X / DrawWidth, 0, 1); + onUserChange(StartTime + (EndTime - StartTime) * relativeX); + } + + private ScheduledDelegate? scheduledSeek; + + private void onUserChange(double value) + { + scheduledSeek?.Cancel(); + scheduledSeek = Schedule(() => OnSeek?.Invoke(value)); + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b5482f2a5b..32ebb82f15 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -259,13 +258,12 @@ namespace osu.Game.Screens.Play Vector2? highestBottomScreenSpace = null; - // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. - foreach (var element in mainComponents.Components.Cast()) + foreach (var element in mainComponents.Components) processDrawable(element); if (rulesetComponents != null) { - foreach (var element in rulesetComponents.Components.Cast()) + foreach (var element in rulesetComponents.Components) processDrawable(element); } @@ -284,33 +282,36 @@ namespace osu.Game.Screens.Play else bottomRightElements.Y = 0; - void processDrawable(Drawable element) + void processDrawable(ISerialisableDrawable element) { + // Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. + Drawable drawable = (Drawable)element; + // for now align some top components with the bottom-edge of the lowest top-anchored hud element. - if (element.Anchor.HasFlagFast(Anchor.y0)) + if (drawable.Anchor.HasFlagFast(Anchor.y0)) { // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. if (element is LegacyHealthDisplay) return; - float bottom = element.ScreenSpaceDrawQuad.BottomRight.Y; + float bottom = drawable.ScreenSpaceDrawQuad.BottomRight.Y; - bool isRelativeX = element.RelativeSizeAxes == Axes.X; + bool isRelativeX = drawable.RelativeSizeAxes == Axes.X; - if (element.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX) + if (drawable.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX) { if (lowestTopScreenSpaceRight == null || bottom > lowestTopScreenSpaceRight.Value) lowestTopScreenSpaceRight = bottom; } - if (element.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX) + if (drawable.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX) { if (lowestTopScreenSpaceLeft == null || bottom > lowestTopScreenSpaceLeft.Value) lowestTopScreenSpaceLeft = bottom; } } // and align bottom-right components with the top-edge of the highest bottom-anchored hud element. - else if (element.Anchor.HasFlagFast(Anchor.BottomRight) || (element.Anchor.HasFlagFast(Anchor.y2) && element.RelativeSizeAxes == Axes.X)) + else if (drawable.Anchor.HasFlagFast(Anchor.BottomRight) || (drawable.Anchor.HasFlagFast(Anchor.y2) && drawable.RelativeSizeAxes == Axes.X)) { var topLeft = element.ScreenSpaceDrawQuad.TopLeft; if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y) diff --git a/osu.Game/Tests/PollingChatClientConnector.cs b/osu.Game/Tests/TestChatClientConnector.cs similarity index 89% rename from osu.Game/Tests/PollingChatClientConnector.cs rename to osu.Game/Tests/TestChatClientConnector.cs index f1b0d9dd7d..40e15b5ef5 100644 --- a/osu.Game/Tests/PollingChatClientConnector.cs +++ b/osu.Game/Tests/TestChatClientConnector.cs @@ -11,7 +11,7 @@ using osu.Game.Online.Chat; namespace osu.Game.Tests { - public class PollingChatClientConnector : PersistentEndpointClientConnector, IChatClient + public class TestChatClientConnector : PersistentEndpointClientConnector, IChatClient { public event Action? ChannelJoined; @@ -29,7 +29,7 @@ namespace osu.Game.Tests // don't really need to do anything special if we poll every second anyway. } - public PollingChatClientConnector(IAPIProvider api) + public TestChatClientConnector(IAPIProvider api) : base(api) { Start(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1b1abe3971..6f71424130 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,8 +36,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 98e8b136e5..bbcabc6360 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png new file mode 100644 index 0000000000..21f5f0f3a0 Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json index af4b103867..29df54b400 100644 --- a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1 +1,14 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"iPhoneApp2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"iPhoneApp3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"iPadApp1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"iPadApp2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"iPadProApp2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"iOSAppStore.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{ + "images" : [ + { + "filename" : "300076680-5cbe0121-ed68-414f-9ddc-dd993ac97e62.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png deleted file mode 100644 index 0e8bb029bc..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iOSAppStore.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp1x.png deleted file mode 100644 index 42fead2364..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp1x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp2x.png deleted file mode 100644 index 785db50cb2..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadApp2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png deleted file mode 100644 index 8c483a0a7a..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification1x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png deleted file mode 100644 index a45b01b91c..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadNotification2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png deleted file mode 100644 index d2ba8f3a7e..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadProApp2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png deleted file mode 100644 index 43d577040e..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings1x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings2x.png deleted file mode 100644 index 1ebec1390b..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSettings2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight1x.png deleted file mode 100644 index a45b01b91c..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight1x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png deleted file mode 100644 index 717603dd68..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPadSpotlight2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp2x.png deleted file mode 100644 index 6b61c09db5..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png deleted file mode 100644 index 78ef8d12b7..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneApp3x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png deleted file mode 100644 index a45b01b91c..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png deleted file mode 100644 index 46ddf1179d..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneNotification3x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png deleted file mode 100644 index 1ebec1390b..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings3x.png deleted file mode 100644 index a8145f0246..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSettings3x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png deleted file mode 100644 index 717603dd68..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight2x.png and /dev/null differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight3x.png deleted file mode 100644 index 6b61c09db5..0000000000 Binary files a/osu.iOS/Assets.xcassets/AppIcon.appiconset/iPhoneSpotlight3x.png and /dev/null differ diff --git a/osu.iOS/iTunesArtwork b/osu.iOS/iTunesArtwork deleted file mode 100644 index 1939459992..0000000000 Binary files a/osu.iOS/iTunesArtwork and /dev/null differ diff --git a/osu.iOS/iTunesArtwork@2x b/osu.iOS/iTunesArtwork@2x deleted file mode 100644 index 0e8bb029bc..0000000000 Binary files a/osu.iOS/iTunesArtwork@2x and /dev/null differ