Compare commits
27 Commits
2025.825.0
...
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 | ||
|
|
3cca458c21 | ||
|
|
bc59270f3e | ||
|
|
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()
|
||||
{
|
||||
|
||||
@@ -198,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);
|
||||
|
||||
@@ -442,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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -5,16 +5,13 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
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
|
||||
{
|
||||
@@ -29,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>
|
||||
@@ -58,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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace osu.Game.Screens.Menu
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(
|
||||
Color4Extensions.FromHex(@"ff66ba"), // original osu! cookie pink
|
||||
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 1.0f)
|
||||
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 0.5f)
|
||||
),
|
||||
},
|
||||
triangles = new TrianglesV2
|
||||
@@ -208,7 +208,7 @@ namespace osu.Game.Screens.Menu
|
||||
SpawnRatio = 1.4f,
|
||||
Colour = ColourInfo.GradientVertical(
|
||||
Color4Extensions.FromHex(@"ff66ba"),
|
||||
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 2.5f)
|
||||
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 1.0f)
|
||||
),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
@@ -260,19 +260,20 @@ namespace osu.Game.Screens.Menu
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateColour() {
|
||||
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)
|
||||
);
|
||||
|
||||
colourBox.Colour = ColourInfo.GradientVertical(
|
||||
logoColour.Value,
|
||||
Color4Extensions.Darken(logoColour.Value, 2.5f)
|
||||
);
|
||||
}
|
||||
|
||||
public Container LogoElements { get; private set; }
|
||||
@@ -313,6 +314,8 @@ namespace osu.Game.Screens.Menu
|
||||
ripple.Texture = textures.Get(@"Menu/logo");
|
||||
|
||||
logoColour = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor);
|
||||
logoColour.BindValueChanged(_ => UpdateColour());
|
||||
UpdateColour();
|
||||
}
|
||||
|
||||
private int lastBeatIndex;
|
||||
@@ -393,7 +396,6 @@ namespace osu.Game.Screens.Menu
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
UpdateColour();
|
||||
|
||||
const float scale_adjust_cutoff = 0.4f;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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