mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 06:52:55 +08:00
Merge branch 'master' into improve-alternate-after-break
This commit is contained in:
commit
bba7722837
@ -191,4 +191,5 @@ dotnet_diagnostic.CA2225.severity = none
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
||||
dotnet_diagnostic.OLOC001.words_in_name = 5
|
||||
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||
|
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.428.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.430.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModStrictTracking) };
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModStrictTracking)).ToArray();
|
||||
|
||||
[SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")]
|
||||
public Bindable<bool> NoSliderHeadAccuracy { get; } = new BindableBool(true);
|
||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Description => "It never gets boring!";
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
||||
|
||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||
|
||||
private Random? rng;
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Automation;
|
||||
public override string Description => @"Spinners will be automatically completed.";
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTarget) };
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override string Description => @"Follow circles just got serious...";
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModClassic) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) };
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
|
@ -42,7 +42,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override string Description => @"Practice keeping up with the beat of the song.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSuddenDeath) };
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
typeof(IRequiresApproachCircles),
|
||||
typeof(OsuModRandom),
|
||||
typeof(OsuModSpunOut),
|
||||
typeof(OsuModStrictTracking),
|
||||
typeof(OsuModSuddenDeath)
|
||||
}).ToArray();
|
||||
|
||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
||||
public Bindable<int?> Seed { get; } = new Bindable<int?>
|
||||
|
@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
ClassicDefault = false,
|
||||
LabelText = "Snaking out sliders",
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
|
||||
},
|
||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
|
||||
AddStep("start gameplay", () => ((IMultiplayerClient)MultiplayerClient).MatchStarted());
|
||||
AddStep("start gameplay", () => ((IMultiplayerClient)MultiplayerClient).GameplayStarted());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[Ignore("Only for visual testing")]
|
||||
public class TestSceneBundledBeatmapDownloader : OsuTestScene
|
||||
{
|
||||
private BundledBeatmapDownloader downloader;
|
||||
|
||||
[Test]
|
||||
public void TestDownloader()
|
||||
{
|
||||
AddStep("Create downloader", () =>
|
||||
{
|
||||
downloader?.Expire();
|
||||
Add(downloader = new BundledBeatmapDownloader(false));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +1,99 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public class TestSceneSongSelectFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
public TestSceneSongSelectFooter()
|
||||
{
|
||||
AddStep("Create footer", () =>
|
||||
{
|
||||
Footer footer;
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
footer = new Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
private FooterButtonRandom randomButton;
|
||||
|
||||
footer.AddButton(new FooterButtonMods(), null);
|
||||
footer.AddButton(new FooterButtonRandom
|
||||
{
|
||||
NextRandom = () => { },
|
||||
PreviousRandom = () => { },
|
||||
}, null);
|
||||
footer.AddButton(new FooterButtonOptions(), null);
|
||||
private bool nextRandomCalled;
|
||||
private bool previousRandomCalled;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
nextRandomCalled = false;
|
||||
previousRandomCalled = false;
|
||||
|
||||
Footer footer;
|
||||
|
||||
Child = footer = new Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
footer.AddButton(new FooterButtonMods(), null);
|
||||
footer.AddButton(randomButton = new FooterButtonRandom
|
||||
{
|
||||
NextRandom = () => nextRandomCalled = true,
|
||||
PreviousRandom = () => previousRandomCalled = true,
|
||||
}, null);
|
||||
footer.AddButton(new FooterButtonOptions(), null);
|
||||
|
||||
InputManager.MoveMouseTo(Vector2.Zero);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestFooterRandom()
|
||||
{
|
||||
AddStep("press F2", () => InputManager.Key(Key.F2));
|
||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRandomViaMouse()
|
||||
{
|
||||
AddStep("click button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(randomButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRewind()
|
||||
{
|
||||
AddStep("press Shift+F2", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.PressKey(Key.F2);
|
||||
InputManager.ReleaseKey(Key.F2);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRewindViaShiftMouseLeft()
|
||||
{
|
||||
AddStep("shift + click button", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.MoveMouseTo(randomButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRewindViaMouseRight()
|
||||
{
|
||||
AddStep("right click button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(randomButton);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.FirstRunSetup;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneFirstRunScreenBehaviour : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
public TestSceneFirstRunScreenBehaviour()
|
||||
{
|
||||
AddStep("load screen", () =>
|
||||
{
|
||||
Child = new ScreenStack(new ScreenBehaviour());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.FirstRunSetup;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneFirstRunScreenBundledBeatmaps : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
public TestSceneFirstRunScreenBundledBeatmaps()
|
||||
{
|
||||
AddStep("load screen", () =>
|
||||
{
|
||||
Child = new ScreenStack(new ScreenBeatmaps());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -65,6 +65,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("Enable when first run setup is being displayed on first run.")]
|
||||
public void TestDoesntOpenOnSecondRun()
|
||||
@ -178,7 +184,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("step to next", () => overlay.NextButton.TriggerClick());
|
||||
|
||||
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale);
|
||||
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenBeatmaps);
|
||||
|
||||
AddStep("hide", () => overlay.Hide());
|
||||
AddAssert("overlay hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
@ -188,7 +194,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("run notification action", () => lastNotification.Activated());
|
||||
|
||||
AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible);
|
||||
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
|
||||
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenBeatmaps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,33 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
&& SelectedMods.Value.Any(mod => mod.GetType() == typeof(OsuModMirror)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDimmedState()
|
||||
{
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddUntilStep("any column dimmed", () => this.ChildrenOfType<ModColumn>().Any(column => !column.Active.Value));
|
||||
|
||||
ModColumn lastColumn = null;
|
||||
|
||||
AddAssert("last column dimmed", () => !this.ChildrenOfType<ModColumn>().Last().Active.Value);
|
||||
AddStep("request scroll to last column", () =>
|
||||
{
|
||||
var lastDimContainer = this.ChildrenOfType<ModSelectScreen.ColumnDimContainer>().Last();
|
||||
lastColumn = lastDimContainer.Column;
|
||||
lastDimContainer.RequestScroll?.Invoke(lastDimContainer);
|
||||
});
|
||||
AddUntilStep("column undimmed", () => lastColumn.Active.Value);
|
||||
|
||||
AddStep("click panel", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(lastColumn.ChildrenOfType<ModPanel>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("panel selected", () => lastColumn.ChildrenOfType<ModPanel>().First().Active.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomisationToggleState()
|
||||
{
|
||||
|
@ -1,3 +1,4 @@
|
||||
[*.cs]
|
||||
dotnet_diagnostic.OLOC001.words_in_name = 5
|
||||
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
|
||||
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||
|
339
osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
Normal file
339
osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
Normal file
@ -0,0 +1,339 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BundledBeatmapDownloader : CompositeDrawable
|
||||
{
|
||||
private readonly bool shouldPostNotifications;
|
||||
|
||||
public IEnumerable<BeatmapDownloadTracker> DownloadTrackers => downloadTrackers;
|
||||
|
||||
private readonly List<BeatmapDownloadTracker> downloadTrackers = new List<BeatmapDownloadTracker>();
|
||||
|
||||
private readonly List<string> downloadableFilenames = new List<string>();
|
||||
|
||||
private BundledBeatmapModelDownloader beatmapDownloader;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new beatmap downloader.
|
||||
/// </summary>
|
||||
/// <param name="onlyTutorial">Whether only the tutorial should be downloaded, instead of bundled beatmaps.</param>
|
||||
/// <param name="shouldPostNotifications">Whether downloads should create tracking notifications.</param>
|
||||
public BundledBeatmapDownloader(bool onlyTutorial, bool shouldPostNotifications = false)
|
||||
{
|
||||
this.shouldPostNotifications = shouldPostNotifications;
|
||||
|
||||
if (onlyTutorial)
|
||||
{
|
||||
queueDownloads(new[] { tutorial_filename });
|
||||
}
|
||||
else
|
||||
{
|
||||
queueDownloads(always_bundled_beatmaps);
|
||||
|
||||
queueDownloads(bundled_osu, 8);
|
||||
queueDownloads(bundled_taiko, 3);
|
||||
queueDownloads(bundled_catch, 3);
|
||||
queueDownloads(bundled_mania, 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var localDependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
localDependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new BundledBeatmapModelDownloader(parent.Get<BeatmapManager>(), parent.Get<IAPIProvider>()));
|
||||
|
||||
if (shouldPostNotifications && parent.Get<INotificationOverlay>() is INotificationOverlay notifications)
|
||||
beatmapDownloader.PostNotification = notifications.Post;
|
||||
|
||||
return localDependencies;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
foreach (string filename in downloadableFilenames)
|
||||
{
|
||||
var match = Regex.Match(filename, @"([0-9]*) (.*) - (.*)\.osz");
|
||||
|
||||
var beatmapSet = new APIBeatmapSet
|
||||
{
|
||||
OnlineID = int.Parse(match.Groups[1].Value),
|
||||
Artist = match.Groups[2].Value,
|
||||
Title = match.Groups[3].Value,
|
||||
};
|
||||
|
||||
var beatmapDownloadTracker = new BeatmapDownloadTracker(beatmapSet);
|
||||
downloadTrackers.Add(beatmapDownloadTracker);
|
||||
AddInternal(beatmapDownloadTracker);
|
||||
|
||||
beatmapDownloader.Download(beatmapSet);
|
||||
}
|
||||
}
|
||||
|
||||
private void queueDownloads(string[] sourceFilenames, int? limit = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps.
|
||||
var random = new LegacyRandom(DateTime.UtcNow.Year * 1000 + (DateTime.UtcNow.DayOfYear / 7));
|
||||
|
||||
downloadableFilenames.AddRange(sourceFilenames.OrderBy(x => random.NextDouble()).Take(limit ?? int.MaxValue));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private class BundledBeatmapModelDownloader : BeatmapModelDownloader
|
||||
{
|
||||
public BundledBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api)
|
||||
: base(beatmapImporter, api)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize)
|
||||
=> new BundledBeatmapDownloadRequest(set, minimiseDownloadSize);
|
||||
|
||||
public class BundledBeatmapDownloadRequest : DownloadBeatmapSetRequest
|
||||
{
|
||||
protected override string Uri => $"https://assets.ppy.sh/client-resources/bundled/{Model.OnlineID}.osz";
|
||||
|
||||
public BundledBeatmapDownloadRequest(IBeatmapSetInfo beatmapSetInfo, bool minimiseDownloadSize)
|
||||
: base(beatmapSetInfo, minimiseDownloadSize)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string tutorial_filename = "1011011 nekodex - new beginnings.osz";
|
||||
|
||||
/// <summary>
|
||||
/// Contest winners or other special cases.
|
||||
/// </summary>
|
||||
private static readonly string[] always_bundled_beatmaps =
|
||||
{
|
||||
// This thing is 40mb, I'm not sure we want it here...
|
||||
@"1388906 Raphlesia & BilliumMoto - My Love.osz"
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_osu =
|
||||
{
|
||||
"682286 Yuyoyuppe - Emerald Galaxy.osz",
|
||||
"682287 baker - For a Dead Girl+.osz",
|
||||
"682289 Hige Driver - I Wanna Feel Your Love (feat. shully).osz",
|
||||
"682290 Hige Driver - Miracle Sugite Yabai (feat. shully).osz",
|
||||
"682416 Hige Driver - Palette.osz",
|
||||
"682595 baker - Kimi ga Kimi ga -vocanico remix-.osz",
|
||||
"716211 yuki. - Spring Signal.osz",
|
||||
"716213 dark cat - BUBBLE TEA (feat. juu & cinders).osz",
|
||||
"716215 LukHash - CLONED.osz",
|
||||
"716219 IAHN - Snowdrop.osz",
|
||||
"716249 *namirin - Senaka Awase no Kuukyo (with Kakichoco).osz",
|
||||
"716390 sakuraburst - SHA.osz",
|
||||
"716441 Fractal Dreamers - Paradigm Shift.osz",
|
||||
"729808 Thaehan - Leprechaun.osz",
|
||||
"751771 Cranky - Hanaarashi.osz",
|
||||
"751772 Cranky - Ran.osz",
|
||||
"751773 Cranky - Feline, the White....osz",
|
||||
"751774 Function Phantom - Variable.osz",
|
||||
"751779 Rin - Daishibyo set 14 ~ Sado no Futatsuiwa.osz",
|
||||
"751782 Fractal Dreamers - Fata Morgana.osz",
|
||||
"751785 Cranky - Chandelier - King.osz",
|
||||
"751846 Fractal Dreamers - Celestial Horizon.osz",
|
||||
"751866 Rin - Moriya set 08 ReEdit ~ Youkai no Yama.osz",
|
||||
"751894 Fractal Dreamers - Blue Haven.osz",
|
||||
"751896 Cranky - Rave 2 Rave.osz",
|
||||
"751932 Cranky - La fuite des jours.osz",
|
||||
"751972 Cranky - CHASER.osz",
|
||||
"779173 Thaehan - Superpower.osz",
|
||||
"780932 VINXIS - A Centralized View.osz",
|
||||
"785572 S3RL - I'll See You Again (feat. Chi Chi).osz",
|
||||
"785650 yuki. feat. setsunan - Hello! World.osz",
|
||||
"785677 Dictate - Militant.osz",
|
||||
"785731 S3RL - Catchit (Radio Edit).osz",
|
||||
"785774 LukHash - GLITCH.osz",
|
||||
"786498 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz",
|
||||
"789374 Pulse - LP.osz",
|
||||
"789528 James Portland - Sky.osz",
|
||||
"789529 Lexurus - Gravity.osz",
|
||||
"789544 Andromedik - Invasion.osz",
|
||||
"789905 Gourski x Himmes - Silence.osz",
|
||||
"791667 cYsmix - Babaroque (Short Ver.).osz",
|
||||
"791798 cYsmix - Behind the Walls.osz",
|
||||
"791845 cYsmix - Little Knight.osz",
|
||||
"792241 cYsmix - Eden.osz",
|
||||
"792396 cYsmix - The Ballad of a Mindless Girl.osz",
|
||||
"795432 Phonetic - Journey.osz",
|
||||
"831322 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz",
|
||||
"847764 Cranky - Crocus.osz",
|
||||
"847776 Culprate & Joe Ford - Gaucho.osz",
|
||||
"847812 J. Pachelbel - Canon (Cranky Remix).osz",
|
||||
"847900 Cranky - Time Alter.osz",
|
||||
"847930 LukHash - 8BIT FAIRY TALE.osz",
|
||||
"848003 Culprate - Aurora.osz",
|
||||
"848068 nanobii - popsicle beach.osz",
|
||||
"848090 Trial & Error - DAI*TAN SENSATION feat. Nanahira, Mii, Aitsuki Nakuru (Short Ver.).osz",
|
||||
"848259 Culprate & Skorpion - Jester.osz",
|
||||
"848976 Dictate - Treason.osz",
|
||||
"851543 Culprate - Florn.osz",
|
||||
"864748 Thaehan - Angry Birds Epic (Remix).osz",
|
||||
"873667 OISHII - ONIGIRI FREEWAY.osz",
|
||||
"876227 Culprate, Keota & Sophie Meiers - Mechanic Heartbeat.osz",
|
||||
"880487 cYsmix - Peer Gynt.osz",
|
||||
"883088 Wisp X - Somewhere I'd Rather Be.osz",
|
||||
"891333 HyuN - White Aura.osz",
|
||||
"891334 HyuN - Wild Card.osz",
|
||||
"891337 HyuN feat. LyuU - Cross Over.osz",
|
||||
"891338 HyuN & Ritoru - Apocalypse in Love.osz",
|
||||
"891339 HyuN feat. Ato - Asu wa Ame ga Yamukara.osz",
|
||||
"891345 HyuN - Infinity Heaven.osz",
|
||||
"891348 HyuN - Guitian.osz",
|
||||
"891356 HyuN - Legend of Genesis.osz",
|
||||
"891366 HyuN - Illusion of Inflict.osz",
|
||||
"891417 HyuN feat. Yu-A - My life is for you.osz",
|
||||
"891441 HyuN - You'Re aRleAdY dEAd.osz",
|
||||
"891632 HyuN feat. YURI - Disorder.osz",
|
||||
"891712 HyuN - Tokyo's Starlight.osz",
|
||||
"901091 *namirin - Ciel etoile.osz",
|
||||
"916990 *namirin - Koishiteiku Planet.osz",
|
||||
"929284 tieff - Sense of Nostalgia.osz",
|
||||
"933940 Ben Briggs - Yes (Maybe).osz",
|
||||
"934415 Ben Briggs - Fearless Living.osz",
|
||||
"934627 Ben Briggs - New Game Plus.osz",
|
||||
"934666 Ben Briggs - Wave Island.osz",
|
||||
"936126 siromaru + cranky - conflict.osz",
|
||||
"940377 onumi - ARROGANCE.osz",
|
||||
"940597 tieff - Take Your Swimsuit.osz",
|
||||
"941085 tieff - Our Story.osz",
|
||||
"949297 tieff - Sunflower.osz",
|
||||
"952380 Ben Briggs - Why Are We Yelling.osz",
|
||||
"954272 *namirin - Kanzen Shouri*Esper Girl.osz",
|
||||
"955866 KIRA & Heartbreaker - B.B.F (feat. Hatsune Miku & Kagamine Rin).osz",
|
||||
"961320 Kuba Oms - All In All.osz",
|
||||
"964553 The Flashbulb - You Take the World's Weight Away.osz",
|
||||
"965651 Fractal Dreamers - Ad Astra.osz",
|
||||
"966225 The Flashbulb - Passage D.osz",
|
||||
"966324 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz",
|
||||
"972810 James Landino & Kabuki - Birdsong.osz",
|
||||
"972932 James Landino - Hide And Seek.osz",
|
||||
"977276 The Flashbulb - Mellann.osz",
|
||||
"981616 *namirin - Mizutamari Tobikoete (with Nanahira).osz",
|
||||
"985788 Loki - Wizard's Tower.osz",
|
||||
"996628 OISHII - ONIGIRI FREEWAY.osz",
|
||||
"996898 HyuN - White Aura.osz",
|
||||
"1003554 yuki. - Nadeshiko Sensation.osz",
|
||||
"1014936 Thaehan - Bwa !.osz",
|
||||
"1019827 UNDEAD CORPORATION - Sad Dream.osz",
|
||||
"1020213 Creo - Idolize.osz",
|
||||
"1021450 Thaehan - Chiptune & Baroque.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_taiko =
|
||||
{
|
||||
"707824 Fractal Dreamers - Fortuna Redux.osz",
|
||||
"789553 Cranky - Ran.osz",
|
||||
"827822 Function Phantom - Neuronecia.osz",
|
||||
"847323 Nakanojojo - Bittersweet (feat. Kuishinboakachan a.k.a Kiato).osz",
|
||||
"847433 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz",
|
||||
"847576 dark cat - hot chocolate.osz",
|
||||
"847957 Wisp X - Final Moments.osz",
|
||||
"876282 VINXIS - Greetings.osz",
|
||||
"876648 Thaehan - Angry Birds Epic (Remix).osz",
|
||||
"877069 IAHN - Transform (Original Mix).osz",
|
||||
"877496 Thaehan - Leprechaun.osz",
|
||||
"877935 Thaehan - Overpowered.osz",
|
||||
"878344 yuki. - Be Your Light.osz",
|
||||
"918446 VINXIS - Facade.osz",
|
||||
"918903 LukHash - Ghosts.osz",
|
||||
"919251 *namirin - Hitokoto no Kyori.osz",
|
||||
"919704 S3RL - I Will Pick You Up (feat. Tamika).osz",
|
||||
"921535 SOOOO - Raven Haven.osz",
|
||||
"927206 *namirin - Kanzen Shouri*Esper Girl.osz",
|
||||
"927544 Camellia feat. Nanahira - Kansoku Eisei.osz",
|
||||
"930806 Nakanojojo - Pararara (feat. Amekoya).osz",
|
||||
"931741 Camellia - Quaoar.osz",
|
||||
"935699 Rin - Mythic set ~ Heart-Stirring Urban Legends.osz",
|
||||
"935732 Thaehan - Yuujou.osz",
|
||||
"941145 Function Phantom - Euclid.osz",
|
||||
"942334 Dictate - Cauldron.osz",
|
||||
"946540 nanobii - astral blast.osz",
|
||||
"948844 Rin - Kishinjou set 01 ~ Mist Lake.osz",
|
||||
"949122 Wisp X - Petal.osz",
|
||||
"951618 Rin - Kishinjou set 02 ~ Mermaid from the Uncharted Land.osz",
|
||||
"957412 Rin - Lunatic set 16 ~ The Space Shrine Maiden Returns Home.osz",
|
||||
"961335 Thaehan - Insert Coin.osz",
|
||||
"965178 The Flashbulb - DIDJ PVC.osz",
|
||||
"966087 The Flashbulb - Creep.osz",
|
||||
"966277 The Flashbulb - Amen Iraq.osz",
|
||||
"966407 LukHash - ROOM 12.osz",
|
||||
"966451 The Flashbulb - Six Acid Strings.osz",
|
||||
"972301 BilliumMoto - four veiled stars.osz",
|
||||
"973173 nanobii - popsicle beach.osz",
|
||||
"973954 BilliumMoto - Rocky Buinne (Short Ver.).osz",
|
||||
"975435 BilliumMoto - life flashes before weeb eyes.osz",
|
||||
"978759 L. V. Beethoven - Moonlight Sonata (Cranky Remix).osz",
|
||||
"982559 BilliumMoto - HDHR.osz",
|
||||
"984361 The Flashbulb - Ninedump.osz",
|
||||
"1023681 Inferi - The Ruin of Mankind.osz",
|
||||
"1034358 ALEPH - The Evil Spirit.osz",
|
||||
"1037567 ALEPH - Scintillations.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_catch =
|
||||
{
|
||||
"554256 Helblinde - When Time Sleeps.osz",
|
||||
"693123 yuki. - Nadeshiko Sensation.osz",
|
||||
"767009 OISHII - PIZZA PLAZA.osz",
|
||||
"767346 Thaehan - Bwa !.osz",
|
||||
"815162 VINXIS - Greetings.osz",
|
||||
"840964 cYsmix - Breeze.osz",
|
||||
"932657 Wisp X - Eventide.osz",
|
||||
"933700 onumi - CONFUSION PART ONE.osz",
|
||||
"933984 onumi - PERSONALITY.osz",
|
||||
"934785 onumi - FAKE.osz",
|
||||
"936545 onumi - REGRET PART ONE.osz",
|
||||
"943803 Fractal Dreamers - Everything for a Dream.osz",
|
||||
"943876 S3RL - I Will Pick You Up (feat. Tamika).osz",
|
||||
"946773 Trial & Error - DREAMING COLOR (Short Ver.).osz",
|
||||
"955808 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI (Short Ver.).osz",
|
||||
"957808 Fractal Dreamers - Module_410.osz",
|
||||
"957842 antiPLUR - One Life Left to Live.osz",
|
||||
"965730 The Flashbulb - Lawn Wake IV (Black).osz",
|
||||
"966240 Creo - Challenger.osz",
|
||||
"968232 Rin - Lunatic set 15 ~ The Moon as Seen from the Shrine.osz",
|
||||
"972302 VINXIS - A Centralized View.osz",
|
||||
"972887 HyuN - Illusion of Inflict.osz",
|
||||
"1008600 LukHash - WHEN AN ANGEL DIES.osz",
|
||||
"1032103 LukHash - H8 U.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_mania =
|
||||
{
|
||||
"943516 antiPLUR - Clockwork Spooks.osz",
|
||||
"946394 VINXIS - Three Times The Original Charm.osz",
|
||||
"966408 antiPLUR - One Life Left to Live.osz",
|
||||
"971561 antiPLUR - Runengon.osz",
|
||||
"983864 James Landino - Shiba Island.osz",
|
||||
"989512 BilliumMoto - 1xMISS.osz",
|
||||
"994104 James Landino - Reaction feat. Slyleaf.osz",
|
||||
"1003217 nekodex - circles!.osz",
|
||||
"1009907 James Landino & Kabuki - Birdsong.osz",
|
||||
"1015169 Thaehan - Insert Coin.osz",
|
||||
};
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -57,6 +58,26 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls a <see cref="Drawable"/> into view.
|
||||
/// </summary>
|
||||
/// <param name="d">The <see cref="Drawable"/> to scroll into view.</param>
|
||||
/// <param name="animated">Whether to animate the movement.</param>
|
||||
/// <param name="extraScroll">An added amount to scroll beyond the requirement to bring the target into view.</param>
|
||||
public void ScrollIntoView(Drawable d, bool animated = true, float extraScroll = 0)
|
||||
{
|
||||
float childPos0 = GetChildPosInContent(d);
|
||||
float childPos1 = GetChildPosInContent(d, d.DrawSize);
|
||||
|
||||
float minPos = Math.Min(childPos0, childPos1);
|
||||
float maxPos = Math.Max(childPos0, childPos1);
|
||||
|
||||
if (minPos < Current || (minPos > Current && d.DrawSize[ScrollDim] > DisplayableContent))
|
||||
ScrollTo(minPos - extraScroll, animated);
|
||||
else if (maxPos > Current + DisplayableContent)
|
||||
ScrollTo(maxPos - DisplayableContent + extraScroll, animated);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (shouldPerformRightMouseScroll(e))
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@ -28,7 +29,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||
{
|
||||
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
|
||||
if (BackgroundColour == Color4.White)
|
||||
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
54
osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs
Normal file
54
osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class FirstRunSetupBeatmapScreenStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.FirstRunSetupBeatmapScreen";
|
||||
|
||||
/// <summary>
|
||||
/// "Obtaining Beatmaps"
|
||||
/// </summary>
|
||||
public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Obtaining Beatmaps");
|
||||
|
||||
/// <summary>
|
||||
/// ""Beatmaps" are what we call playable levels. osu! doesn't come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection."
|
||||
/// </summary>
|
||||
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"""Beatmaps"" are what we call playable levels. osu! doesn't come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection.");
|
||||
|
||||
/// <summary>
|
||||
/// "If you are a new player, we recommend playing through the tutorial to get accustomed to the gameplay."
|
||||
/// </summary>
|
||||
public static LocalisableString TutorialDescription => new TranslatableString(getKey(@"tutorial_description"), @"If you are a new player, we recommend playing through the tutorial to get accustomed to the gameplay.");
|
||||
|
||||
/// <summary>
|
||||
/// "Get the osu! tutorial"
|
||||
/// </summary>
|
||||
public static LocalisableString TutorialButton => new TranslatableString(getKey(@"tutorial_button"), @"Get the osu! tutorial");
|
||||
|
||||
/// <summary>
|
||||
/// "To get you started, we have some recommended beatmaps."
|
||||
/// </summary>
|
||||
public static LocalisableString BundledDescription => new TranslatableString(getKey(@"bundled_description"), @"To get you started, we have some recommended beatmaps.");
|
||||
|
||||
/// <summary>
|
||||
/// "Get recommended beatmaps"
|
||||
/// </summary>
|
||||
public static LocalisableString BundledButton => new TranslatableString(getKey(@"bundled_button"), @"Get recommended beatmaps");
|
||||
|
||||
/// <summary>
|
||||
/// "You can also obtain more beatmaps from the main menu "browse" button at any time."
|
||||
/// </summary>
|
||||
public static LocalisableString ObtainMoreBeatmaps => new TranslatableString(getKey(@"obtain_more_beatmaps"), @"You can also obtain more beatmaps from the main menu ""browse"" button at any time.");
|
||||
|
||||
/// <summary>
|
||||
/// "You currently have {0} beatmap(s) loaded!"
|
||||
/// </summary>
|
||||
public static LocalisableString CurrentlyLoadedBeatmaps(int beatmaps) => new TranslatableString(getKey(@"currently_loaded_beatmaps"), @"You currently have {0} beatmap(s) loaded!", beatmaps);
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@ namespace osu.Game.Localisation
|
||||
/// <summary>
|
||||
/// "Click to resume first-run setup at any point"
|
||||
/// </summary>
|
||||
public static LocalisableString ClickToResumeFirstRunSetupAtAnyPoint => new TranslatableString(getKey(@"click_to_resume_first_run_setup_at_any_point"), @"Click to resume first-run setup at any point");
|
||||
public static LocalisableString ClickToResumeFirstRunSetupAtAnyPoint =>
|
||||
new TranslatableString(getKey(@"click_to_resume_first_run_setup_at_any_point"), @"Click to resume first-run setup at any point");
|
||||
|
||||
/// <summary>
|
||||
/// "First-run setup"
|
||||
@ -48,6 +49,31 @@ osu! is a very configurable game, and diving straight into the settings can some
|
||||
/// </summary>
|
||||
public static LocalisableString UIScaleDescription => new TranslatableString(getKey(@"ui_scale_description"), @"The size of the osu! user interface can be adjusted to your liking.");
|
||||
|
||||
/// <summary>
|
||||
/// "Behaviour"
|
||||
/// </summary>
|
||||
public static LocalisableString Behaviour => new TranslatableString(getKey(@"behaviour"), @"Behaviour");
|
||||
|
||||
/// <summary>
|
||||
/// "Some new defaults for game behaviours have been implemented, with the aim of improving the game experience and making it more accessible to everyone.
|
||||
///
|
||||
/// We recommend you give the new defaults a try, but if you'd like to have things feel more like classic versions of osu!, you can easily apply some sane defaults below."
|
||||
/// </summary>
|
||||
public static LocalisableString BehaviourDescription => new TranslatableString(getKey(@"behaviour_description"),
|
||||
@"Some new defaults for game behaviours have been implemented, with the aim of improving the game experience and making it more accessible to everyone.
|
||||
|
||||
We recommend you give the new defaults a try, but if you'd like to have things feel more like classic versions of osu!, you can easily apply some sane defaults below.");
|
||||
|
||||
/// <summary>
|
||||
/// "New defaults"
|
||||
/// </summary>
|
||||
public static LocalisableString NewDefaults => new TranslatableString(getKey(@"new_defaults"), @"New defaults");
|
||||
|
||||
/// <summary>
|
||||
/// "Classic defaults"
|
||||
/// </summary>
|
||||
public static LocalisableString ClassicDefaults => new TranslatableString(getKey(@"classic_defaults"), @"Classic defaults");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
17
osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs
Normal file
17
osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using MessagePack;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="MultiplayerCountdown"/> started by the server when clients being to load.
|
||||
/// Indicates how long until gameplay will forcefully start, excluding any users which have not completed loading,
|
||||
/// and forcing progression of any clients that are blocking load due to user interaction.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class ForceGameplayStartCountdown : MultiplayerCountdown
|
||||
{
|
||||
}
|
||||
}
|
@ -93,14 +93,20 @@ namespace osu.Game.Online.Multiplayer
|
||||
Task UserModsChanged(int userId, IEnumerable<APIMod> mods);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point.
|
||||
/// Signals that the match is starting and the loading of gameplay should be started. This will *only* be sent to clients which are to begin loading at this point.
|
||||
/// </summary>
|
||||
Task LoadRequested();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a match has started. All users in the <see cref="MultiplayerUserState.Loaded"/> state should begin gameplay as soon as possible.
|
||||
/// Signals that loading of gameplay is to be aborted.
|
||||
/// </summary>
|
||||
Task MatchStarted();
|
||||
Task LoadAborted();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that gameplay has started.
|
||||
/// All users in the <see cref="MultiplayerUserState.Loaded"/> or <see cref="MultiplayerUserState.ReadyForGameplay"/> states should begin gameplay as soon as possible.
|
||||
/// </summary>
|
||||
Task GameplayStarted();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the match has ended, all players have finished and results are ready to be displayed.
|
||||
|
@ -69,10 +69,15 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
public virtual event Action? LoadRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the multiplayer server requests loading of play to be aborted.
|
||||
/// </summary>
|
||||
public event Action? LoadAborted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the multiplayer server requests gameplay to be started.
|
||||
/// </summary>
|
||||
public event Action? MatchStarted;
|
||||
public event Action? GameplayStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the multiplayer server has finished collating results.
|
||||
@ -604,14 +609,27 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.MatchStarted()
|
||||
Task IMultiplayerClient.LoadAborted()
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
MatchStarted?.Invoke();
|
||||
LoadAborted?.Invoke();
|
||||
}, false);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.GameplayStarted()
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
GameplayStarted?.Invoke();
|
||||
}, false);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
[Union(1, typeof(ForceGameplayStartCountdown))]
|
||||
public abstract class MultiplayerCountdown
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -65,5 +65,21 @@ namespace osu.Game.Online.Multiplayer
|
||||
}
|
||||
|
||||
public override int GetHashCode() => UserID.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this user has finished loading and can start gameplay.
|
||||
/// </summary>
|
||||
public bool CanStartGameplay()
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case MultiplayerUserState.Loaded:
|
||||
case MultiplayerUserState.ReadyForGameplay:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,16 @@ namespace osu.Game.Online.Multiplayer
|
||||
WaitingForLoad,
|
||||
|
||||
/// <summary>
|
||||
/// The user's client has marked itself as loaded and ready to begin gameplay.
|
||||
/// The user has marked itself as loaded, but may still be adjusting settings prior to being ready for gameplay.
|
||||
/// Players remaining in this state for an extended period of time will be automatically transitioned to the <see cref="Playing"/> state by the server.
|
||||
/// </summary>
|
||||
Loaded,
|
||||
|
||||
/// <summary>
|
||||
/// The user has finished adjusting settings and is ready to start gameplay.
|
||||
/// </summary>
|
||||
ReadyForGameplay,
|
||||
|
||||
/// <summary>
|
||||
/// The user is currently playing in a game. This is a reserved state, and is set by the server.
|
||||
/// </summary>
|
||||
|
@ -54,7 +54,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<MultiplayerRoomSettings>(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
|
||||
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
|
||||
connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested);
|
||||
connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted);
|
||||
connection.On(nameof(IMultiplayerClient.GameplayStarted), ((IMultiplayerClient)this).GameplayStarted);
|
||||
connection.On(nameof(IMultiplayerClient.LoadAborted), ((IMultiplayerClient)this).LoadAborted);
|
||||
connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady);
|
||||
connection.On<int, IEnumerable<APIMod>>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged);
|
||||
connection.On<int, BeatmapAvailability>(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged);
|
||||
|
@ -10,8 +10,8 @@ namespace osu.Game.Online
|
||||
WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh";
|
||||
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
APIClientID = "5";
|
||||
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";
|
||||
MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer";
|
||||
SpectatorEndpointUrl = "https://spectator2.ppy.sh/spectator";
|
||||
MultiplayerEndpointUrl = "https://spectator2.ppy.sh/multiplayer";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ namespace osu.Game.Online
|
||||
(typeof(CountdownChangedEvent), typeof(MatchServerEvent)),
|
||||
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
|
||||
(typeof(TeamVersusUserState), typeof(MatchUserState)),
|
||||
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown))
|
||||
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
|
||||
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,11 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
|
||||
protected FillFlowContainer Content { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
protected OverlayColourProvider OverlayColourProvider { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider overlayColourProvider)
|
||||
private void load()
|
||||
{
|
||||
const float header_size = 40;
|
||||
const float spacing = 20;
|
||||
@ -30,13 +33,14 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = this.GetLocalisableDescription(),
|
||||
Font = OsuFont.Default.With(size: header_size),
|
||||
Colour = overlayColourProvider.Light1,
|
||||
Colour = OverlayColourProvider.Light1,
|
||||
},
|
||||
Content = new FillFlowContainer
|
||||
{
|
||||
|
258
osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs
Normal file
258
osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs
Normal file
@ -0,0 +1,258 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
[LocalisableDescription(typeof(FirstRunSetupBeatmapScreenStrings), nameof(FirstRunSetupBeatmapScreenStrings.Header))]
|
||||
public class ScreenBeatmaps : FirstRunSetupScreen
|
||||
{
|
||||
private ProgressRoundedButton downloadBundledButton = null!;
|
||||
private ProgressRoundedButton importBeatmapsButton = null!;
|
||||
private ProgressRoundedButton downloadTutorialButton = null!;
|
||||
|
||||
private OsuTextFlowContainer currentlyLoadedBeatmaps = null!;
|
||||
|
||||
private BundledBeatmapDownloader? tutorialDownloader;
|
||||
private BundledBeatmapDownloader? bundledDownloader;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realmAccess { get; set; } = null!;
|
||||
|
||||
private IDisposable? beatmapSubscription;
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(LegacyImportManager? legacyImportManager)
|
||||
{
|
||||
Vector2 buttonSize = new Vector2(500, 60);
|
||||
|
||||
Content.Children = new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.Description,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 30,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
currentlyLoadedBeatmaps = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24, weight: FontWeight.SemiBold))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content2,
|
||||
TextAnchor = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.TutorialDescription,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
downloadTutorialButton = new ProgressRoundedButton
|
||||
{
|
||||
Size = buttonSize,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
BackgroundColour = colours.Pink3,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.TutorialButton,
|
||||
Action = downloadTutorial
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.BundledDescription,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
downloadBundledButton = new ProgressRoundedButton
|
||||
{
|
||||
Size = buttonSize,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
BackgroundColour = colours.Blue3,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.BundledButton,
|
||||
Action = downloadBundled
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = "If you have an existing osu! install, you can also choose to import your existing beatmap collection.",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
importBeatmapsButton = new ProgressRoundedButton
|
||||
{
|
||||
Size = buttonSize,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
BackgroundColour = colours.Blue3,
|
||||
Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable,
|
||||
Action = () =>
|
||||
{
|
||||
importBeatmapsButton.Enabled.Value = false;
|
||||
legacyImportManager?.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
importBeatmapsButton.Complete();
|
||||
else
|
||||
importBeatmapsButton.Enabled.Value = true;
|
||||
}));
|
||||
}
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.ObtainMoreBeatmaps,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
beatmapSubscription = realmAccess.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected), beatmapsChanged);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
beatmapSubscription?.Dispose();
|
||||
}
|
||||
|
||||
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error)
|
||||
{
|
||||
currentlyLoadedBeatmaps.Text = FirstRunSetupBeatmapScreenStrings.CurrentlyLoadedBeatmaps(sender.Count);
|
||||
|
||||
if (sender.Count == 0)
|
||||
{
|
||||
currentlyLoadedBeatmaps.FadeColour(colours.Red1, 500, Easing.OutQuint);
|
||||
}
|
||||
else if (changes != null && (changes.DeletedIndices.Any() || changes.InsertedIndices.Any()))
|
||||
{
|
||||
currentlyLoadedBeatmaps.FadeColour(colours.Yellow)
|
||||
.FadeColour(OverlayColourProvider.Content2, 1500, Easing.OutQuint);
|
||||
|
||||
currentlyLoadedBeatmaps.ScaleTo(1.1f)
|
||||
.ScaleTo(1, 1500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadTutorial()
|
||||
{
|
||||
if (tutorialDownloader != null)
|
||||
return;
|
||||
|
||||
tutorialDownloader = new BundledBeatmapDownloader(true);
|
||||
|
||||
AddInternal(tutorialDownloader);
|
||||
|
||||
var downloadTracker = tutorialDownloader.DownloadTrackers.First();
|
||||
|
||||
downloadTracker.Progress.BindValueChanged(progress =>
|
||||
{
|
||||
downloadTutorialButton.SetProgress(progress.NewValue, false);
|
||||
|
||||
if (progress.NewValue == 1)
|
||||
downloadTutorialButton.Complete();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void downloadBundled()
|
||||
{
|
||||
if (bundledDownloader != null)
|
||||
return;
|
||||
|
||||
bundledDownloader = new BundledBeatmapDownloader(false);
|
||||
|
||||
AddInternal(bundledDownloader);
|
||||
|
||||
foreach (var tracker in bundledDownloader.DownloadTrackers)
|
||||
tracker.State.BindValueChanged(_ => updateProgress(), true);
|
||||
|
||||
void updateProgress()
|
||||
{
|
||||
double progress = (double)bundledDownloader.DownloadTrackers.Count(t => t.State.Value == DownloadState.LocallyAvailable) / bundledDownloader.DownloadTrackers.Count();
|
||||
|
||||
if (progress == 1)
|
||||
downloadBundledButton.Complete();
|
||||
else
|
||||
downloadBundledButton.SetProgress(progress, true);
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressRoundedButton : RoundedButton
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private ProgressBar progressBar = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Add(progressBar = new ProgressBar(false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
FillColour = BackgroundColour,
|
||||
Alpha = 0.5f,
|
||||
Depth = float.MinValue
|
||||
});
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
Enabled.Value = false;
|
||||
|
||||
Background.FadeColour(colours.Green, 500, Easing.OutQuint);
|
||||
progressBar.FillColour = colours.Green;
|
||||
|
||||
this.TransformBindableTo(progressBar.Current, 1, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public void SetProgress(double progress, bool animated)
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return;
|
||||
|
||||
this.TransformBindableTo(progressBar.Current, progress, animated ? 500 : 0, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs
Normal file
109
osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
|
||||
namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
[LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.Behaviour))]
|
||||
public class ScreenBehaviour : FirstRunSetupScreen
|
||||
{
|
||||
private SearchContainer<SettingsSection> searchContainer;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Content.Children = new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24))
|
||||
{
|
||||
Text = FirstRunSetupOverlayStrings.BehaviourDescription,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new TriangleButton
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Text = FirstRunSetupOverlayStrings.NewDefaults,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Action = applyStandard,
|
||||
},
|
||||
Empty(),
|
||||
new DangerousTriangleButton
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Text = FirstRunSetupOverlayStrings.ClassicDefaults,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Action = applyClassic
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
searchContainer = new SearchContainer<SettingsSection>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new SettingsSection[]
|
||||
{
|
||||
// This list should be kept in sync with SettingsOverlay.
|
||||
new GeneralSection(),
|
||||
new SkinSection(),
|
||||
// InputSection is intentionally omitted for now due to its sub-panel being a pain to set up.
|
||||
new UserInterfaceSection(),
|
||||
new GameplaySection(),
|
||||
new RulesetSection(),
|
||||
new AudioSection(),
|
||||
new GraphicsSection(),
|
||||
new OnlineSection(),
|
||||
new MaintenanceSection(),
|
||||
new DebugSection(),
|
||||
},
|
||||
SearchTerm = SettingsItem<bool>.CLASSIC_DEFAULT_SEARCH_TERM,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void applyClassic()
|
||||
{
|
||||
foreach (var i in searchContainer.ChildrenOfType<ISettingsItem>().Where(s => s.HasClassicDefault))
|
||||
i.ApplyClassicDefault();
|
||||
}
|
||||
|
||||
private void applyStandard()
|
||||
{
|
||||
foreach (var i in searchContainer.ChildrenOfType<ISettingsItem>().Where(s => s.HasClassicDefault))
|
||||
i.ApplyDefault();
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@ using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -45,8 +44,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
private ScreenStack? stack;
|
||||
|
||||
public PurpleTriangleButton NextButton = null!;
|
||||
public DangerousTriangleButton BackButton = null!;
|
||||
public ShearedButton NextButton = null!;
|
||||
public ShearedButton BackButton = null!;
|
||||
|
||||
private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>();
|
||||
|
||||
@ -60,7 +59,9 @@ namespace osu.Game.Overlays
|
||||
private readonly Type[] steps =
|
||||
{
|
||||
typeof(ScreenWelcome),
|
||||
typeof(ScreenUIScale)
|
||||
typeof(ScreenBeatmaps),
|
||||
typeof(ScreenUIScale),
|
||||
typeof(ScreenBehaviour),
|
||||
};
|
||||
|
||||
private Container stackContainer = null!;
|
||||
@ -70,7 +71,7 @@ namespace osu.Game.Overlays
|
||||
private Container content = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Header.Title = FirstRunSetupOverlayStrings.FirstRunSetupTitle;
|
||||
Header.Description = FirstRunSetupOverlayStrings.FirstRunSetupDescription;
|
||||
@ -82,7 +83,11 @@ namespace osu.Game.Overlays
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 50 },
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 70 * 1.2f,
|
||||
Bottom = 20,
|
||||
},
|
||||
Child = new InputBlockingContainer
|
||||
{
|
||||
Masking = true,
|
||||
@ -103,7 +108,7 @@ namespace osu.Game.Overlays
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 20,
|
||||
Horizontal = 20,
|
||||
Horizontal = 70,
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -115,14 +120,15 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.98f,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Vertical = PADDING },
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
@ -132,21 +138,25 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
new[]
|
||||
{
|
||||
BackButton = new DangerousTriangleButton
|
||||
Empty(),
|
||||
BackButton = new ShearedButton(300)
|
||||
{
|
||||
Width = 300,
|
||||
Text = CommonStrings.Back,
|
||||
Action = showPreviousStep,
|
||||
Enabled = { Value = false },
|
||||
DarkerColour = colours.Pink2,
|
||||
LighterColour = colours.Pink1,
|
||||
},
|
||||
Empty(),
|
||||
NextButton = new PurpleTriangleButton
|
||||
NextButton = new ShearedButton(0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
Text = FirstRunSetupOverlayStrings.GetStarted,
|
||||
DarkerColour = ColourProvider.Colour2,
|
||||
LighterColour = ColourProvider.Colour1,
|
||||
Action = showNextStep
|
||||
}
|
||||
},
|
||||
Empty(),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
@ -53,6 +53,9 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
public Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
public Bindable<bool> Active = new BindableBool(true);
|
||||
|
||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
|
||||
|
||||
protected virtual ModPanel CreateModPanel(Mod mod) => new ModPanel(mod);
|
||||
|
||||
|
@ -13,7 +13,9 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -59,7 +61,8 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private DifficultyMultiplierDisplay? multiplierDisplay;
|
||||
private ModSettingsArea modSettingsArea = null!;
|
||||
private FillFlowContainer<ModColumn> columnFlow = null!;
|
||||
private ColumnScrollContainer columnScroll = null!;
|
||||
private ColumnFlowContainer columnFlow = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -95,27 +98,27 @@ namespace osu.Game.Overlays.Mods
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuScrollContainer(Direction.Horizontal)
|
||||
columnScroll = new ColumnScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = false,
|
||||
ClampExtension = 100,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = columnFlow = new ModColumnContainer
|
||||
Child = columnFlow = new ColumnFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
Shear = new Vector2(SHEAR, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Margin = new MarginPadding { Right = 70 },
|
||||
Margin = new MarginPadding { Horizontal = 70 },
|
||||
Children = new[]
|
||||
{
|
||||
CreateModColumn(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }),
|
||||
CreateModColumn(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }),
|
||||
CreateModColumn(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }),
|
||||
CreateModColumn(ModType.Conversion),
|
||||
CreateModColumn(ModType.Fun)
|
||||
createModColumnContent(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }),
|
||||
createModColumnContent(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }),
|
||||
createModColumnContent(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }),
|
||||
createModColumnContent(ModType.Conversion),
|
||||
createModColumnContent(ModType.Fun)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,6 +156,14 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
|
||||
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
|
||||
};
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -166,7 +177,7 @@ namespace osu.Game.Overlays.Mods
|
||||
updateSelectionFromBindable();
|
||||
}, true);
|
||||
|
||||
foreach (var column in columnFlow)
|
||||
foreach (var column in columnFlow.Columns)
|
||||
{
|
||||
column.SelectedMods.BindValueChanged(updateBindableFromSelection);
|
||||
}
|
||||
@ -191,7 +202,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void updateAvailableMods()
|
||||
{
|
||||
foreach (var column in columnFlow)
|
||||
foreach (var column in columnFlow.Columns)
|
||||
column.Filter = isValidMod;
|
||||
}
|
||||
|
||||
@ -244,7 +255,7 @@ namespace osu.Game.Overlays.Mods
|
||||
// to synchronise state correctly, updateBindableFromSelection() computes the final mods (including incompatibility rules) and updates SelectedMods,
|
||||
// and this method then runs unconditionally again to make sure the new visual selection accurately reflects the final set of selected mods.
|
||||
// selectionBindableSyncInProgress ensures that mutual infinite recursion does not happen after that unconditional call.
|
||||
foreach (var column in columnFlow)
|
||||
foreach (var column in columnFlow.Columns)
|
||||
column.SelectedMods.Value = SelectedMods.Value.Where(mod => mod.Type == column.ModType).ToArray();
|
||||
}
|
||||
|
||||
@ -265,7 +276,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IEnumerable<Mod> addedMods, IEnumerable<Mod> removedMods)
|
||||
=> columnFlow.SelectMany(column => column.SelectedMods.Value).ToArray();
|
||||
=> columnFlow.Columns.SelectMany(column => column.SelectedMods.Value).ToArray();
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
@ -280,7 +291,8 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
for (int i = 0; i < columnFlow.Count; i++)
|
||||
{
|
||||
columnFlow[i].TopLevelContent
|
||||
columnFlow[i].Column
|
||||
.TopLevelContent
|
||||
.Delay(i * 30)
|
||||
.MoveToY(0, fade_in_duration, Easing.OutQuint)
|
||||
.FadeIn(fade_in_duration, Easing.OutQuint);
|
||||
@ -301,27 +313,68 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
const float distance = 700;
|
||||
|
||||
columnFlow[i].TopLevelContent
|
||||
columnFlow[i].Column
|
||||
.TopLevelContent
|
||||
.MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint)
|
||||
.FadeOut(fade_out_duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private class ModColumnContainer : FillFlowContainer<ModColumn>
|
||||
internal class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
||||
{
|
||||
public ColumnScrollContainer()
|
||||
: base(Direction.Horizontal)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// the bounds below represent the horizontal range of scroll items to be considered fully visible/active, in the scroll's internal coordinate space.
|
||||
// note that clamping is applied to the left scroll bound to ensure scrolling past extents does not change the set of active columns.
|
||||
float leftVisibleBound = Math.Clamp(Current, 0, ScrollableExtent);
|
||||
float rightVisibleBound = leftVisibleBound + DrawWidth;
|
||||
|
||||
// if a movement is occurring at this time, the bounds below represent the full range of columns that the scroll movement will encompass.
|
||||
// this will be used to ensure that columns do not change state from active to inactive back and forth until they are fully scrolled past.
|
||||
float leftMovementBound = Math.Min(Current, Target);
|
||||
float rightMovementBound = Math.Max(Current, Target) + DrawWidth;
|
||||
|
||||
foreach (var column in Child)
|
||||
{
|
||||
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
|
||||
// so we have to manually compensate.
|
||||
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
|
||||
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * SHEAR, 0), ScrollContent);
|
||||
|
||||
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
|
||||
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
|
||||
bool isBeingScrolledToward = Precision.AlmostBigger(topLeft.X, leftMovementBound)
|
||||
&& Precision.DefinitelyBigger(rightMovementBound, bottomRight.X);
|
||||
|
||||
column.Active.Value = isCurrentlyVisible || isBeingScrolledToward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ColumnFlowContainer : FillFlowContainer<ColumnDimContainer>
|
||||
{
|
||||
public IEnumerable<ModColumn> Columns => Children.Select(dimWrapper => dimWrapper.Column);
|
||||
|
||||
private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize);
|
||||
|
||||
public ModColumnContainer()
|
||||
public ColumnFlowContainer()
|
||||
{
|
||||
AddLayout(drawSizeLayout);
|
||||
}
|
||||
|
||||
public override void Add(ModColumn column)
|
||||
public override void Add(ColumnDimContainer dimContainer)
|
||||
{
|
||||
base.Add(column);
|
||||
base.Add(dimContainer);
|
||||
|
||||
Debug.Assert(column != null);
|
||||
column.Shear = Vector2.Zero;
|
||||
Debug.Assert(dimContainer != null);
|
||||
dimContainer.Column.Shear = Vector2.Zero;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -341,6 +394,63 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
internal class ColumnDimContainer : Container
|
||||
{
|
||||
public ModColumn Column { get; }
|
||||
|
||||
public readonly Bindable<bool> Active = new BindableBool();
|
||||
public Action<ColumnDimContainer>? RequestScroll { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public ColumnDimContainer(ModColumn column)
|
||||
{
|
||||
Child = Column = column;
|
||||
column.Active.BindTo(Active);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Active.BindValueChanged(_ => updateDim(), true);
|
||||
FinishTransforms();
|
||||
}
|
||||
|
||||
private void updateDim()
|
||||
{
|
||||
Colour4 targetColour;
|
||||
|
||||
if (Active.Value)
|
||||
targetColour = Colour4.White;
|
||||
else
|
||||
targetColour = IsHovered ? colours.GrayC : colours.Gray8;
|
||||
|
||||
this.FadeColour(targetColour, 800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (!Active.Value)
|
||||
RequestScroll?.Invoke(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
base.OnHover(e);
|
||||
updateDim();
|
||||
return Active.Value;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
updateDim();
|
||||
}
|
||||
}
|
||||
|
||||
private class ClickToReturnContainer : Container
|
||||
{
|
||||
public BindableBool HandleMouse { get; } = new BindableBool();
|
||||
|
@ -14,13 +14,13 @@ namespace osu.Game.Overlays.Mods
|
||||
/// </summary>
|
||||
public class NestedVerticalScrollContainer : OsuScrollContainer
|
||||
{
|
||||
private OsuScrollContainer? parentScrollContainer;
|
||||
private ModSelectScreen.ColumnScrollContainer? parentScrollContainer;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
parentScrollContainer = this.FindClosestParent<OsuScrollContainer>();
|
||||
parentScrollContainer = this.FindClosestParent<ModSelectScreen.ColumnScrollContainer>();
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
|
@ -31,7 +31,9 @@ namespace osu.Game.Overlays.Profile
|
||||
User.ValueChanged += e => updateDisplay(e.NewValue);
|
||||
|
||||
TabControl.AddItem(LayoutStrings.HeaderUsersShow);
|
||||
TabControl.AddItem(LayoutStrings.HeaderUsersModding);
|
||||
|
||||
// todo: pending implementation.
|
||||
// TabControl.AddItem(LayoutStrings.HeaderUsersModding);
|
||||
|
||||
centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true);
|
||||
}
|
||||
|
@ -9,5 +9,20 @@ namespace osu.Game.Overlays.Settings
|
||||
public interface ISettingsItem : IDrawable, IDisposable
|
||||
{
|
||||
event Action SettingChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this setting has a classic default (ie. a different default which better aligns with osu-stable expectations).
|
||||
/// </summary>
|
||||
bool HasClassicDefault { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Apply the classic default value of the associated setting. Will throw if <see cref="HasClassicDefault"/> is <c>false</c>.
|
||||
/// </summary>
|
||||
void ApplyClassicDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Apply the default value of the associated setting.
|
||||
/// </summary>
|
||||
void ApplyDefault();
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
ClassicDefault = false,
|
||||
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
|
||||
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
{
|
||||
new SettingsEnumDropdown<ScoringMode>
|
||||
{
|
||||
ClassicDefault = ScoringMode.Classic,
|
||||
LabelText = GameplaySettingsStrings.ScoreDisplayMode,
|
||||
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
|
||||
Keywords = new[] { "scoring" }
|
||||
|
@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
ClassicDefault = false,
|
||||
LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
|
||||
Keywords = new[] { "hp", "bar" }
|
||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
},
|
||||
new SettingsSlider<double, TimeSlider>
|
||||
{
|
||||
ClassicDefault = 0,
|
||||
LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
|
||||
Current = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay),
|
||||
Keywords = new[] { @"delay" },
|
||||
|
@ -32,6 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
ClassicDefault = true,
|
||||
LabelText = UserInterfaceStrings.RightMouseScroll,
|
||||
Current = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
|
||||
},
|
||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Settings
|
||||
/// </summary>
|
||||
public object SettingSourceObject { get; internal set; }
|
||||
|
||||
public const string CLASSIC_DEFAULT_SEARCH_TERM = @"has-classic-default";
|
||||
|
||||
private IHasCurrentValue<T> controlWithCurrent => Control as IHasCurrentValue<T>;
|
||||
|
||||
protected override Container<Drawable> Content => FlowContent;
|
||||
@ -96,7 +98,21 @@ namespace osu.Game.Overlays.Settings
|
||||
set => controlWithCurrent.Current = value;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List<string>(Keywords) { LabelText.ToString() }.ToArray();
|
||||
public virtual IEnumerable<string> FilterTerms
|
||||
{
|
||||
get
|
||||
{
|
||||
var keywords = new List<string>(Keywords ?? Array.Empty<string>())
|
||||
{
|
||||
LabelText.ToString()
|
||||
};
|
||||
|
||||
if (HasClassicDefault)
|
||||
keywords.Add(CLASSIC_DEFAULT_SEARCH_TERM);
|
||||
|
||||
return keywords;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Keywords { get; set; }
|
||||
|
||||
@ -122,6 +138,32 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
public event Action SettingChanged;
|
||||
|
||||
private T classicDefault;
|
||||
|
||||
public bool HasClassicDefault { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A "classic" default value for this setting.
|
||||
/// </summary>
|
||||
public T ClassicDefault
|
||||
{
|
||||
set
|
||||
{
|
||||
classicDefault = value;
|
||||
HasClassicDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyClassicDefault()
|
||||
{
|
||||
if (!HasClassicDefault)
|
||||
throw new InvalidOperationException($"Cannot apply a classic default to a setting which doesn't have one defined via {nameof(ClassicDefault)}.");
|
||||
|
||||
Current.Value = classicDefault;
|
||||
}
|
||||
|
||||
public void ApplyDefault() => Current.SetDefault();
|
||||
|
||||
protected SettingsItem()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -58,7 +59,7 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
public bool FilteringActive { get; set; }
|
||||
|
||||
[Resolved]
|
||||
[Resolved(canBeNull: true)]
|
||||
private SettingsPanel settingsPanel { get; set; }
|
||||
|
||||
protected SettingsSection()
|
||||
@ -131,7 +132,7 @@ namespace osu.Game.Overlays.Settings
|
||||
},
|
||||
});
|
||||
|
||||
selectedSection = settingsPanel.CurrentSection.GetBoundCopy();
|
||||
selectedSection = settingsPanel?.CurrentSection.GetBoundCopy() ?? new Bindable<SettingsSection>(this);
|
||||
selectedSection.BindValueChanged(_ => updateContentFade(), true);
|
||||
}
|
||||
|
||||
@ -152,7 +153,10 @@ namespace osu.Game.Overlays.Settings
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (!isCurrentSection)
|
||||
{
|
||||
Debug.Assert(settingsPanel != null);
|
||||
settingsPanel.SectionsContainer.ScrollTo(this);
|
||||
}
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[]
|
||||
{
|
||||
// This list should be kept in sync with ScreenBehaviour.
|
||||
new GeneralSection(),
|
||||
new SkinSection(),
|
||||
new InputSection(createSubPanel(new KeyBindingPanel())),
|
||||
|
@ -405,6 +405,8 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private class DragContainer : Container
|
||||
{
|
||||
public override bool DragBlocksClick => false;
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||
{
|
||||
bool countdownActive = multiplayerClient.Room?.Countdown != null;
|
||||
bool countdownActive = multiplayerClient.Room?.Countdown is MatchStartCountdown;
|
||||
|
||||
if (countdownActive)
|
||||
{
|
||||
|
@ -55,7 +55,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||
{
|
||||
if (countdown != room?.Countdown)
|
||||
MultiplayerCountdown newCountdown;
|
||||
|
||||
switch (room?.Countdown)
|
||||
{
|
||||
case MatchStartCountdown _:
|
||||
newCountdown = room.Countdown;
|
||||
break;
|
||||
|
||||
// Clear the countdown with any other (including non-null) countdown values.
|
||||
default:
|
||||
newCountdown = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newCountdown != countdown)
|
||||
{
|
||||
countdown = room?.Countdown;
|
||||
countdownChangeTime = Time.Current;
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
@ -20,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
base.LoadComplete();
|
||||
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
client.LoadAborted += onLoadAborted;
|
||||
onRoomUpdated();
|
||||
}
|
||||
|
||||
@ -35,6 +37,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
transitionFromResults();
|
||||
}
|
||||
|
||||
private void onLoadAborted()
|
||||
{
|
||||
// If the server aborts gameplay for this user (due to loading too slow), exit gameplay screens.
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important);
|
||||
this.MakeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnResuming(e);
|
||||
@ -42,9 +54,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(client.LocalUser != null);
|
||||
|
||||
if (!(e.Last is MultiplayerPlayerLoader playerLoader))
|
||||
return;
|
||||
|
||||
// Nothing needs to be done if already in the idle state (e.g. via load being aborted by the server).
|
||||
if (client.LocalUser.State == MultiplayerUserState.Idle)
|
||||
return;
|
||||
|
||||
// If gameplay wasn't finished, then we have a simple path back to the idle state by aborting gameplay.
|
||||
if (!playerLoader.GameplayPassed)
|
||||
{
|
||||
|
@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (!ValidForResume)
|
||||
return; // token retrieval may have failed.
|
||||
|
||||
client.MatchStarted += onMatchStarted;
|
||||
client.GameplayStarted += onGameplayStarted;
|
||||
client.ResultsReady += onResultsReady;
|
||||
|
||||
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
|
||||
@ -144,10 +144,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
protected override void StartGameplay()
|
||||
{
|
||||
// block base call, but let the server know we are ready to start.
|
||||
loadingDisplay.Show();
|
||||
|
||||
client.ChangeState(MultiplayerUserState.Loaded).ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion);
|
||||
if (client.LocalUser?.State == MultiplayerUserState.Loaded)
|
||||
{
|
||||
// block base call, but let the server know we are ready to start.
|
||||
loadingDisplay.Show();
|
||||
client.ChangeState(MultiplayerUserState.ReadyForGameplay);
|
||||
}
|
||||
}
|
||||
|
||||
private void failAndBail(string message = null)
|
||||
@ -175,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
|
||||
}
|
||||
|
||||
private void onMatchStarted() => Scheduler.Add(() =>
|
||||
private void onGameplayStarted() => Scheduler.Add(() =>
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
@ -223,7 +225,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
if (client != null)
|
||||
{
|
||||
client.MatchStarted -= onMatchStarted;
|
||||
client.GameplayStarted -= onGameplayStarted;
|
||||
client.ResultsReady -= onResultsReady;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
@ -11,6 +15,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public bool GameplayPassed => player?.GameplayState.HasPassed == true;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient multiplayerClient { get; set; }
|
||||
|
||||
private Player player;
|
||||
|
||||
public MultiplayerPlayerLoader(Func<Player> createPlayer)
|
||||
@ -18,6 +25,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ReadyForGameplay =>
|
||||
base.ReadyForGameplay
|
||||
// The server is forcefully starting gameplay.
|
||||
|| multiplayerClient.LocalUser?.State == MultiplayerUserState.Playing;
|
||||
|
||||
protected override void OnPlayerLoaded()
|
||||
{
|
||||
base.OnPlayerLoaded();
|
||||
|
||||
multiplayerClient.ChangeState(MultiplayerUserState.Loaded)
|
||||
.ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion);
|
||||
}
|
||||
|
||||
private void failAndBail(string message = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Exit();
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnSuspending(e);
|
||||
|
@ -112,6 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
break;
|
||||
|
||||
case MultiplayerUserState.Loaded:
|
||||
case MultiplayerUserState.ReadyForGameplay:
|
||||
text.Text = "loaded";
|
||||
icon.Icon = FontAwesome.Solid.DotCircle;
|
||||
icon.Colour = colours.YellowLight;
|
||||
|
@ -92,11 +92,15 @@ namespace osu.Game.Screens.Play
|
||||
!playerConsumed
|
||||
// don't push unless the player is completely loaded
|
||||
&& CurrentPlayer?.LoadState == LoadState.Ready
|
||||
// don't push if the user is hovering one of the panes, unless they are idle.
|
||||
&& (IsHovered || idleTracker.IsIdle.Value)
|
||||
// don't push if the user is dragging a slider or otherwise.
|
||||
// don't push unless the player is ready to start gameplay
|
||||
&& ReadyForGameplay;
|
||||
|
||||
protected virtual bool ReadyForGameplay =>
|
||||
// not ready if the user is hovering one of the panes, unless they are idle.
|
||||
(IsHovered || idleTracker.IsIdle.Value)
|
||||
// not ready if the user is dragging a slider or otherwise.
|
||||
&& inputManager.DraggedDrawable == null
|
||||
// don't push if a focused overlay is visible, like settings.
|
||||
// not ready if a focused overlay is visible, like settings.
|
||||
&& inputManager.FocusedDrawable == null;
|
||||
|
||||
private readonly Func<Player> createPlayer;
|
||||
@ -364,7 +368,15 @@ namespace osu.Game.Screens.Play
|
||||
CurrentPlayer.RestartCount = restartCount++;
|
||||
CurrentPlayer.RestartRequested = restartRequested;
|
||||
|
||||
LoadTask = LoadComponentAsync(CurrentPlayer, _ => MetadataInfo.Loading = false);
|
||||
LoadTask = LoadComponentAsync(CurrentPlayer, _ =>
|
||||
{
|
||||
MetadataInfo.Loading = false;
|
||||
OnPlayerLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void OnPlayerLoaded()
|
||||
{
|
||||
}
|
||||
|
||||
private void restartRequested()
|
||||
|
@ -27,8 +27,9 @@ namespace osu.Game.Screens.Select.Filter
|
||||
[LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksLength))]
|
||||
Length,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
|
||||
RankAchieved,
|
||||
// todo: pending support (https://github.com/ppy/osu/issues/4917)
|
||||
// [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
|
||||
// RankAchieved,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
|
||||
Source,
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -25,7 +26,7 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
SelectedColour = colours.Green;
|
||||
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||
Text = @"random";
|
||||
updateText();
|
||||
|
||||
Action = () =>
|
||||
{
|
||||
@ -59,6 +60,44 @@ namespace osu.Game.Screens.Select
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
updateText(e.ShiftPressed);
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyUpEvent e)
|
||||
{
|
||||
updateText(e.ShiftPressed);
|
||||
base.OnKeyUp(e);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this uses OR to handle rewinding when clicks are triggered by other sources (i.e. right button in OnMouseUp).
|
||||
rewindSearch |= e.ShiftPressed;
|
||||
return base.OnClick(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
rewindSearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Right)
|
||||
{
|
||||
rewindSearch = true;
|
||||
TriggerClick();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
rewindSearch = e.Action == GlobalAction.SelectPreviousRandom;
|
||||
@ -79,5 +118,7 @@ namespace osu.Game.Screens.Select
|
||||
rewindSearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateText(bool rewind = false) => Text = rewind ? "rewind" : "random";
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded))
|
||||
ChangeUserState(u.UserID, MultiplayerUserState.Playing);
|
||||
|
||||
((IMultiplayerClient)this).MatchStarted();
|
||||
((IMultiplayerClient)this).GameplayStarted();
|
||||
|
||||
ChangeRoomState(MultiplayerRoomState.Playing);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.10.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.428.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.430.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||
<PackageReference Include="Sentry" Version="3.14.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
|
@ -61,7 +61,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.428.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.430.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||
@ -84,7 +84,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.428.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.430.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user