diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs index d6d7750448..fd01b11164 100644 --- a/osu.Android/AndroidMouseSettings.cs +++ b/osu.Android/AndroidMouseSettings.cs @@ -70,7 +70,7 @@ namespace osu.Android }, new SettingsCheckbox { - LabelText = MouseSettingsStrings.DisableMouseButtons, + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), }, }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 2e62689e2c..25fe8170b1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -133,8 +133,11 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestSimpleInput() + public void TestSimpleInput([Values] bool disableMouseButtons) { + // OsuSetting.MouseDisableButtons should not affect touch taps + AddStep($"{(disableMouseButtons ? "disable" : "enable")} mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, disableMouseButtons)); + beginTouch(TouchSource.Touch1); assertKeyCounter(1, 0); @@ -468,7 +471,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestInputWhileMouseButtonsDisabled() { - AddStep("Disable mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, true)); + AddStep("Disable gameplay taps", () => config.SetValue(OsuSetting.TouchDisableGameplayTaps, true)); beginTouch(TouchSource.Touch1); @@ -620,6 +623,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Release all touches", () => { config.SetValue(OsuSetting.MouseDisableButtons, false); + config.SetValue(OsuSetting.TouchDisableGameplayTaps, false); foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources) InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre)); }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index ea57a6a1b5..77b16dd0c5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -36,6 +37,12 @@ namespace osu.Game.Rulesets.Osu.Tests AddSliderStep("Spin rate", 0.5, 5, 1, val => spinRate.Value = val); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Reset rate", () => spinRate.Value = 1); + } + [TestCase(true)] [TestCase(false)] public void TestVariousSpinners(bool autoplay) @@ -46,6 +53,36 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep($"{term} Small", () => SetContents(_ => testSingle(7, autoplay))); } + [Test] + public void TestSpinnerNoBonus() + { + AddStep("Set high spin rate", () => spinRate.Value = 5); + + Spinner spinner; + + AddStep("add spinner", () => SetContents(_ => + { + spinner = new Spinner + { + StartTime = Time.Current, + EndTime = Time.Current + 750, + Samples = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + } + }; + + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { OverallDifficulty = 0 }); + + return drawableSpinner = new TestDrawableSpinner(spinner, true, spinRate) + { + Anchor = Anchor.Centre, + Depth = depthIndex++, + Scale = new Vector2(0.75f) + }; + })); + } + [Test] public void TestSpinningSamplePitchShift() { @@ -153,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests { base.Update(); if (auto) - RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * spinRate.Value)); + RotationTracker.AddRotation((float)Math.Min(180, Clock.ElapsedFrameTime * spinRate.Value)); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2e9a4d92ec..c0c135d145 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -45,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; + private SkinnableSound maxBonusSample; + /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. /// @@ -109,6 +112,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, Looping = true, Frequency = { Value = spinning_sample_initial_frequency } + }, + maxBonusSample = new SkinnableSound + { + MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, } }); @@ -128,6 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnFree(); spinningSample.ClearSamples(); + maxBonusSample.ClearSamples(); } protected override void LoadSamples() @@ -136,6 +144,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables spinningSample.Samples = HitObject.CreateSpinningSamples().Cast().ToArray(); spinningSample.Frequency.Value = spinning_sample_initial_frequency; + + maxBonusSample.Samples = new ISampleInfo[] { new SpinnerBonusMaxSampleInfo(HitObject.CreateHitSampleInfo()) }; } private void updateSpinningSample(ValueChangedEvent tracking) @@ -157,6 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.StopAllSamples(); spinningSample?.Stop(); + maxBonusSample?.Stop(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) @@ -322,10 +333,38 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var tick = ticks.FirstOrDefault(t => !t.Result.HasResult); // tick may be null if we've hit the spin limit. - tick?.TriggerResult(true); + if (tick == null) + { + // we still want to play a sound. this will probably be a new sound in the future, but for now let's continue playing the bonus sound. + // TODO: this doesn't concurrency. i can't figure out how to make it concurrency. samples are bad and need a refactor. + maxBonusSample.Play(); + } + else + tick.TriggerResult(true); completedFullSpins.Value++; } } + + public class SpinnerBonusMaxSampleInfo : HitSampleInfo + { + public override IEnumerable LookupNames + { + get + { + foreach (string name in base.LookupNames) + yield return name; + + foreach (string name in base.LookupNames) + yield return name.Replace("-max", string.Empty); + } + } + + public SpinnerBonusMaxSampleInfo(HitSampleInfo sampleInfo) + : base("spinnerbonus-max", sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume) + + { + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 5277a1f7d6..e815d7873e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly OsuInputManager osuInputManager; - private Bindable mouseDisabled = null!; + private Bindable tapsDisabled = null!; public OsuTouchInputMapper(OsuInputManager inputManager) { @@ -43,9 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - // The mouse button disable setting affects touch. It's a bit weird. - // This is mostly just doing the same as what is done in RulesetInputManager to match behaviour. - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); + tapsDisabled = config.GetBindable(OsuSetting.TouchDisableGameplayTaps); } // Required to handle touches outside of the playfield when screen scaling is enabled. @@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI : OsuAction.LeftButton; // Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future. - bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action); + bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !tapsDisabled.Value && trackedTouches.All(t => t.Action != action); // If we can actually accept as an action, check whether this tap was on a circle's receptor. // This case gets special handling to allow for empty-space stream tapping. diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index d0fa5fc737..b79b61202b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -6,11 +6,13 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Overlays; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -232,6 +234,35 @@ namespace osu.Game.Tests.Visual.Navigation () => Is.EqualTo(beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0).ID)); } + [Test] + public void TestCreateNewDifficultyOnNonExistentBeatmap() + { + AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + + AddStep("open editor", () => Game.ChildrenOfType().Single().OnEdit.Invoke()); + AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddStep("click on file", () => + { + var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "File"); + item.TriggerClick(); + }); + AddStep("click on create new difficulty", () => + { + var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "Create new difficulty"); + item.TriggerClick(); + }); + AddStep("click on catch", () => + { + var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "osu!catch"); + item.TriggerClick(); + }); + AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is SaveRequiredPopupDialog); + + AddStep("press save", () => Game.ChildrenOfType().Single().CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits"); + } + private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; diff --git a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs index 4f19003638..f4bde159e5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs @@ -17,18 +17,16 @@ namespace osu.Game.Tests.Visual.Online { BarGraph graph; - Children = new[] + Child = graph = new BarGraph { - graph = new BarGraph - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.5f), - }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f), }; AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i)); + AddStep("small values", () => graph.Values = Enumerable.Range(1, 10).Select(i => i * 0.01f).Concat(new[] { 100f })); AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); AddStep("empty values", () => graph.Values = Array.Empty()); @@ -36,6 +34,14 @@ namespace osu.Game.Tests.Visual.Online AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); + + AddToggleStep("Toggle movement", enabled => + { + if (enabled) + graph.MoveToY(-10, 1000).Then().MoveToY(10, 1000).Loop(); + else + graph.ClearTransforms(); + }); } } } diff --git a/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs index 4f0c7d6b72..fd7a51140b 100644 --- a/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs +++ b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -17,6 +19,11 @@ namespace osu.Game.Tournament.Components { AutoSizeAxes = Axes.Both; + var players = team?.Players ?? new BindableList(); + + // split the players into two even columns, favouring the first column if odd. + int split = (int)Math.Ceiling(players.Count / 2f); + InternalChildren = new Drawable[] { new FillFlowContainer @@ -39,13 +46,13 @@ namespace osu.Game.Tournament.Components { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - ChildrenEnumerable = team?.Players.Select(createPlayerText).Take(5) ?? Enumerable.Empty() + ChildrenEnumerable = players.Take(split).Select(createPlayerText), }, new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - ChildrenEnumerable = team?.Players.Select(createPlayerText).Skip(5) ?? Enumerable.Empty() + ChildrenEnumerable = players.Skip(split).Select(createPlayerText), }, } }, diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 5d2d782063..6ef55ab919 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -108,6 +108,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MouseDisableWheel, false); SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); + SetDefault(OsuSetting.TouchDisableGameplayTaps, false); + // Graphics SetDefault(OsuSetting.ShowFpsDisplay, false); @@ -330,6 +332,10 @@ namespace osu.Game.Configuration ShowHealthDisplayWhenCantFail, FadePlayfieldWhenHealthLow, + + /// + /// Disables mouse buttons clicks during gameplay. + /// MouseDisableButtons, MouseDisableWheel, ConfineMouseMode, @@ -408,6 +414,7 @@ namespace osu.Game.Configuration EditorLimitedDistanceSnap, ReplaySettingsOverlay, AutomaticallyDownloadMissingBeatmaps, - EditorShowSpeedChanges + EditorShowSpeedChanges, + TouchDisableGameplayTaps, } } diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 27a41eb7e3..0ac987e85b 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -145,6 +145,13 @@ namespace osu.Game.Graphics.UserInterface float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? lengths[i] : barBreadth); float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? lengths[i] : barBreadth); + if (barHeight == 0 || barWidth == 0) + continue; + + // Apply minimum sizing to hide the fact that we don't have fractional anti-aliasing. + barHeight = Math.Max(barHeight, 1.5f); + barWidth = Math.Max(barWidth, 1.5f); + Vector2 topLeft; switch (direction) diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 1772f03b29..e61af07364 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -40,14 +40,14 @@ namespace osu.Game.Localisation public static LocalisableString DisableMouseWheelVolumeAdjust => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust"), @"Disable mouse wheel adjusting volume during gameplay"); /// - /// "Volume can still be adjusted using the mouse wheel by holding "Alt"" + /// "Volume can still be adjusted using the mouse wheel by holding "Alt"" /// public static LocalisableString DisableMouseWheelVolumeAdjustTooltip => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust_tooltip"), @"Volume can still be adjusted using the mouse wheel by holding ""Alt"""); /// - /// "Disable mouse buttons during gameplay" + /// "Disable clicks during gameplay" /// - public static LocalisableString DisableMouseButtons => new TranslatableString(getKey(@"disable_mouse_buttons"), @"Disable mouse buttons during gameplay"); + public static LocalisableString DisableClicksDuringGameplay => new TranslatableString(getKey(@"disable_clicks"), @"Disable clicks during gameplay"); /// /// "Enable high precision mouse to adjust sensitivity" diff --git a/osu.Game/Localisation/TouchSettingsStrings.cs b/osu.Game/Localisation/TouchSettingsStrings.cs new file mode 100644 index 0000000000..785b333100 --- /dev/null +++ b/osu.Game/Localisation/TouchSettingsStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class TouchSettingsStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.TouchSettings"; + + /// + /// "Touch" + /// + public static LocalisableString Touch => new TranslatableString(getKey(@"touch"), @"Touch"); + + /// + /// "Disable taps during gameplay" + /// + public static LocalisableString DisableTapsDuringGameplay => new TranslatableString(getKey(@"disable_taps_during_gameplay"), @"Disable taps during gameplay"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs b/osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs new file mode 100644 index 0000000000..cd361bf7b8 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetKudosuRankingsRequest.cs @@ -0,0 +1,28 @@ +// 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.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class GetKudosuRankingsRequest : APIRequest + { + private readonly int page; + + public GetKudosuRankingsRequest(int page = 1) + { + this.page = page; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddParameter(@"page", page.ToString()); + + return req; + } + + protected override string Target => @"rankings/kudosu"; + } +} diff --git a/osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs b/osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs new file mode 100644 index 0000000000..4e3ade3795 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetKudosuRankingsResponse.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetKudosuRankingsResponse + { + [JsonProperty("ranking")] + public List Users = null!; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 7c4093006d..2ee66453cf 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,20 +34,15 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; - private CountryCode? countryCode; + [JsonProperty(@"country_code")] + private string countryCodeString; public CountryCode CountryCode { - get => countryCode ??= (Enum.TryParse(country?.Code, out CountryCode result) ? result : default); - set => countryCode = value; + get => Enum.TryParse(countryCodeString, out CountryCode result) ? result : CountryCode.Unknown; + set => countryCodeString = value.ToString(); } -#pragma warning disable 649 - [CanBeNull] - [JsonProperty(@"country")] - private Country country; -#pragma warning restore 649 - public readonly Bindable Status = new Bindable(); public readonly Bindable Activity = new Bindable(); diff --git a/osu.Game/Overlays/KudosuTable.cs b/osu.Game/Overlays/KudosuTable.cs new file mode 100644 index 0000000000..93884435a4 --- /dev/null +++ b/osu.Game/Overlays/KudosuTable.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Rankings.Tables; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Users; + +namespace osu.Game.Overlays +{ + public partial class KudosuTable : RankingsTable + { + public KudosuTable(int page, List users) + : base(page, users) + { + } + + protected override Drawable CreateRowBackground(APIUser item) + { + var background = base.CreateRowBackground(item); + + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 + if (!item.Active) + background.Alpha = 0.5f; + + return background; + } + + protected override Drawable[] CreateRowContent(int index, APIUser item) + { + var content = base.CreateRowContent(index, item); + + // see: https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/resources/views/multiplayer/rooms/_rankings_table.blade.php#L23 + if (!item.Active) + { + foreach (var d in content) + d.Alpha = 0.5f; + } + + return content; + } + + protected override RankingsTableColumn[] CreateAdditionalHeaders() + { + const int min_width = 120; + return new[] + { + new RankingsTableColumn(RankingsStrings.KudosuTotal, Anchor.Centre, new Dimension(GridSizeMode.AutoSize, minSize: min_width), true), + new RankingsTableColumn(RankingsStrings.KudosuAvailable, Anchor.Centre, new Dimension(GridSizeMode.AutoSize, minSize: min_width)), + new RankingsTableColumn(RankingsStrings.KudosuUsed, Anchor.Centre, new Dimension(GridSizeMode.AutoSize, minSize: min_width)), + }; + } + + protected override Drawable[] CreateAdditionalContent(APIUser item) + { + int kudosuTotal = item.Kudosu.Total; + int kudosuAvailable = item.Kudosu.Available; + return new Drawable[] + { + new RowText + { + Text = kudosuTotal.ToLocalisableString(@"N0") + }, + new ColouredRowText + { + Text = kudosuAvailable.ToLocalisableString(@"N0") + }, + new ColouredRowText + { + Text = (kudosuTotal - kudosuAvailable).ToLocalisableString(@"N0") + }, + }; + } + + protected override CountryCode GetCountryCode(APIUser item) => item.CountryCode; + + protected override Drawable CreateFlagContent(APIUser item) + { + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true)) + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + TextAnchor = Anchor.CentreLeft + }; + username.AddUserLink(item); + return username; + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsScope.cs b/osu.Game/Overlays/Rankings/RankingsScope.cs index 3392db9360..356a861764 100644 --- a/osu.Game/Overlays/Rankings/RankingsScope.cs +++ b/osu.Game/Overlays/Rankings/RankingsScope.cs @@ -18,6 +18,9 @@ namespace osu.Game.Overlays.Rankings Score, [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeCountry))] - Country + Country, + + [LocalisableDescription(typeof(RankingsStrings), nameof(RankingsStrings.TypeKudosu))] + Kudosu, } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index f25bf80b6a..6a32515cbc 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -135,6 +135,9 @@ namespace osu.Game.Overlays case RankingsScope.Score: return new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); + + case RankingsScope.Kudosu: + return new GetKudosuRankingsRequest(); } return null; @@ -166,6 +169,12 @@ namespace osu.Game.Overlays return new CountriesTable(1, countryRequest.Response.Countries); } + + case GetKudosuRankingsRequest kudosuRequest: + if (kudosuRequest.Response == null) + return null; + + return new KudosuTable(1, kudosuRequest.Response.Users); } return null; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index dfaeafbf5d..6bf06f4f98 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsCheckbox { - LabelText = MouseSettingsStrings.DisableMouseButtons, + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs index 8d1b12d5b2..175fcc4709 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -4,37 +4,43 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Input.Handlers.Touch; +using osu.Framework.Input.Handlers; using osu.Framework.Localisation; +using osu.Game.Configuration; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Input { + /// + /// Touch input settings subsection common to all touch handlers (even on different platforms). + /// public partial class TouchSettings : SettingsSubsection { - private readonly TouchHandler handler; + private readonly InputHandler handler; - public TouchSettings(TouchHandler handler) + protected override LocalisableString Header => TouchSettingsStrings.Touch; + + public TouchSettings(InputHandler handler) { this.handler = handler; } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager osuConfig) { - Children = new Drawable[] + Add(new SettingsCheckbox { - new SettingsCheckbox - { - LabelText = CommonStrings.Enabled, - Current = handler.Enabled - }, - }; + LabelText = CommonStrings.Enabled, + Current = handler.Enabled + }); + + Add(new SettingsCheckbox + { + LabelText = TouchSettingsStrings.DisableTapsDuringGameplay, + Current = osuConfig.GetBindable(OsuSetting.TouchDisableGameplayTaps) + }); } public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" }); - - protected override LocalisableString Header => handler.Description; } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 39b83ecca1..35d05b87c0 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI private void load(OsuConfigManager config) { mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); + tapsDisabled = config.GetBindable(OsuSetting.TouchDisableGameplayTaps); } #region Action mapping (for replays) @@ -124,6 +125,7 @@ namespace osu.Game.Rulesets.UI #region Setting application (disables etc.) private Bindable mouseDisabled; + private Bindable tapsDisabled; protected override bool Handle(UIEvent e) { @@ -147,9 +149,9 @@ namespace osu.Game.Rulesets.UI protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e) { - if (mouseDisabled.Value) + if (tapsDisabled.Value) { - // Only propagate positional data when mouse buttons are disabled. + // Only propagate positional data when taps are disabled. e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, e.LastPosition); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 91c3c98f01..3136faf855 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1095,6 +1095,19 @@ namespace osu.Game.Screens.Edit protected void CreateNewDifficulty(RulesetInfo rulesetInfo) { + if (isNewBeatmap) + { + dialogOverlay.Push(new SaveRequiredPopupDialog("This beatmap will be saved in order to create another difficulty.", () => + { + if (!Save()) + return; + + CreateNewDifficulty(rulesetInfo); + })); + + return; + } + if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset)) { switchToNewDifficulty(rulesetInfo, false); diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index bf2eba43c0..a0cf9f5322 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -85,7 +85,8 @@ namespace osu.Game.Screens.Menu private readonly List buttonsTopLevel = new List(); private readonly List buttonsPlay = new List(); - private Sample sampleBack; + private Sample sampleBackToLogo; + private Sample sampleLogoSwoosh; private readonly LogoTrackingContainer logoTrackingContainer; @@ -104,7 +105,7 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, @@ -127,14 +128,14 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); if (host.CanExit) buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); @@ -155,7 +156,8 @@ namespace osu.Game.Screens.Menu if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); - sampleBack = audio.Samples.Get(@"Menu/button-back-select"); + sampleBackToLogo = audio.Samples.Get(@"Menu/back-to-logo"); + sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh"); } private void onMultiplayer() @@ -197,6 +199,7 @@ namespace osu.Game.Screens.Menu { if (State == ButtonSystemState.Initial) { + StopSamplePlayback(); logo?.TriggerClick(); return true; } @@ -260,10 +263,15 @@ namespace osu.Game.Screens.Menu { case ButtonSystemState.TopLevel: State = ButtonSystemState.Initial; - sampleBack?.Play(); + + // Samples are explicitly played here in response to user interaction and not when transitioning due to idle. + StopSamplePlayback(); + sampleBackToLogo?.Play(); + return true; case ButtonSystemState.Play: + StopSamplePlayback(); backButton.TriggerClick(); return true; @@ -272,6 +280,13 @@ namespace osu.Game.Screens.Menu } } + public void StopSamplePlayback() + { + buttonsPlay.ForEach(button => button.StopSamplePlayback()); + buttonsTopLevel.ForEach(button => button.StopSamplePlayback()); + logo?.StopSamplePlayback(); + } + private bool onOsuLogo() { switch (state) @@ -346,6 +361,9 @@ namespace osu.Game.Screens.Menu logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo?.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); + + if (lastState == ButtonSystemState.TopLevel) + sampleLogoSwoosh?.Play(); break; case ButtonSystemState.TopLevel: diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 22040b4f0b..0f73707544 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -7,6 +7,8 @@ using System; using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -89,8 +91,10 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; private Container logoTarget; + private Sample reappearSampleSwoosh; + [BackgroundDependencyLoader(true)] - private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) + private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio) { holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); @@ -162,6 +166,8 @@ namespace osu.Game.Screens.Menu Buttons.OnSettings = () => settings?.ToggleVisibility(); Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); + reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh"); + preloadSongSelect(); } @@ -291,6 +297,10 @@ namespace osu.Game.Screens.Menu { base.OnResuming(e); + // Ensures any playing `ButtonSystem` samples are stopped when returning to MainMenu (as to not overlap with the 'back' sample) + Buttons.StopSamplePlayback(); + reappearSampleSwoosh?.Play(); + ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next()); // we may have consumed our preloaded instance, so let's make another. diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index c3a96e36a1..63fc34b4fb 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Menu private readonly Action clickAction; private Sample sampleClick; private Sample sampleHover; + private SampleChannel sampleChannel; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); @@ -225,7 +226,8 @@ namespace osu.Game.Screens.Menu private void trigger() { - sampleClick?.Play(); + sampleChannel = sampleClick?.GetChannel(); + sampleChannel?.Play(); clickAction?.Invoke(); @@ -237,6 +239,8 @@ namespace osu.Game.Screens.Menu public override bool HandleNonPositionalInput => state == ButtonState.Expanded; public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + public void StopSamplePlayback() => sampleChannel?.Stop(); + protected override void Update() { iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 8867ecfb2a..75ef8be02e 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Menu private readonly IntroSequence intro; private Sample sampleClick; + private SampleChannel sampleClickChannel; + private Sample sampleBeat; private Sample sampleDownbeat; @@ -391,7 +393,11 @@ namespace osu.Game.Screens.Menu flashLayer.FadeOut(1500, Easing.OutExpo); if (Action?.Invoke() == true) - sampleClick.Play(); + { + StopSamplePlayback(); + sampleClickChannel = sampleClick.GetChannel(); + sampleClickChannel.Play(); + } return true; } @@ -440,6 +446,8 @@ namespace osu.Game.Screens.Menu private Container currentProxyTarget; private Drawable proxy; + public void StopSamplePlayback() => sampleClickChannel?.Stop(); + public Drawable ProxyToContainer(Container c) { if (currentProxyTarget != null) diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index cf261ba49b..852fbd8dcc 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -10,21 +10,22 @@ namespace osu.Game.Screens.Play.PlayerSettings { public partial class InputSettings : PlayerSettingsGroup { - private readonly PlayerCheckbox mouseButtonsCheckbox; - public InputSettings() : base("Input Settings") { - Children = new Drawable[] - { - mouseButtonsCheckbox = new PlayerCheckbox - { - LabelText = MouseSettingsStrings.DisableMouseButtons - } - }; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(OsuSetting.MouseDisableButtons); + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new PlayerCheckbox + { + LabelText = MouseSettingsStrings.DisableClicksDuringGameplay, + Current = config.GetBindable(OsuSetting.MouseDisableButtons) + } + }; + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4926240831..73cd239854 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - +