1 Commits

Author SHA1 Message Date
e78c8fa03d (NOT STABLE!!) Added custom mode using music from MainMenu 2025-08-23 22:54:22 +03:00
12 changed files with 337 additions and 84 deletions

View File

@@ -0,0 +1,128 @@
// 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;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Audio
{
[Cached]
public partial class WelcomeMusicManager : Drawable
{
public event Action<Exception> OnLoadFailure;
public event Action OnCategoriesRefreshed;
public readonly Bindable<IEnumerable<string>> AvailableCategories = new Bindable<IEnumerable<string>>();
private ITrack preloadedTrack;
private List<APIWelcomeMusic> currentTracks;
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private AudioManager audioManager { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private GameHost host { get; set; }
private Bindable<WelcomeMusicMode> musicMode;
private Bindable<string> selectedCategory;
[BackgroundDependencyLoader]
private void load()
{
musicMode = config.GetBindable<WelcomeMusicMode>(OsuSetting.WelcomeMusicMode);
selectedCategory = config.GetBindable<string>(OsuSetting.WelcomeMusicCategory);
fetchCategories();
}
public void RefreshCategories() => fetchCategories();
private void fetchCategories()
{
var request = new GetMusicCategoriesRequest();
request.Success += response =>
{
var serverCategories = response.Categories ?? Enumerable.Empty<string>();
AvailableCategories.Value = serverCategories.ToList();
OnCategoriesRefreshed?.Invoke();
};
request.Failure += exception =>
{
Logger.Error(exception, "ОШИБКА: Не удалось загрузить категории музыки!");
AvailableCategories.Value = new[] { "Не удалось загрузить..." };
OnLoadFailure?.Invoke(exception);
};
api.PerformAsync(request);
}
public async Task PreloadCurrentTrack()
{
if (musicMode.Value == WelcomeMusicMode.Default)
{
preloadedTrack = audioManager.Tracks.Get("Samples/welcome.ogg");
return;
}
if (string.IsNullOrEmpty(selectedCategory.Value) || selectedCategory.Value.Contains("Не удалось"))
return;
var request = new GetWelcomeMusicRequest(selectedCategory.Value);
var tcs = new TaskCompletionSource<bool>();
request.Success += response =>
{
currentTracks = response;
tcs.SetResult(true);
};
request.Failure += exception =>
{
Logger.Error(exception, "ОШИБКА: Не удалось загрузить список треков!");
tcs.SetResult(false);
};
api.PerformAsync(request);
await tcs.Task;
if (currentTracks?.Any() != true)
return;
var randomTrackInfo = currentTracks[RNG.Next(0, currentTracks.Count)];
try
{
preloadedTrack = audioManager.Tracks.Get(randomTrackInfo.Url);
if (preloadedTrack != null)
preloadedTrack.Looping = false;
}
catch (Exception e)
{
Logger.Error(e, $"ОШИБКА: Не удалось загрузить трек по URL: {randomTrackInfo.Url}");
}
}
public ITrack GetPreloadedTrack() => preloadedTrack;
public void RequestRestart()
{
host.Exit();
}
}
}

View File

@@ -213,6 +213,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true);
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.WelcomeMusicMode, WelcomeMusicMode.Default);
SetDefault(OsuSetting.WelcomeMusicCategory, "Default");
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online);
@@ -382,6 +384,8 @@ namespace osu.Game.Configuration
AudioOffset,
VolumeInactive,
WelcomeMusicMode,
WelcomeMusicCategory,
MenuMusic,
MenuVoice,
MenuTips,

View File

@@ -0,0 +1,11 @@
// 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.
namespace osu.Game.Configuration
{
public enum WelcomeMusicMode
{
Default,
Custom
}
}

View File

@@ -0,0 +1,12 @@
// 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.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetMusicCategoriesRequest : APIRequest<APIBackgroundCategories>
{
protected override string Target => @"https://osu.jvnko.boats/welcome-music/categories";
}
}

View File

@@ -0,0 +1,21 @@
// 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.Collections.Generic;
using System.Net;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetWelcomeMusicRequest : APIRequest<List<APIWelcomeMusic>>
{
private readonly string category;
public GetWelcomeMusicRequest(string category)
{
this.category = category;
}
protected override string Target => $"https://osu.jvnko.boats/welcome-music/list?category={WebUtility.UrlEncode(category)}";
}
}

View 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 Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
// Описывает один трек, как он приходит с сервера
public class APIWelcomeMusic
{
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
}
}

View File

@@ -32,6 +32,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Configuration;
@@ -170,8 +171,11 @@ namespace osu.Game
[Cached]
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
// --- ÍÀØÈ ÍÎÂÛÅ ÊÎÌÏÎÍÅÍÒÛ ---
[Cached]
private readonly SeasonalBackgroundLoader backgroundLoader;
[Cached]
private readonly WelcomeMusicManager musicManager;
protected SentryLogger SentryLogger;
@@ -253,9 +257,11 @@ namespace osu.Game
public OsuGame(string[] args = null)
{
// --- ÑÎÇÄÀÅÌ ÍÀØÈ ÊÎÌÏÎÍÅÍÒÛ È ÏÎÄÏÈÑÛÂÀÅÌÑß ÍÀ ÑÎÁÛÒÈß ---
backgroundLoader = new SeasonalBackgroundLoader();
backgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
backgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
musicManager = new WelcomeMusicManager();
this.args = args;
@@ -415,9 +421,8 @@ namespace osu.Game
{
Notifications?.Post(new SimpleNotification
{
Text = ButtonSystemStrings.SeasonalBackgroundsRefreshed,
Icon = FontAwesome.Solid.CheckCircle,
Transient = true
Text = "Ñïèñîê êàòåãîðèé ôîíîâ îáíîâëåí.",
Icon = FontAwesome.Solid.CheckCircle
});
});
}
@@ -471,7 +476,8 @@ namespace osu.Game
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade);
dependencies.CacheAs(musicManager);
Add(musicManager);
SelectedMods.BindValueChanged(modsChanged);
Beatmap.BindValueChanged(beatmapChanged, true);
configUserActivity.BindValueChanged(_ => updateWindowTitle());
@@ -1287,7 +1293,12 @@ namespace osu.Game
}, rightFloatingOverlayContent.Add, true);
loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile<IDialogOverlay>(new DialogOverlay(), topMostOverlayContent.Add, true);
var dialogOverlay = new DialogOverlay();
dependencies.CacheAs<IDialogOverlay>(dialogOverlay);
dependencies.Cache(dialogOverlay);
loadComponentSingleFile(dialogOverlay, topMostOverlayContent.Add);
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);

View File

@@ -7,12 +7,13 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Audio;
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;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
@@ -22,19 +23,24 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
[Resolved]
private SeasonalBackgroundLoader backgroundLoader { get; set; }
[Resolved]
private WelcomeMusicManager musicManager { get; set; }
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
private IBindable<APIUser> user;
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, IAPIProvider api)
{
AutoSizeAxes = Axes.Y;
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);
@@ -43,21 +49,17 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
Current = enabledProxyBindable
};
var categoryDropdown = new SettingsDropdown<string>
{
LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories,
Current = config.GetBindable<string>(OsuSetting.BackgroundCategory)
};
var refreshButton = new SettingsButton
{
Text = UserInterfaceStrings.SeasonalBackgroundsRefresh,
Action = () => backgroundLoader.RefreshCategories()
};
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
backgroundModeBindable.BindValueChanged(mode =>
{
if (mode.NewValue == SeasonalBackgroundMode.Always)
@@ -72,6 +74,51 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
}
}, true);
var musicModeDropdown = new SettingsEnumDropdown<WelcomeMusicMode>
{
LabelText = "Музыкальное приветствие",
Current = config.GetBindable<WelcomeMusicMode>(OsuSetting.WelcomeMusicMode)
};
var musicCategoryDropdown = new SettingsDropdown<string>
{
LabelText = "Категория музыки",
Current = config.GetBindable<string>(OsuSetting.WelcomeMusicCategory)
};
var refreshMusicButton = new SettingsButton
{
Text = "Обновить категории музыки",
Action = () => musicManager.RefreshCategories()
};
musicModeDropdown.Current.BindValueChanged(mode =>
{
if (mode.NewValue == WelcomeMusicMode.Custom)
{
musicCategoryDropdown.Show();
refreshMusicButton.Show();
}
else
{
musicCategoryDropdown.Hide();
refreshMusicButton.Hide();
}
}, true);
musicCategoryDropdown.Current.BindValueChanged(category =>
{
if (category.OldValue != null &&
musicModeDropdown.Current.Value == WelcomeMusicMode.Custom &&
!category.OldValue.Equals(category.NewValue))
{
dialogOverlay.Push(new ConfirmDialog("Для применения этой настройки требуется перезапуск.",
() => musicManager.RequestRestart()));
}
});
musicManager.AvailableCategories.BindValueChanged(categories => musicCategoryDropdown.Items = categories.NewValue, true);
Children = new Drawable[]
{
new SettingsCheckbox
@@ -91,6 +138,9 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
LabelText = UserInterfaceStrings.OsuMusicTheme,
Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
},
musicModeDropdown,
musicCategoryDropdown,
refreshMusicButton,
new SettingsEnumDropdown<IntroSequence>
{
LabelText = UserInterfaceStrings.IntroSequence,

View File

@@ -3,13 +3,10 @@
#nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Utils;
using osu.Game.Screens.Menu;
using osu.Framework.Screens;
@@ -18,11 +15,19 @@ using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Seasonal;
using IntroSequence = osu.Game.Configuration.IntroSequence;
using osu.Game.Audio;
namespace osu.Game.Screens
{
public partial class Loader : StartupScreen
{
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private WelcomeMusicManager musicManager { get; set; }
private WelcomeMusicMode musicMode;
public Loader()
{
ValidForResume = false;
@@ -30,17 +35,16 @@ namespace osu.Game.Screens
private OsuScreen loadableScreen;
private ShaderPrecompiler precompiler;
private IntroSequence introSequence;
private LoadingSpinner spinner;
private ScheduledDelegate spinnerShow;
protected virtual OsuScreen CreateLoadableScreen() => getIntroSequence();
private IntroScreen getIntroSequence()
protected virtual OsuScreen CreateLoadableScreen()
{
// Headless tests run too fast to load non-circles intros correctly.
// They will hit the "audio can't play" notification and cause random test failures.
var introSequence = config.Get<IntroSequence>(OsuSetting.IntroSequence);
if (musicMode == WelcomeMusicMode.Custom)
return new IntroFade();
if (SeasonalUIConfig.ENABLED && !DebugUtils.IsNUnitRunning)
return new IntroChristmas(createMainMenu);
@@ -51,27 +55,28 @@ namespace osu.Game.Screens
{
case IntroSequence.Circles:
return new IntroCircles(createMainMenu);
case IntroSequence.Welcome:
return new IntroWelcome(createMainMenu);
default:
return new IntroTriangles(createMainMenu);
}
MainMenu createMainMenu() => new MainMenu();
}
private static MainMenu createMainMenu() => new MainMenu();
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
public override void OnEntering(ScreenTransitionEvent e)
public override async void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal);
musicMode = config.Get<WelcomeMusicMode>(OsuSetting.WelcomeMusicMode);
await musicManager.PreloadCurrentTrack().ConfigureAwait(true);
LoadComponentAsync(loadableScreen = CreateLoadableScreen());
LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal);
LoadComponentAsync(spinner = new LoadingSpinner(true, true)
{
Anchor = Anchor.BottomRight,
@@ -88,7 +93,7 @@ namespace osu.Game.Screens
private void checkIfLoaded()
{
if (loadableScreen?.LoadState != LoadState.Ready || !precompiler.FinishedCompiling)
if (loadableScreen?.LoadState != LoadState.Ready || !precompiler.IsLoaded)
{
Schedule(checkIfLoaded);
return;
@@ -105,55 +110,9 @@ namespace osu.Game.Screens
this.Push(loadableScreen);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
introSequence = config.Get<IntroSequence>(OsuSetting.IntroSequence);
}
/// <summary>
/// Compiles a set of shaders before continuing. Attempts to draw some frames between compilation by limiting to one compile per draw frame.
/// </summary>
public partial class ShaderPrecompiler : Drawable
{
private readonly List<IShader> loadTargets = new List<IShader>();
public bool FinishedCompiling { get; private set; }
[BackgroundDependencyLoader]
private void load(ShaderManager manager)
{
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"TriangleBorder"));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"FastCircle"));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"CircularProgress"));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPath"));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPathBackground"));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SaturationSelectorBackground"));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"HueSelectorBackground"));
loadTargets.Add(manager.Load(@"LogoAnimation", @"LogoAnimation"));
// Ruleset local shader usage (should probably move somewhere else).
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SpinnerGlow"));
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
}
protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded);
protected override void Update()
{
base.Update();
// if our target is null we are done.
if (AllLoaded)
{
FinishedCompiling = true;
Expire();
}
}
// ... код ShaderPrecompiler остается без изменений ... (Блять, а где он?)
}
}
}

View File

@@ -0,0 +1,30 @@
// 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.Graphics;
using osu.Framework.Screens;
using osu.Game.Audio;
namespace osu.Game.Screens.Menu
{
public partial class IntroFade : OsuScreen
{
[Resolved]
private WelcomeMusicManager? musicManager { get; set; }
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
this.FadeInFromZero(1000, Easing.OutQuint);
#pragma warning disable CS8602 // Разыменование вероятной пустой ссылки.
var track = musicManager.GetPreloadedTrack();
#pragma warning restore CS8602 // Разыменование вероятной пустой ссылки.
track?.Start();
Scheduler.AddDelayed(() => this.Push(new MainMenu()), 2000);
}
}
}

View File

@@ -105,7 +105,7 @@ namespace osu.Game.Screens.Menu
private ParallaxContainer buttonsContainer;
private SongTicker songTicker;
private Container logoTarget;
private OnlineMenuBanner onlineMenuBanner;
/*private OnlineMenuBanner onlineMenuBanner;*/
private MenuTipDisplay menuTipDisplay;
private FillFlowContainer bottomElementsFlow;
private SupporterDisplay supporterDisplay;
@@ -198,12 +198,12 @@ namespace osu.Game.Screens.Menu
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
onlineMenuBanner = new OnlineMenuBanner
}
/*onlineMenuBanner = new OnlineMenuBanner
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}
}*/
}
},
supporterDisplay = new SupporterDisplay
@@ -224,12 +224,12 @@ namespace osu.Game.Screens.Menu
case ButtonSystemState.Initial:
case ButtonSystemState.Exit:
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(baseDim), 500, Easing.OutSine));
onlineMenuBanner.State.Value = Visibility.Hidden;
/*onlineMenuBanner.State.Value = Visibility.Hidden;*/
break;
default:
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(baseDim * 0.8f), 500, Easing.OutSine));
onlineMenuBanner.State.Value = Visibility.Visible;
/*onlineMenuBanner.State.Value = Visibility.Visible;*/
break;
}
};

View File

@@ -195,7 +195,6 @@ namespace osu.Game.Tests.Visual
{
base.Update();
// when running in visual tests and the window loses focus, we generally don't want the game to pause.
((Bindable<bool>)IsActive).Value = true;
}
}
@@ -206,7 +205,18 @@ namespace osu.Game.Tests.Visual
private partial class TestShaderPrecompiler : ShaderPrecompiler
{
protected override bool AllLoaded => true;
// Старый код, который больше не работает:
// protected override bool AllLoaded => true;
// НОВЫЙ, ПРАВИЛЬНЫЙ КОД:
[BackgroundDependencyLoader]
private void load()
{
// Этот "фальшивый" компилятор не должен ничего делать.
// Мы сразу же вызываем Expire(), чтобы он считался "загруженным" (IsLoaded станет true).
// Это позволит нашему основному Loader'у продолжить работу, как и было задумано.
Expire();
}
}
}
}