Compare commits
30 Commits
70f7f09a83
...
2025.829.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 081355864e | |||
| 6435a835d1 | |||
| 628181a883 | |||
| 835329efd3 | |||
| 5399943118 | |||
| d07f82f6f4 | |||
|
|
2bea59e65f | ||
|
|
c0fd5637de | ||
|
|
5e7a99c97f | ||
|
|
8f628d16ae | ||
|
|
2ccb65aa65 | ||
|
|
4d851f2527 | ||
|
|
4bafbfb9e4 | ||
|
|
3f179e3903 | ||
|
|
196b28115e | ||
|
|
7660a9ba8e | ||
|
|
e908b80359 | ||
|
|
a2bf8e3988 | ||
| 5b186bb740 | |||
|
|
6e8246b539 | ||
| 6cb99c13c2 | |||
|
|
3cca458c21 | ||
|
|
bc59270f3e | ||
| 96008e06ab | |||
| 590b0a8028 | |||
|
|
c0c3690908 | ||
|
|
73624e4e25 | ||
|
|
f374af7ce7 | ||
|
|
7530ad1a7b | ||
|
|
a049f5065d |
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "osu.Desktop.slnf"
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||
|
||||
public override float DefaultFlashlightSize => 325;
|
||||
public override float DefaultFlashlightSize => 203.125f;
|
||||
|
||||
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
|
||||
|
||||
|
||||
@@ -32,26 +32,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestPlayfieldBasedSize()
|
||||
{
|
||||
OsuModFlashlight flashlight;
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()],
|
||||
PassCondition = () =>
|
||||
{
|
||||
var flashlightOverlay = Player.DrawableRuleset.Overlays
|
||||
.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
|
||||
.First();
|
||||
|
||||
// the combo check is here because the flashlight radius decreases for the first time at 100 combo
|
||||
// and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()`
|
||||
return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderDimsOnlyAfterStartTime()
|
||||
{
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Osu.HUD
|
||||
if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null)
|
||||
{
|
||||
hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition,
|
||||
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * 0.5f;
|
||||
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * (inner_portion / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||
|
||||
public override float DefaultFlashlightSize => 200;
|
||||
public override float DefaultFlashlightSize => 125;
|
||||
|
||||
private OsuFlashlight flashlight = null!;
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK;
|
||||
@@ -12,6 +15,34 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneTaikoModFlashlight : TaikoModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestAspectRatios([Values] bool withClassicMod)
|
||||
{
|
||||
if (withClassicMod)
|
||||
CreateModTest(new ModTestData { Mods = new Mod[] { new TaikoModFlashlight(), new TaikoModClassic() }, PassCondition = () => true });
|
||||
else
|
||||
CreateModTest(new ModTestData { Mod = new TaikoModFlashlight(), PassCondition = () => true });
|
||||
|
||||
AddStep("clear dim", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0.0));
|
||||
|
||||
AddStep("reset", () => Stack.FillMode = FillMode.Stretch);
|
||||
AddStep("set to 16:9", () =>
|
||||
{
|
||||
Stack.FillAspectRatio = 16 / 9f;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
AddStep("set to 4:3", () =>
|
||||
{
|
||||
Stack.FillAspectRatio = 4 / 3f;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
AddSliderStep("aspect ratio", 0.01f, 5f, 1f, v =>
|
||||
{
|
||||
Stack.FillAspectRatio = v;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.25f)]
|
||||
|
||||
@@ -47,28 +47,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
this.taikoPlayfield = taikoPlayfield;
|
||||
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSize = new Vector2(0, GetSize());
|
||||
FlashlightSmoothness = 1.4f;
|
||||
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the aspect ratio-adjusted size of the flashlight.
|
||||
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
|
||||
/// </summary>
|
||||
/// <param name="size">
|
||||
/// The size of the flashlight.
|
||||
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
|
||||
/// </param>
|
||||
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
|
||||
{
|
||||
return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
|
||||
}
|
||||
|
||||
protected override void UpdateFlashlightSize(float size)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
@@ -82,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
ClearTransforms(targetMember: nameof(FlashlightSize));
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSize = new Vector2(0, GetSize());
|
||||
|
||||
flashlightProperties.Validate();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
protected TestReplayPlayer Player = null!;
|
||||
|
||||
[Test]
|
||||
public void TestFailedBeatmapLoad()
|
||||
{
|
||||
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo, withHitObjects: false));
|
||||
|
||||
AddUntilStep("wait for exit", () => Player.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseViaSpace()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Configuration;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
@@ -41,6 +42,8 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.Ruleset, string.Empty);
|
||||
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
|
||||
|
||||
SetDefault(OsuSetting.MenuCookieColor, Colour4.FromHex(@"ff66ba"));
|
||||
|
||||
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
|
||||
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
|
||||
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
|
||||
@@ -65,6 +68,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
|
||||
|
||||
SetDefault(OsuSetting.ForceLegacySongSelect, false);
|
||||
SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
|
||||
|
||||
// Online settings
|
||||
@@ -194,6 +198,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
|
||||
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never);
|
||||
SetDefault(OsuSetting.UseSeasonalBackgroundsV2, true);
|
||||
|
||||
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
|
||||
|
||||
@@ -405,6 +410,7 @@ namespace osu.Game.Configuration
|
||||
ChatDisplayHeight,
|
||||
BeatmapListingCardSize,
|
||||
ToolbarClockDisplayMode,
|
||||
ForceLegacySongSelect,
|
||||
SongSelectBackgroundBlur,
|
||||
Version,
|
||||
ShowFirstRunSetup,
|
||||
@@ -412,6 +418,7 @@ namespace osu.Game.Configuration
|
||||
Skin,
|
||||
ScreenshotFormat,
|
||||
ScreenshotCaptureMenuCursor,
|
||||
MenuCookieColor,
|
||||
BeatmapSkins,
|
||||
BeatmapColours,
|
||||
BeatmapHitsounds,
|
||||
@@ -436,6 +443,7 @@ namespace osu.Game.Configuration
|
||||
MenuBackgroundSource,
|
||||
GameplayDisableWinKey,
|
||||
SeasonalBackgroundMode,
|
||||
UseSeasonalBackgroundsV2, // TODO: add migrations
|
||||
BackgroundCategory,
|
||||
EditorWaveformOpacity,
|
||||
EditorShowHitMarkers,
|
||||
|
||||
@@ -727,7 +727,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(@$"Failed to update ranked/submitted dates for beatmap set {id}: {e}");
|
||||
Logger.Log(@$"Failed to update user tags for beatmap {id}: {e}");
|
||||
++failedCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,19 +33,19 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private Bindable<SeasonalBackgroundMode> backgroundMode;
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
private Bindable<string> selectedCategory;
|
||||
private Bindable<APISeasonalBackgrounds> currentBackgrounds;
|
||||
|
||||
private int currentBackgroundIndex;
|
||||
|
||||
private bool shouldShowCustomBackgrounds => backgroundMode.Value != SeasonalBackgroundMode.Never;
|
||||
private bool shouldShowCustomBackgrounds => useSeasonalBackgrounds.Value;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, SessionStatics sessionStatics)
|
||||
{
|
||||
backgroundMode = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
|
||||
backgroundMode.BindValueChanged(_ => BackgroundChanged?.Invoke());
|
||||
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
|
||||
useSeasonalBackgrounds.BindValueChanged(_ => BackgroundChanged?.Invoke());
|
||||
|
||||
selectedCategory = config.GetBindable<string>(OsuSetting.BackgroundCategory);
|
||||
selectedCategory.BindValueChanged(_ => fetchBackgroundsForSelectedCategory());
|
||||
@@ -53,18 +53,18 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
currentBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
|
||||
|
||||
if (shouldShowCustomBackgrounds)
|
||||
fetchCategories();
|
||||
fetchCategories(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to trigger a refresh of categories from the UI.
|
||||
/// </summary>
|
||||
public void RefreshCategories()
|
||||
public void RefreshCategories(bool ignoreSuccess = false)
|
||||
{
|
||||
fetchCategories();
|
||||
fetchCategories(ignoreSuccess);
|
||||
}
|
||||
|
||||
private void fetchCategories()
|
||||
private void fetchCategories(bool ignoreSuccess = false)
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds) return;
|
||||
|
||||
@@ -74,20 +74,28 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
var serverCategories = response.Categories ?? Enumerable.Empty<string>();
|
||||
|
||||
AvailableCategories.Value = new[] { "Default" }.Concat(serverCategories)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
AvailableCategories.Value = serverCategories.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
if (!AvailableCategories.Value.Any())
|
||||
{
|
||||
selectedCategory.Value = "";
|
||||
return; // we don't have any categories!!!
|
||||
}
|
||||
|
||||
if (!AvailableCategories.Value.Contains(selectedCategory.Value))
|
||||
selectedCategory.Value = "Default";
|
||||
else
|
||||
fetchBackgroundsForSelectedCategory();
|
||||
selectedCategory.Value = AvailableCategories.Value.Contains("Default")
|
||||
? "Default"
|
||||
: AvailableCategories.Value.ElementAt(0);
|
||||
|
||||
OnCategoriesRefreshed?.Invoke();
|
||||
fetchBackgroundsForSelectedCategory();
|
||||
|
||||
if (!ignoreSuccess)
|
||||
OnCategoriesRefreshed?.Invoke();
|
||||
};
|
||||
|
||||
request.Failure += exception =>
|
||||
{
|
||||
AvailableCategories.Value = new[] { "Íå óäàëîñü çàãðóçèòü..." };
|
||||
AvailableCategories.Value = Array.Empty<string>();
|
||||
OnLoadFailure?.Invoke(exception);
|
||||
};
|
||||
|
||||
@@ -98,13 +106,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds) return;
|
||||
|
||||
if (AvailableCategories.Value.Count() == 1 && AvailableCategories.Value.First().Contains("Íå óäàëîñü"))
|
||||
{
|
||||
currentBackgrounds.Value = new APISeasonalBackgrounds { Backgrounds = new List<APISeasonalBackground>() };
|
||||
BackgroundChanged?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
string categoryToFetch = selectedCategory.Value == "Default" ? null : selectedCategory.Value;
|
||||
var request = new GetSeasonalBackgroundsRequest(categoryToFetch);
|
||||
|
||||
|
||||
@@ -184,6 +184,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString SelectedMods => new TranslatableString(getKey(@"selected_mods"), @"Selected Mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Use legacy song select (SelectV1)"
|
||||
/// </summary>
|
||||
public static LocalisableString ForceLegacySongSelect => new TranslatableString(getKey(@"force_select_v1"), @"Use legacy song select (SelectV1)");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace osu.Game
|
||||
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
||||
|
||||
[Cached]
|
||||
private readonly SeasonalBackgroundLoader backgroundLoader;
|
||||
private SeasonalBackgroundLoader seasonalBackgroundLoader;
|
||||
|
||||
protected SentryLogger SentryLogger;
|
||||
|
||||
@@ -253,10 +253,6 @@ namespace osu.Game
|
||||
|
||||
public OsuGame(string[] args = null)
|
||||
{
|
||||
backgroundLoader = new SeasonalBackgroundLoader();
|
||||
backgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
|
||||
backgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
|
||||
|
||||
this.args = args;
|
||||
|
||||
Logger.NewEntry += forwardGeneralLogToNotifications;
|
||||
@@ -271,6 +267,8 @@ namespace osu.Game
|
||||
tabletLogNotifyOnError = true;
|
||||
}, true);
|
||||
});
|
||||
|
||||
initializeSeasonalBackgrounds();
|
||||
}
|
||||
|
||||
#region IOverlayManager
|
||||
@@ -409,18 +407,6 @@ namespace osu.Game
|
||||
}
|
||||
}
|
||||
}
|
||||
private void handleCategoriesRefreshed()
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsRefreshed,
|
||||
Icon = FontAwesome.Solid.CheckCircle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -1252,7 +1238,7 @@ namespace osu.Game
|
||||
|
||||
loadComponentSingleFile(screenshotManager, Add);
|
||||
|
||||
loadComponentSingleFile(backgroundLoader, Add);
|
||||
loadComponentSingleFile(seasonalBackgroundLoader, Add);
|
||||
|
||||
// dependency on notification overlay, dependent by settings overlay
|
||||
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
||||
@@ -1355,18 +1341,6 @@ namespace osu.Game
|
||||
handleStartupImport();
|
||||
}
|
||||
|
||||
private void handleBackgroundLoadFailure(Exception exception)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsFail,
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
private void handleBackButton()
|
||||
{
|
||||
// TODO: this is SUPER SUPER bad.
|
||||
@@ -1503,6 +1477,40 @@ namespace osu.Game
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeSeasonalBackgrounds()
|
||||
{
|
||||
seasonalBackgroundLoader = new SeasonalBackgroundLoader();
|
||||
seasonalBackgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
|
||||
seasonalBackgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
|
||||
}
|
||||
|
||||
private void handleCategoriesRefreshed()
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsRefreshed,
|
||||
Icon = FontAwesome.Solid.CheckCircle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleBackgroundLoadFailure(Exception exception)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsFail,
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private Task asyncLoadStream;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
@@ -27,21 +26,19 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
|
||||
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
|
||||
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, IAPIProvider api)
|
||||
{
|
||||
user = api.LocalUser.GetBoundCopy();
|
||||
|
||||
var backgroundModeBindable = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
|
||||
var enabledProxyBindable = new Bindable<bool>();
|
||||
|
||||
backgroundModeBindable.BindValueChanged(mode => enabledProxyBindable.Value = mode.NewValue == SeasonalBackgroundMode.Always, true);
|
||||
enabledProxyBindable.BindValueChanged(enabled => backgroundModeBindable.Value = enabled.NewValue ? SeasonalBackgroundMode.Always : SeasonalBackgroundMode.Never);
|
||||
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
|
||||
|
||||
var backgroundToggle = new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
|
||||
Current = enabledProxyBindable
|
||||
Current = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2),
|
||||
ClassicDefault = true
|
||||
};
|
||||
|
||||
var categoryDropdown = new SettingsDropdown<string>
|
||||
@@ -56,28 +53,21 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
Action = () => backgroundLoader.RefreshCategories()
|
||||
};
|
||||
|
||||
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
|
||||
// TODO: the category dropdown disappear if no backgrounds (e.g. when first enabling the setting)
|
||||
refreshButton.CanBeShown.BindTo(useSeasonalBackgrounds);
|
||||
categoryDropdown.CanBeShown.BindTo(useSeasonalBackgrounds);
|
||||
useSeasonalBackgrounds.BindValueChanged(
|
||||
_ => backgroundLoader.RefreshCategories(true)
|
||||
);
|
||||
|
||||
backgroundModeBindable.BindValueChanged(mode =>
|
||||
{
|
||||
if (mode.NewValue == SeasonalBackgroundMode.Always)
|
||||
{
|
||||
categoryDropdown.Show();
|
||||
refreshButton.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
categoryDropdown.Hide();
|
||||
refreshButton.Hide();
|
||||
}
|
||||
}, true);
|
||||
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ShowMenuTips,
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuTips)
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuTips),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@@ -104,6 +94,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
backgroundToggle,
|
||||
categoryDropdown,
|
||||
refreshButton,
|
||||
new SettingsColour
|
||||
{
|
||||
LabelText = @"osu! logo colour",
|
||||
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
|
||||
ClassicDefault = Colour4.FromHex(@"ff66ba"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ForceLegacySongSelect,
|
||||
Current = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect),
|
||||
ClassicDefault = false
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -14,8 +13,8 @@ using osu.Framework.Graphics.Rendering.Vertices;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Shaders.Types;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.OpenGL.Vertices;
|
||||
@@ -85,7 +84,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
flashlight.Colour = Color4.Black;
|
||||
|
||||
flashlight.Combo.BindTo(Combo);
|
||||
flashlight.GetPlayfieldScale = () => drawableRuleset.PlayfieldAdjustmentContainer.Scale;
|
||||
|
||||
var playfieldDrawInfoTracker = new PlayfieldDrawInfoTracker();
|
||||
|
||||
drawableRuleset.PlayfieldAdjustmentContainer.Add(playfieldDrawInfoTracker);
|
||||
flashlight.PlayfieldDrawInfoTracker = playfieldDrawInfoTracker;
|
||||
|
||||
drawableRuleset.Overlays.Add(new Container
|
||||
{
|
||||
@@ -111,7 +114,9 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
|
||||
internal Func<Vector2>? GetPlayfieldScale;
|
||||
internal PlayfieldDrawInfoTracker PlayfieldDrawInfoTracker { get; set; } = null!;
|
||||
|
||||
private DrawInfo playfieldDrawInfo => PlayfieldDrawInfoTracker.DrawInfo;
|
||||
|
||||
private readonly float defaultFlashlightSize;
|
||||
private readonly float sizeMultiplier;
|
||||
@@ -146,6 +151,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
isBreakTime.BindTo(player.IsBreakTime);
|
||||
isBreakTime.BindValueChanged(_ => UpdateFlashlightSize(GetSize()), true);
|
||||
}
|
||||
|
||||
PlayfieldDrawInfoTracker.OnDrawInfoInvalidate += () => Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
protected abstract void UpdateFlashlightSize(float size);
|
||||
@@ -156,15 +163,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
float size = defaultFlashlightSize * sizeMultiplier;
|
||||
|
||||
if (GetPlayfieldScale != null)
|
||||
{
|
||||
Vector2 playfieldScale = GetPlayfieldScale();
|
||||
|
||||
Debug.Assert(Precision.AlmostEquals(Math.Abs(playfieldScale.X), Math.Abs(playfieldScale.Y)),
|
||||
@"Playfield has non-proportional scaling. Flashlight implementations should be revisited with regard to balance.");
|
||||
size *= Math.Abs(playfieldScale.X);
|
||||
}
|
||||
|
||||
if (isBreakTime.Value)
|
||||
size *= 2.5f;
|
||||
else if (comboBasedSize)
|
||||
@@ -265,7 +263,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
shader = Source.shader;
|
||||
screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad;
|
||||
flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix);
|
||||
flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy;
|
||||
|
||||
// scale the flashlight based on the playfield to match gameplay components scale.
|
||||
Vector2 drawInfoScale = Source.playfieldDrawInfo.Matrix.ExtractScale().Xy;
|
||||
flashlightSize = Source.FlashlightSize * drawInfoScale;
|
||||
|
||||
flashlightDim = Source.FlashlightDim;
|
||||
flashlightSmoothness = Source.flashlightSmoothness;
|
||||
}
|
||||
@@ -321,5 +323,33 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The purpose of this component is to track any changes to <c>Playfield.Parent.DrawInfo</c>
|
||||
/// (by being added to the content of <see cref="PlayfieldAdjustmentContainer"/>).
|
||||
/// All in order for the flashlight to invalidate its draw node and read any changes in the playfield's scaling.
|
||||
/// </summary>
|
||||
internal partial class PlayfieldDrawInfoTracker : Component
|
||||
{
|
||||
private readonly LayoutValue drawInfoLayout = new LayoutValue(Invalidation.DrawInfo);
|
||||
|
||||
public Action? OnDrawInfoInvalidate;
|
||||
|
||||
public PlayfieldDrawInfoTracker()
|
||||
{
|
||||
AddLayout(drawInfoLayout);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!drawInfoLayout.IsValid)
|
||||
{
|
||||
OnDrawInfoInvalidate?.Invoke();
|
||||
drawInfoLayout.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Rulesets;
|
||||
@@ -1312,6 +1313,8 @@ namespace osu.Game.Screens.Edit
|
||||
yield return upload;
|
||||
}
|
||||
|
||||
yield return new EditorMenuItem("Remove all online IDs", MenuItemType.Destructive, anonymizeBeatmap);
|
||||
|
||||
if (editorBeatmap.BeatmapInfo.OnlineID > 0)
|
||||
{
|
||||
yield return new OsuMenuItemSpacer();
|
||||
@@ -1396,6 +1399,14 @@ namespace osu.Game.Screens.Edit
|
||||
void startSubmission() => this.Push(new BeatmapSubmissionScreen());
|
||||
}
|
||||
|
||||
private void anonymizeBeatmap()
|
||||
{
|
||||
dialogOverlay.Push(new ConfirmDialog(
|
||||
"Really remove online IDs?",
|
||||
() => playableBeatmap.BeatmapInfo.ResetOnlineInfo(true)
|
||||
));
|
||||
}
|
||||
|
||||
private void exportBeatmap(bool legacy)
|
||||
{
|
||||
if (HasUnsavedChanges)
|
||||
|
||||
@@ -39,6 +39,7 @@ using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Seasonal;
|
||||
using osuTK;
|
||||
@@ -118,12 +119,15 @@ namespace osu.Game.Screens.Menu
|
||||
[CanBeNull]
|
||||
private IDisposable logoProxy;
|
||||
|
||||
private Bindable<bool> forceSSV1;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio)
|
||||
{
|
||||
holdDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
|
||||
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
|
||||
showMobileDisclaimer = config.GetBindable<bool>(OsuSetting.ShowMobileDisclaimer);
|
||||
forceSSV1 = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect);
|
||||
|
||||
if (host.CanExit)
|
||||
{
|
||||
@@ -479,7 +483,7 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
}
|
||||
|
||||
private void loadSongSelect() => this.Push(new SoloSongSelect());
|
||||
private void loadSongSelect() => this.Push(forceSSV1.Value ? new PlaySongSelect() : new SoloSongSelect());
|
||||
|
||||
private partial class MobileDisclaimerDialog : PopupDialog
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@@ -20,6 +21,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
@@ -63,7 +65,10 @@ namespace osu.Game.Screens.Menu
|
||||
protected Sample SampleDownbeat;
|
||||
|
||||
private readonly Container colourAndTriangles;
|
||||
private readonly TrianglesV2 triangles;
|
||||
private Box colourBox;
|
||||
private TrianglesV2 triangles;
|
||||
|
||||
private Bindable<Colour4> logoColour;
|
||||
|
||||
/// <summary>
|
||||
/// Return value decides whether the logo should play its own sample for the click action.
|
||||
@@ -186,10 +191,13 @@ namespace osu.Game.Screens.Menu
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
colourBox = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"cc5289")),
|
||||
Colour = ColourInfo.GradientVertical(
|
||||
Color4Extensions.FromHex(@"ff66ba"), // original osu! cookie pink
|
||||
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 0.5f)
|
||||
),
|
||||
},
|
||||
triangles = new TrianglesV2
|
||||
{
|
||||
@@ -198,7 +206,10 @@ namespace osu.Game.Screens.Menu
|
||||
Thickness = 0.009f,
|
||||
ScaleAdjust = 3,
|
||||
SpawnRatio = 1.4f,
|
||||
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"b6346f")),
|
||||
Colour = ColourInfo.GradientVertical(
|
||||
Color4Extensions.FromHex(@"ff66ba"),
|
||||
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 1.0f)
|
||||
),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
@@ -249,6 +260,22 @@ namespace osu.Game.Screens.Menu
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateColour()
|
||||
{
|
||||
if (triangles == null || colourBox == null)
|
||||
return; // we're still loading
|
||||
|
||||
colourBox.Colour = ColourInfo.GradientVertical(
|
||||
logoColour.Value,
|
||||
Color4Extensions.Darken(logoColour.Value, 0.5f)
|
||||
);
|
||||
|
||||
triangles.Colour = ColourInfo.GradientVertical(
|
||||
logoColour.Value,
|
||||
Color4Extensions.Darken(logoColour.Value, 1.0f)
|
||||
);
|
||||
}
|
||||
|
||||
public Container LogoElements { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -276,7 +303,7 @@ namespace osu.Game.Screens.Menu
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures, AudioManager audio)
|
||||
private void load(TextureStore textures, AudioManager audio, OsuConfigManager config)
|
||||
{
|
||||
sampleClick = audio.Samples.Get(@"Menu/osu-logo-select");
|
||||
|
||||
@@ -285,6 +312,10 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
logo.Texture = textures.Get(@"Menu/logo");
|
||||
ripple.Texture = textures.Get(@"Menu/logo");
|
||||
|
||||
logoColour = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor);
|
||||
logoColour.BindValueChanged(_ => UpdateColour());
|
||||
UpdateColour();
|
||||
}
|
||||
|
||||
private int lastBeatIndex;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// 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 disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -31,17 +29,19 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private readonly Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore;
|
||||
|
||||
private PlaybackSettings playbackSettings;
|
||||
|
||||
[Cached(typeof(IGameplayLeaderboardProvider))]
|
||||
private readonly SoloGameplayLeaderboardProvider leaderboardProvider = new SoloGameplayLeaderboardProvider();
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.WatchingReplay(Score.ScoreInfo);
|
||||
protected override UserActivity? InitialActivity =>
|
||||
// score may be null if LoadedBeatmapSuccessfully is false.
|
||||
Score == null ? null : new UserActivity.WatchingReplay(Score.ScoreInfo);
|
||||
|
||||
private bool isAutoplayPlayback => GameplayState.Mods.OfType<ModAutoplay>().Any();
|
||||
|
||||
private double? lastFrameTime;
|
||||
private ReplayFailIndicator failIndicator;
|
||||
|
||||
private ReplayFailIndicator? failIndicator;
|
||||
private PlaybackSettings? playbackSettings;
|
||||
|
||||
protected override bool CheckModsAllowFailure()
|
||||
{
|
||||
@@ -60,12 +60,12 @@ namespace osu.Game.Screens.Play
|
||||
return false;
|
||||
}
|
||||
|
||||
public ReplayPlayer(Score score, PlayerConfiguration configuration = null)
|
||||
public ReplayPlayer(Score score, PlayerConfiguration? configuration = null)
|
||||
: this((_, _) => score, configuration)
|
||||
{
|
||||
}
|
||||
|
||||
public ReplayPlayer(Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore, PlayerConfiguration configuration = null)
|
||||
public ReplayPlayer(Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore, PlayerConfiguration? configuration = null)
|
||||
: base(configuration)
|
||||
{
|
||||
this.createScore = createScore;
|
||||
@@ -133,6 +133,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.StepReplayBackward:
|
||||
@@ -144,11 +147,11 @@ namespace osu.Game.Screens.Play
|
||||
return true;
|
||||
|
||||
case GlobalAction.SeekReplayBackward:
|
||||
SeekInDirection(-5 * (float)playbackSettings.UserPlaybackRate.Value);
|
||||
SeekInDirection(-5 * (float)playbackSettings!.UserPlaybackRate.Value);
|
||||
return true;
|
||||
|
||||
case GlobalAction.SeekReplayForward:
|
||||
SeekInDirection(5 * (float)playbackSettings.UserPlaybackRate.Value);
|
||||
SeekInDirection(5 * (float)playbackSettings!.UserPlaybackRate.Value);
|
||||
return true;
|
||||
|
||||
case GlobalAction.TogglePauseReplay:
|
||||
@@ -192,7 +195,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
// base logic intentionally suppressed - we have our own custom fail interaction
|
||||
ScoreProcessor.FailScore(Score.ScoreInfo);
|
||||
failIndicator.Display();
|
||||
failIndicator!.Display();
|
||||
}
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
@@ -204,18 +207,18 @@ namespace osu.Game.Screens.Play
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
{
|
||||
// safety against filters or samples from the indicator playing long after the screen is exited
|
||||
failIndicator.RemoveAndDisposeImmediately();
|
||||
failIndicator?.RemoveAndDisposeImmediately();
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
private void stopAllAudioEffects()
|
||||
{
|
||||
// safety against filters or samples from the indicator playing long after the screen is exited
|
||||
failIndicator.RemoveAndDisposeImmediately();
|
||||
failIndicator?.RemoveAndDisposeImmediately();
|
||||
|
||||
if (GameplayClockContainer is MasterGameplayClockContainer master)
|
||||
{
|
||||
playbackSettings.UserPlaybackRate.UnbindFrom(master.UserPlaybackRate);
|
||||
playbackSettings?.UserPlaybackRate.UnbindFrom(master.UserPlaybackRate);
|
||||
master.UserPlaybackRate.SetDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,11 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
var scoresToShow = new List<GameplayLeaderboardScore>();
|
||||
|
||||
var scoresRequest = new IndexPlaylistScoresRequest(room.RoomID!.Value, playlistItem.ID);
|
||||
scoresRequest.Success += response =>
|
||||
api.Perform(scoresRequest);
|
||||
|
||||
var response = scoresRequest.Response;
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
isPartial = response.Scores.Count < response.TotalScores;
|
||||
|
||||
@@ -50,8 +54,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
if (response.UserScore != null && response.Scores.All(s => s.ID != response.UserScore.ID))
|
||||
scoresToShow.Add(new GameplayLeaderboardScore(response.UserScore, tracked: false, GameplayLeaderboardScore.ComboDisplayMode.Highest));
|
||||
};
|
||||
api.Perform(scoresRequest);
|
||||
}
|
||||
|
||||
if (gameplayState != null)
|
||||
{
|
||||
@@ -62,8 +65,12 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
// touching the public bindable must happen on the update thread for general thread safety,
|
||||
// since we may have external subscribers bound already
|
||||
Schedule(() => scores.AddRange(scoresToShow));
|
||||
Scheduler.AddDelayed(sort, 1000, true);
|
||||
Schedule(() =>
|
||||
{
|
||||
scores.AddRange(scoresToShow);
|
||||
sort();
|
||||
Scheduler.AddDelayed(sort, 1000, true);
|
||||
});
|
||||
}
|
||||
|
||||
// logic shared with SoloGameplayLeaderboardProvider
|
||||
|
||||
166
osu.Game/Screens/Select/PlaySongSelect.cs
Normal file
166
osu.Game/Screens/Select/PlaySongSelect.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
public partial class PlaySongSelect : SongSelect
|
||||
{
|
||||
private OsuScreen? playerLoader;
|
||||
|
||||
[Resolved]
|
||||
private INotificationOverlay? notifications { get; set; }
|
||||
|
||||
public override bool AllowExternalScreenChange => true;
|
||||
|
||||
public override MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(Func<BeatmapInfo> getBeatmap) => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem(ButtonSystemStrings.Play.ToSentence(), MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())),
|
||||
new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => Edit(getBeatmap()))
|
||||
};
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap();
|
||||
|
||||
private PlayBeatmapDetailArea playBeatmapDetailArea = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BeatmapOptions.AddButton(ButtonSystemStrings.Edit.ToSentence(), @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit());
|
||||
|
||||
AddInternal(new SongSelectTouchInputDetector());
|
||||
}
|
||||
|
||||
protected void PresentScore(ScoreInfo score) =>
|
||||
FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score)));
|
||||
|
||||
protected override BeatmapDetailArea CreateBeatmapDetailArea()
|
||||
{
|
||||
playBeatmapDetailArea = new PlayBeatmapDetailArea
|
||||
{
|
||||
Leaderboard =
|
||||
{
|
||||
ScoreSelected = PresentScore
|
||||
}
|
||||
};
|
||||
|
||||
return playBeatmapDetailArea;
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
case Key.KeypadEnter:
|
||||
// this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is
|
||||
// matching with exact modifier consideration (so Ctrl+Enter would be ignored).
|
||||
FinaliseSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
private IReadOnlyList<Mod>? modsAtGameplayStart;
|
||||
|
||||
private ModAutoplay? getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||
|
||||
protected override bool OnStart()
|
||||
{
|
||||
if (playerLoader != null) return false;
|
||||
|
||||
modsAtGameplayStart = Mods.Value.Select(m => m.DeepClone()).ToArray();
|
||||
|
||||
// Ctrl+Enter should start map with autoplay enabled.
|
||||
if (GetContainingInputManager()?.CurrentState?.Keyboard.ControlPressed == true)
|
||||
{
|
||||
var autoInstance = getAutoplayMod();
|
||||
|
||||
if (autoInstance == null)
|
||||
{
|
||||
notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = NotificationsStrings.NoAutoplayMod
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
var mods = Mods.Value.Append(autoInstance).ToArray();
|
||||
|
||||
if (!ModUtils.CheckCompatibleSet(mods, out var invalid))
|
||||
mods = mods.Except(invalid).Append(autoInstance).ToArray();
|
||||
|
||||
Mods.Value = mods;
|
||||
}
|
||||
|
||||
SampleConfirm?.Play();
|
||||
|
||||
this.Push(playerLoader = new PlayerLoader(createPlayer));
|
||||
return true;
|
||||
|
||||
Player createPlayer()
|
||||
{
|
||||
Player player;
|
||||
|
||||
var replayGeneratingMod = Mods.Value.OfType<ICreateReplayData>().FirstOrDefault();
|
||||
|
||||
if (replayGeneratingMod != null)
|
||||
{
|
||||
player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods));
|
||||
}
|
||||
else
|
||||
{
|
||||
player = new SoloPlayer();
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnResuming(e);
|
||||
revertMods();
|
||||
}
|
||||
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
{
|
||||
if (base.OnExiting(e))
|
||||
return true;
|
||||
|
||||
revertMods();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void revertMods()
|
||||
{
|
||||
if (playerLoader == null) return;
|
||||
|
||||
Mods.Value = modsAtGameplayStart;
|
||||
playerLoader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,21 @@ namespace osu.Game.Screens.SelectV2
|
||||
get => working;
|
||||
set
|
||||
{
|
||||
if (value == working)
|
||||
if (working == null && value == null)
|
||||
return;
|
||||
|
||||
// this guard papers over excessive refreshes of the background asset which occur if `working == value` type guards are used.
|
||||
// the root cause of why `working == value` type guards fail here is that `SongSelect` will invalidate working beatmaps very often
|
||||
// (via https://github.com/ppy/osu/blob/d3ae20dd882381e109c20ca00ee5237e4dd1750d/osu.Game/Screens/SelectV2/SongSelect.cs#L506-L507),
|
||||
// due to a variety of causes, ranging from "someone typed a letter in the search box" (which triggers a refilter -> presentation of new items -> `ensureGlobalBeatmapValid()`),
|
||||
// to "someone just went into the editor and replaced every single file in the set, including the background".
|
||||
// the following guard approximates the most appropriate debounce criterion, which is the contents of the actual asset that is supposed to be displayed in the background,
|
||||
// i.e. if the hash of the new background file matches the old, then we do not bother updating the working beatmap here.
|
||||
//
|
||||
// note that this is basically a reimplementation of the caching scheme in `WorkingBeatmapCache.getBackgroundFromStore()`,
|
||||
// which cannot be used directly by retrieving the texture and checking texture reference equality,
|
||||
// because missing the cache would incur a synchronous texture load on the update thread.
|
||||
if (getBackgroundFileHash(working) == getBackgroundFileHash(value))
|
||||
return;
|
||||
|
||||
working = value;
|
||||
@@ -52,6 +66,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
}
|
||||
}
|
||||
|
||||
private static string? getBackgroundFileHash(WorkingBeatmap? working)
|
||||
=> working?.BeatmapSetInfo.GetFile(working.Metadata.BackgroundFile)?.File.Hash;
|
||||
|
||||
public PanelSetBackground()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Reference in New Issue
Block a user