2019-01-24 17:43:03 +09:00
// 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.
2018-04-13 18:19:50 +09:00
2022-06-17 16:37:17 +09:00
#nullable disable
2017-03-06 18:36:46 +09:00
using System ;
2018-02-15 14:19:16 +09:00
using System.Collections.Generic ;
2022-09-22 12:52:24 +09:00
using System.IO ;
2018-02-15 14:19:16 +09:00
using System.Linq ;
2017-03-06 17:09:48 +09:00
using System.Reflection ;
2021-10-12 16:04:09 +09:00
using System.Threading ;
2022-05-10 10:02:32 +03:00
using System.Threading.Tasks ;
2016-11-08 18:13:20 -05:00
using osu.Framework.Allocation ;
2017-11-26 17:07:35 +09:00
using osu.Framework.Audio ;
2022-05-22 22:15:53 +09:00
using osu.Framework.Audio.Track ;
2019-02-21 19:04:31 +09:00
using osu.Framework.Bindables ;
2023-06-08 02:47:07 +03:00
using osu.Framework.Configuration ;
2019-06-20 12:48:45 +09:00
using osu.Framework.Development ;
2020-07-29 12:39:18 +09:00
using osu.Framework.Extensions ;
2016-09-30 18:45:27 +09:00
using osu.Framework.Graphics ;
2016-09-24 00:39:20 +09:00
using osu.Framework.Graphics.Containers ;
2017-12-31 06:40:28 +09:00
using osu.Framework.Graphics.Textures ;
2018-07-09 15:26:22 +09:00
using osu.Framework.Input ;
2022-01-14 23:16:07 +01:00
using osu.Framework.Input.Handlers ;
2022-10-21 01:52:19 +03:00
using osu.Framework.Input.Handlers.Joystick ;
2022-01-14 23:16:07 +01:00
using osu.Framework.Input.Handlers.Midi ;
2022-10-21 01:52:19 +03:00
using osu.Framework.Input.Handlers.Mouse ;
using osu.Framework.Input.Handlers.Tablet ;
using osu.Framework.Input.Handlers.Touch ;
2021-10-01 00:27:54 +09:00
using osu.Framework.IO.Stores ;
2023-06-08 02:47:07 +03:00
using osu.Framework.Localisation ;
2017-10-20 08:01:45 +09:00
using osu.Framework.Logging ;
2021-10-01 00:27:54 +09:00
using osu.Framework.Platform ;
2022-05-22 22:15:53 +09:00
using osu.Framework.Timing ;
2018-05-09 14:52:46 +03:00
using osu.Game.Audio ;
2021-10-01 00:27:54 +09:00
using osu.Game.Beatmaps ;
2022-05-22 22:15:53 +09:00
using osu.Game.Beatmaps.ControlPoints ;
2022-02-16 16:57:28 +09:00
using osu.Game.Beatmaps.Formats ;
2021-10-01 00:27:54 +09:00
using osu.Game.Configuration ;
2017-07-27 20:38:35 +09:00
using osu.Game.Database ;
2023-06-08 02:47:07 +03:00
using osu.Game.Extensions ;
2021-10-01 00:27:54 +09:00
using osu.Game.Graphics ;
using osu.Game.Graphics.Cursor ;
2017-08-09 12:37:47 +09:00
using osu.Game.Input ;
2017-08-11 16:11:46 +09:00
using osu.Game.Input.Bindings ;
2017-07-26 20:22:02 +09:00
using osu.Game.IO ;
2023-06-08 02:47:07 +03:00
using osu.Game.Localisation ;
2020-12-24 17:58:38 +09:00
using osu.Game.Online ;
2021-10-01 00:27:54 +09:00
using osu.Game.Online.API ;
2021-02-12 14:54:19 +09:00
using osu.Game.Online.Chat ;
2022-07-04 17:18:33 +09:00
using osu.Game.Online.Metadata ;
2020-12-25 13:38:11 +09:00
using osu.Game.Online.Multiplayer ;
2022-12-24 11:20:31 +01:00
using osu.Game.Online.Solo ;
2020-10-22 13:41:54 +09:00
using osu.Game.Online.Spectator ;
2020-08-06 19:01:23 +09:00
using osu.Game.Overlays ;
2022-01-14 23:16:07 +01:00
using osu.Game.Overlays.Settings ;
using osu.Game.Overlays.Settings.Sections ;
2022-10-21 01:52:19 +03:00
using osu.Game.Overlays.Settings.Sections.Input ;
2019-12-28 21:13:18 +08:00
using osu.Game.Resources ;
2017-07-26 13:22:46 +09:00
using osu.Game.Rulesets ;
2019-05-15 13:00:11 +09:00
using osu.Game.Rulesets.Mods ;
2018-11-28 16:12:57 +09:00
using osu.Game.Scoring ;
2018-02-15 13:45:39 +09:00
using osu.Game.Skinning ;
2021-04-08 19:34:35 -04:00
using osu.Game.Utils ;
2020-08-03 18:48:10 +09:00
using RuntimeInfo = osu . Framework . RuntimeInfo ;
2018-04-13 18:19:50 +09:00
2016-09-24 00:39:20 +09:00
namespace osu.Game
{
2018-04-08 12:58:34 +09:00
/// <summary>
/// The most basic <see cref="Game"/> that can be used to host osu! components and systems.
/// Unlike <see cref="OsuGame"/>, this class will not load any kind of UI, allowing it to be used
/// for provide dependencies to test cases without interfering with them.
/// </summary>
2022-10-12 17:50:31 +03:00
[Cached(typeof(OsuGameBase))]
2022-05-22 22:15:53 +09:00
public partial class OsuGameBase : Framework . Game , ICanAcceptFiles , IBeatSyncProvider
2016-09-24 00:39:20 +09:00
{
2023-04-25 21:05:40 +02:00
public static readonly string [ ] VIDEO_EXTENSIONS = { ".mp4" , ".mov" , ".avi" , ".flv" , ".mpg" , ".wmv" , ".m4v" } ;
2022-06-16 18:00:27 +09:00
2022-02-18 15:57:37 +09:00
public const string OSU_PROTOCOL = "osu://" ;
2021-05-28 02:27:06 +09:00
public const string CLIENT_STREAM_NAME = @"lazer" ;
2019-06-03 13:16:05 +09:00
2022-03-30 13:34:48 +09:00
/// <summary>
/// The filename of the main client database.
/// </summary>
public const string CLIENT_DATABASE_FILENAME = @"client.realm" ;
2020-03-22 02:16:28 +09:00
public const int SAMPLE_CONCURRENCY = 6 ;
2022-12-16 17:19:07 +09:00
public const double SFX_STEREO_STRENGTH = 0.75 ;
2021-09-05 13:25:10 +09:00
/// <summary>
/// Length of debounce (in milliseconds) for commonly occuring sample playbacks that could stack.
/// </summary>
public const int SAMPLE_DEBOUNCE_TIME = 20 ;
2021-05-28 02:37:14 +09:00
/// <summary>
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
/// </summary>
2021-11-09 17:27:07 +09:00
private const double global_track_volume_adjust = 0.8 ;
2021-05-28 02:37:14 +09:00
2022-03-14 13:54:54 +09:00
public virtual bool UseDevelopmentServer = > DebugUtils . IsDebugBuild ;
2020-12-24 17:58:38 +09:00
2023-01-17 18:44:42 +09:00
public virtual EndpointConfiguration CreateEndpoints ( ) = >
UseDevelopmentServer ? new DevelopmentEndpointConfiguration ( ) : new ExperimentalEndpointConfiguration ( ) ;
2020-12-24 17:58:38 +09:00
2021-05-28 02:37:14 +09:00
public virtual Version AssemblyVersion = > Assembly . GetEntryAssembly ( ) ? . GetName ( ) . Version ? ? new Version ( ) ;
/// <summary>
/// MD5 representation of the game executable.
/// </summary>
public string VersionHash { get ; private set ; }
public bool IsDeployedBuild = > AssemblyVersion . Major > 0 ;
2022-05-11 10:38:35 +09:00
internal const string BUILD_SUFFIX = "lazer" ;
2021-05-28 02:37:14 +09:00
public virtual string Version
{
get
{
if ( ! IsDeployedBuild )
return @"local " + ( DebugUtils . IsDebugBuild ? @"debug" : @"release" ) ;
var version = AssemblyVersion ;
2022-05-11 10:38:35 +09:00
return $@"{version.Major}.{version.Minor}.{version.Build}-{BUILD_SUFFIX}" ;
2021-05-28 02:37:14 +09:00
}
}
2022-02-04 18:58:29 +09:00
/// <summary>
/// The <see cref="Edges"/> that the game should be drawn over at a top level.
/// Defaults to <see cref="Edges.None"/>.
/// </summary>
2022-02-04 16:10:49 +03:00
protected virtual Edges SafeAreaOverrideEdges = > Edges . None ;
2022-02-04 18:58:29 +09:00
2021-05-28 02:27:06 +09:00
protected OsuConfigManager LocalConfig { get ; private set ; }
2018-04-13 18:19:50 +09:00
2021-04-19 11:30:55 +09:00
protected SessionStatics SessionStatics { get ; private set ; }
2022-09-16 16:17:24 +03:00
protected OsuColour Colours { get ; private set ; }
2021-05-28 02:27:06 +09:00
protected BeatmapManager BeatmapManager { get ; private set ; }
2018-04-13 18:19:50 +09:00
2021-11-25 17:23:46 +09:00
protected BeatmapModelDownloader BeatmapDownloader { get ; private set ; }
2021-05-28 02:27:06 +09:00
protected ScoreManager ScoreManager { get ; private set ; }
2018-11-28 16:47:10 +09:00
2021-11-25 17:21:05 +09:00
protected ScoreModelDownloader ScoreDownloader { get ; private set ; }
2021-05-28 02:27:06 +09:00
protected SkinManager SkinManager { get ; private set ; }
2018-04-13 18:19:50 +09:00
2022-02-16 16:57:28 +09:00
protected RealmRulesetStore RulesetStore { get ; private set ; }
2018-04-13 18:19:50 +09:00
2021-06-10 13:58:08 +09:00
protected RealmKeyBindingStore KeyBindingStore { get ; private set ; }
2018-04-13 18:19:50 +09:00
2022-07-26 14:11:52 +09:00
protected GlobalCursorDisplay GlobalCursorDisplay { get ; private set ; }
2018-12-06 12:17:08 +09:00
2021-05-28 02:27:06 +09:00
protected MusicController MusicController { get ; private set ; }
protected IAPIProvider API { get ; set ; }
2020-10-22 13:41:54 +09:00
2021-05-28 02:27:06 +09:00
protected Storage Storage { get ; set ; }
2023-06-08 02:47:07 +03:00
/// <summary>
/// The language in which the game is currently displayed in.
/// </summary>
public Bindable < Language > CurrentLanguage { get ; } = new Bindable < Language > ( ) ;
2021-05-28 02:27:06 +09:00
protected Bindable < WorkingBeatmap > Beatmap { get ; private set ; } // cached via load() method
2018-04-13 18:19:50 +09:00
2023-01-05 12:49:45 +03:00
/// <summary>
/// The current ruleset selection for the local user.
/// </summary>
2019-05-15 13:00:11 +09:00
[Cached]
[Cached(typeof(IBindable<RulesetInfo>))]
2023-01-05 12:49:45 +03:00
protected internal readonly Bindable < RulesetInfo > Ruleset = new Bindable < RulesetInfo > ( ) ;
2019-05-15 13:00:11 +09:00
2021-02-10 14:38:15 +09:00
/// <summary>
/// The current mod selection for the local user.
/// </summary>
/// <remarks>
/// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy.
/// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection.
/// As such, all settings should be finalised before adding a mod to this collection.
/// </remarks>
2019-05-15 13:00:11 +09:00
[Cached]
2019-12-13 20:13:53 +09:00
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
2019-12-13 21:45:38 +09:00
protected readonly Bindable < IReadOnlyList < Mod > > SelectedMods = new Bindable < IReadOnlyList < Mod > > ( Array . Empty < Mod > ( ) ) ;
2019-02-01 15:42:15 +09:00
2019-12-12 18:53:25 +09:00
/// <summary>
/// Mods available for the current <see cref="Ruleset"/>.
/// </summary>
2022-06-15 17:39:30 +02:00
public readonly Bindable < Dictionary < ModType , IReadOnlyList < Mod > > > AvailableMods = new Bindable < Dictionary < ModType , IReadOnlyList < Mod > > > ( new Dictionary < ModType , IReadOnlyList < Mod > > ( ) ) ;
2019-12-12 18:53:25 +09:00
2021-05-28 02:30:31 +09:00
private BeatmapDifficultyCache difficultyCache ;
2022-07-04 18:13:53 +09:00
private BeatmapUpdater beatmapUpdater ;
2021-05-28 02:30:31 +09:00
private UserLookupCache userCache ;
2021-11-30 19:27:43 +09:00
private BeatmapLookupCache beatmapCache ;
2021-05-28 02:30:31 +09:00
private RulesetConfigCache rulesetConfigCache ;
2023-01-15 15:51:18 +03:00
protected SpectatorClient SpectatorClient { get ; private set ; }
2021-05-28 02:30:31 +09:00
2022-09-15 20:54:06 +09:00
protected MultiplayerClient MultiplayerClient { get ; private set ; }
2021-05-28 02:30:31 +09:00
2022-07-04 17:18:33 +09:00
private MetadataClient metadataClient ;
2022-12-24 11:20:31 +01:00
private SoloStatisticsWatcher soloStatisticsWatcher ;
2022-07-04 17:18:33 +09:00
2022-01-24 19:59:58 +09:00
private RealmAccess realm ;
2021-01-07 14:07:36 +09:00
2022-10-28 13:52:45 +09:00
protected SafeAreaContainer SafeAreaContainer { get ; private set ; }
2022-08-26 17:44:33 +09:00
/// <summary>
/// For now, this is used as a source specifically for beat synced components.
/// Going forward, it could potentially be used as the single source-of-truth for beatmap timing.
/// </summary>
private readonly FramedBeatmapClock beatmapClock = new FramedBeatmapClock ( true ) ;
2023-01-12 09:48:38 +03:00
protected override Container < Drawable > Content = > content ;
2021-05-28 02:30:31 +09:00
private Container content ;
2021-05-28 02:37:14 +09:00
private DependencyContainer dependencies ;
2018-04-13 18:19:50 +09:00
2021-11-09 17:27:07 +09:00
private readonly BindableNumber < double > globalTrackVolumeAdjust = new BindableNumber < double > ( global_track_volume_adjust ) ;
2018-04-13 18:19:50 +09:00
2023-06-08 02:47:07 +03:00
private Bindable < string > frameworkLocale = null ! ;
private IBindable < LocalisationParameters > localisationParameters = null ! ;
2022-05-10 10:02:32 +03:00
/// <summary>
2022-05-10 19:07:07 +09:00
/// Number of unhandled exceptions to allow before aborting execution.
2022-05-10 10:02:32 +03:00
/// </summary>
2022-05-10 19:07:07 +09:00
/// <remarks>
/// When an unhandled exception is encountered, an internal count will be decremented.
/// If the count hits zero, the game will crash.
/// Each second, the count is incremented until reaching the value specified.
/// </remarks>
2022-05-10 19:10:34 +09:00
protected virtual int UnhandledExceptionsBeforeCrash = > DebugUtils . IsDebugBuild ? 0 : 1 ;
2022-05-10 10:02:32 +03:00
2017-03-06 17:09:48 +09:00
public OsuGameBase ( )
{
2021-07-04 12:39:50 +09:00
Name = @"osu!" ;
2022-05-10 10:02:32 +03:00
2022-08-01 14:38:02 +09:00
#if DEBUG
Name + = " (development)" ;
#endif
2022-05-10 19:10:34 +09:00
allowableExceptions = UnhandledExceptionsBeforeCrash ;
2017-03-06 17:09:48 +09:00
}
2018-04-13 18:19:50 +09:00
2016-11-12 19:44:16 +09:00
[BackgroundDependencyLoader]
2023-06-08 02:47:07 +03:00
private void load ( ReadableKeyCombinationProvider keyCombinationProvider , FrameworkConfigManager frameworkConfig )
2016-09-24 00:39:20 +09:00
{
2020-08-03 18:48:10 +09:00
try
{
using ( var str = File . OpenRead ( typeof ( OsuGameBase ) . Assembly . Location ) )
VersionHash = str . ComputeMD5Hash ( ) ;
}
catch
{
// special case for android builds, which can't read DLLs from a packed apk.
// should eventually be handled in a better way.
VersionHash = $"{Version}-{RuntimeInfo.OS}" . ComputeMD5Hash ( ) ;
}
2020-07-29 12:39:18 +09:00
2019-12-28 21:13:18 +08:00
Resources . AddStore ( new DllResourceStore ( OsuResources . ResourceAssembly ) ) ;
2018-06-04 20:01:55 +09:00
2022-09-15 16:31:00 +09:00
dependencies . Cache ( realm = new RealmAccess ( Storage , CLIENT_DATABASE_FILENAME , Host . UpdateThread ) ) ;
2021-11-23 11:48:56 +09:00
2022-02-16 16:57:28 +09:00
dependencies . CacheAs < RulesetStore > ( RulesetStore = new RealmRulesetStore ( realm , Storage ) ) ;
2021-12-03 18:14:44 +09:00
dependencies . CacheAs < IRulesetStore > ( RulesetStore ) ;
2018-04-13 18:19:50 +09:00
2022-02-16 16:57:28 +09:00
Decoder . RegisterDependencies ( RulesetStore ) ;
2021-11-30 15:41:18 +09:00
2020-05-04 17:01:05 +09:00
dependencies . CacheAs ( Storage ) ;
2022-08-02 19:50:57 +09:00
var largeStore = new LargeTextureStore ( Host . Renderer , Host . CreateTextureLoaderStore ( new NamespacedResourceStore < byte [ ] > ( Resources , @"Textures" ) ) ) ;
2022-06-24 16:07:14 +02:00
largeStore . AddTextureSource ( Host . CreateTextureLoaderStore ( new OnlineStore ( ) ) ) ;
2018-09-09 02:41:47 +09:00
dependencies . Cache ( largeStore ) ;
2018-04-13 18:19:50 +09:00
2020-12-24 17:58:38 +09:00
dependencies . CacheAs ( LocalConfig ) ;
2022-11-08 18:24:57 +09:00
dependencies . CacheAs < IGameplaySettings > ( LocalConfig ) ;
2018-04-13 18:19:50 +09:00
2021-09-07 00:45:53 +09:00
InitialiseFonts ( ) ;
2018-04-13 18:19:50 +09:00
2022-09-22 12:52:24 +09:00
addFilesWarning ( ) ;
2020-03-22 02:16:28 +09:00
Audio . Samples . PlaybackConcurrency = SAMPLE_CONCURRENCY ;
2022-01-24 19:59:58 +09:00
dependencies . Cache ( SkinManager = new SkinManager ( Storage , realm , Host , Resources , Audio , Scheduler ) ) ;
2018-09-01 17:39:54 +09:00
dependencies . CacheAs < ISkinSource > ( SkinManager ) ;
2022-06-03 16:29:55 +09:00
EndpointConfiguration endpoints = CreateEndpoints ( ) ;
2020-12-24 17:58:38 +09:00
2021-02-12 14:54:19 +09:00
MessageFormatter . WebsiteRootUrl = endpoints . WebsiteRootUrl ;
2023-06-08 02:47:07 +03:00
frameworkLocale = frameworkConfig . GetBindable < string > ( FrameworkSetting . Locale ) ;
frameworkLocale . BindValueChanged ( _ = > updateLanguage ( ) ) ;
localisationParameters = Localisation . CurrentParameters . GetBoundCopy ( ) ;
localisationParameters . BindValueChanged ( _ = > updateLanguage ( ) , true ) ;
CurrentLanguage . BindValueChanged ( val = > frameworkLocale . Value = val . NewValue . ToCultureCode ( ) ) ;
2023-06-08 02:50:14 +03:00
dependencies . CacheAs ( API ? ? = new APIAccess ( this , LocalConfig , endpoints , VersionHash ) ) ;
2018-09-01 17:39:54 +09:00
2019-05-31 14:51:12 +09:00
var defaultBeatmap = new DummyWorkingBeatmap ( Audio , Textures ) ;
2018-12-25 18:34:45 +09:00
2022-06-20 17:52:42 +09:00
dependencies . Cache ( difficultyCache = new BeatmapDifficultyCache ( ) ) ;
2019-05-09 15:15:28 +09:00
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
2022-08-22 21:31:30 +09:00
dependencies . Cache ( ScoreManager = new ScoreManager ( RulesetStore , ( ) = > BeatmapManager , Storage , realm , API , LocalConfig ) ) ;
2022-07-04 18:13:53 +09:00
2022-07-28 16:19:05 +09:00
dependencies . Cache ( BeatmapManager = new BeatmapManager ( Storage , realm , API , Audio , Resources , Host , defaultBeatmap , difficultyCache , performOnlineLookups : true ) ) ;
2019-05-09 15:15:28 +09:00
2021-11-25 17:42:41 +09:00
dependencies . Cache ( BeatmapDownloader = new BeatmapModelDownloader ( BeatmapManager , API ) ) ;
dependencies . Cache ( ScoreDownloader = new ScoreModelDownloader ( ScoreManager , API ) ) ;
2021-11-25 17:21:05 +09:00
2022-06-20 17:52:42 +09:00
// Add after all the above cache operations as it depends on them.
2023-01-12 09:48:38 +03:00
base . Content . Add ( difficultyCache ) ;
2020-07-21 23:13:04 +09:00
2022-07-04 18:13:53 +09:00
// TODO: OsuGame or OsuGameBase?
2022-07-21 03:18:57 +09:00
dependencies . CacheAs ( beatmapUpdater = new BeatmapUpdater ( BeatmapManager , difficultyCache , API , Storage ) ) ;
2023-01-15 15:51:18 +03:00
dependencies . CacheAs ( SpectatorClient = new OnlineSpectatorClient ( endpoints ) ) ;
2022-09-15 20:54:06 +09:00
dependencies . CacheAs ( MultiplayerClient = new OnlineMultiplayerClient ( endpoints ) ) ;
2022-07-14 15:18:12 +09:00
dependencies . CacheAs ( metadataClient = new OnlineMetadataClient ( endpoints ) ) ;
2022-12-24 11:20:31 +01:00
dependencies . CacheAs ( soloStatisticsWatcher = new SoloStatisticsWatcher ( ) ) ;
2022-07-04 18:13:53 +09:00
2023-01-12 09:48:38 +03:00
base . Content . Add ( new BeatmapOnlineChangeIngest ( beatmapUpdater , realm , metadataClient ) ) ;
2022-06-20 18:40:05 +09:00
2023-04-11 21:42:55 +02:00
BeatmapManager . ProcessBeatmap = ( beatmapSet , scope ) = > beatmapUpdater . Process ( beatmapSet , scope ) ;
2022-07-04 18:13:53 +09:00
2021-05-28 02:30:31 +09:00
dependencies . Cache ( userCache = new UserLookupCache ( ) ) ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( userCache ) ;
2020-11-06 16:38:57 +09:00
2021-11-30 19:27:43 +09:00
dependencies . Cache ( beatmapCache = new BeatmapLookupCache ( ) ) ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( beatmapCache ) ;
2021-11-30 19:27:43 +09:00
2020-11-06 13:15:33 +09:00
var scorePerformanceManager = new ScorePerformanceCache ( ) ;
2020-11-02 14:49:25 +09:00
dependencies . Cache ( scorePerformanceManager ) ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( scorePerformanceManager ) ;
2020-09-27 12:44:29 +02:00
2022-01-24 19:59:58 +09:00
dependencies . CacheAs < IRulesetConfigCache > ( rulesetConfigCache = new RulesetConfigCache ( realm , RulesetStore ) ) ;
2021-04-11 09:00:37 +03:00
2021-04-12 10:52:12 -04:00
var powerStatus = CreateBatteryInfo ( ) ;
2021-04-11 09:00:37 +03:00
if ( powerStatus ! = null )
dependencies . CacheAs ( powerStatus ) ;
2021-04-19 11:30:55 +09:00
dependencies . Cache ( SessionStatics = new SessionStatics ( ) ) ;
2022-09-16 16:17:24 +03:00
dependencies . Cache ( Colours = new OsuColour ( ) ) ;
2018-09-01 17:39:54 +09:00
2020-10-02 16:08:11 +09:00
RegisterImportHandler ( BeatmapManager ) ;
RegisterImportHandler ( ScoreManager ) ;
RegisterImportHandler ( SkinManager ) ;
2018-09-01 17:39:54 +09:00
2021-02-11 15:02:34 +09:00
// drop track volume game-wide to leave some head-room for UI effects / samples.
// this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable.
// we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial).
Audio . Tracks . AddAdjustment ( AdjustableProperty . Volume , globalTrackVolumeAdjust ) ;
2018-04-13 18:19:50 +09:00
2019-11-12 17:45:42 +08:00
Beatmap = new NonNullableBindable < WorkingBeatmap > ( defaultBeatmap ) ;
2020-03-04 19:07:34 +09:00
2019-11-12 17:45:42 +08:00
dependencies . CacheAs < IBindable < WorkingBeatmap > > ( Beatmap ) ;
dependencies . CacheAs ( Beatmap ) ;
2018-04-13 18:19:50 +09:00
2020-10-22 14:54:27 +09:00
// add api components to hierarchy.
2020-05-28 19:05:35 +09:00
if ( API is APIAccess apiAccess )
2023-01-12 09:48:38 +03:00
base . Content . Add ( apiAccess ) ;
2022-07-04 17:18:33 +09:00
2023-01-15 15:51:18 +03:00
base . Content . Add ( SpectatorClient ) ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( MultiplayerClient ) ;
base . Content . Add ( metadataClient ) ;
base . Content . Add ( soloStatisticsWatcher ) ;
2020-10-22 14:54:27 +09:00
2023-01-12 09:48:38 +03:00
base . Content . Add ( rulesetConfigCache ) ;
2023-01-11 22:02:06 +03:00
PreviewTrackManager previewTrackManager ;
dependencies . Cache ( previewTrackManager = new PreviewTrackManager ( BeatmapManager . BeatmapTrackStore ) ) ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( previewTrackManager ) ;
2023-01-11 22:02:06 +03:00
2023-01-12 09:48:38 +03:00
base . Content . Add ( MusicController = new MusicController ( ) ) ;
2023-01-11 22:02:06 +03:00
dependencies . CacheAs ( MusicController ) ;
MusicController . TrackChanged + = onTrackChanged ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( beatmapClock ) ;
2018-04-22 04:10:06 +09:00
2021-04-08 15:17:53 +09:00
GlobalActionContainer globalBindings ;
2023-01-12 09:48:38 +03:00
base . Content . Add ( SafeAreaContainer = new SafeAreaContainer
2018-04-22 04:10:06 +09:00
{
2022-02-04 18:58:29 +09:00
SafeAreaOverrideEdges = SafeAreaOverrideEdges ,
2022-02-04 16:07:05 +09:00
RelativeSizeAxes = Axes . Both ,
Child = CreateScalingContainer ( ) . WithChildren ( new Drawable [ ]
{
2022-07-26 14:11:52 +09:00
( GlobalCursorDisplay = new GlobalCursorDisplay
2023-01-12 09:48:38 +03:00
{
RelativeSizeAxes = Axes . Both
} ) . WithChild ( content = new OsuTooltipContainer ( GlobalCursorDisplay . MenuCursor )
2022-02-04 16:07:05 +09:00
{
RelativeSizeAxes = Axes . Both
} ) ,
// to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
globalBindings = new GlobalActionContainer ( this )
} )
} ) ;
2018-04-22 04:10:06 +09:00
2022-01-24 19:59:58 +09:00
KeyBindingStore = new RealmKeyBindingStore ( realm , keyCombinationProvider ) ;
2021-09-07 15:19:23 +09:00
KeyBindingStore . Register ( globalBindings , RulesetStore . AvailableRulesets ) ;
2021-01-12 14:59:48 +09:00
2021-04-08 15:17:53 +09:00
dependencies . Cache ( globalBindings ) ;
2018-05-09 14:52:46 +03:00
2019-12-12 18:53:25 +09:00
Ruleset . BindValueChanged ( onRulesetChanged ) ;
2022-01-18 12:20:52 +09:00
Beatmap . BindValueChanged ( onBeatmapChanged ) ;
2019-12-12 18:53:25 +09:00
}
2023-06-08 02:47:07 +03:00
private void updateLanguage ( ) = > CurrentLanguage . Value = LanguageExtensions . GetLanguageFor ( frameworkLocale . Value , localisationParameters . Value ) ;
2022-09-22 12:52:24 +09:00
private void addFilesWarning ( )
{
var realmStore = new RealmFileStore ( realm , Storage ) ;
const string filename = "IMPORTANT READ ME.txt" ;
if ( ! realmStore . Storage . Exists ( filename ) )
{
using ( var stream = realmStore . Storage . CreateFileSafely ( filename ) )
using ( var textWriter = new StreamWriter ( stream ) )
{
textWriter . WriteLine ( @"This folder contains all your user files (beatmaps, skins, replays etc.)" ) ;
textWriter . WriteLine ( @"Please do not touch or delete this folder!!" ) ;
textWriter . WriteLine ( ) ;
textWriter . WriteLine ( @"If you are really looking to completely delete user data, please delete" ) ;
textWriter . WriteLine ( @"the parent folder including all other files and directories" ) ;
textWriter . WriteLine ( ) ;
textWriter . WriteLine ( @"For more information on how these files are organised," ) ;
textWriter . WriteLine ( @"see https://github.com/ppy/osu/wiki/User-file-storage" ) ;
}
}
}
2022-08-29 16:29:19 +09:00
private void onTrackChanged ( WorkingBeatmap beatmap , TrackChangeDirection direction )
{
// FramedBeatmapClock uses a decoupled clock internally which will mutate the source if it is an `IAdjustableClock`.
// We don't want this for now, as the intention of beatmapClock is to be a read-only source for beat sync components.
//
// Encapsulating in a FramedClock will avoid any mutations.
var framedClock = new FramedClock ( beatmap . Track ) ;
beatmapClock . ChangeSource ( framedClock ) ;
}
2022-08-26 17:44:33 +09:00
2021-09-07 00:45:53 +09:00
protected virtual void InitialiseFonts ( )
{
AddFont ( Resources , @"Fonts/osuFont" ) ;
AddFont ( Resources , @"Fonts/Torus/Torus-Regular" ) ;
AddFont ( Resources , @"Fonts/Torus/Torus-Light" ) ;
AddFont ( Resources , @"Fonts/Torus/Torus-SemiBold" ) ;
AddFont ( Resources , @"Fonts/Torus/Torus-Bold" ) ;
2021-10-03 23:36:39 +02:00
AddFont ( Resources , @"Fonts/Torus-Alternate/Torus-Alternate-Regular" ) ;
AddFont ( Resources , @"Fonts/Torus-Alternate/Torus-Alternate-Light" ) ;
AddFont ( Resources , @"Fonts/Torus-Alternate/Torus-Alternate-SemiBold" ) ;
AddFont ( Resources , @"Fonts/Torus-Alternate/Torus-Alternate-Bold" ) ;
2021-09-07 00:45:53 +09:00
AddFont ( Resources , @"Fonts/Inter/Inter-Regular" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-RegularItalic" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-Light" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-LightItalic" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-SemiBold" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-SemiBoldItalic" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-Bold" ) ;
AddFont ( Resources , @"Fonts/Inter/Inter-BoldItalic" ) ;
AddFont ( Resources , @"Fonts/Noto/Noto-Basic" ) ;
AddFont ( Resources , @"Fonts/Noto/Noto-Hangul" ) ;
AddFont ( Resources , @"Fonts/Noto/Noto-CJK-Basic" ) ;
AddFont ( Resources , @"Fonts/Noto/Noto-CJK-Compatibility" ) ;
AddFont ( Resources , @"Fonts/Noto/Noto-Thai" ) ;
AddFont ( Resources , @"Fonts/Venera/Venera-Light" ) ;
AddFont ( Resources , @"Fonts/Venera/Venera-Bold" ) ;
AddFont ( Resources , @"Fonts/Venera/Venera-Black" ) ;
}
2021-05-31 13:39:18 +09:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent ) = >
dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
public override void SetHost ( GameHost host )
{
base . SetHost ( host ) ;
// may be non-null for certain tests
Storage ? ? = host . Storage ;
LocalConfig ? ? = UseDevelopmentServer
? new DevelopmentOsuConfigManager ( Storage )
: new OsuConfigManager ( Storage ) ;
2022-05-10 10:02:32 +03:00
host . ExceptionThrown + = onExceptionThrown ;
2021-05-31 13:39:18 +09:00
}
/// <summary>
/// Use to programatically exit the game as if the user was triggering via alt-f4.
2022-06-19 01:14:48 +09:00
/// By default, will keep persisting until an exit occurs (exit may be blocked multiple times).
2022-06-19 13:42:45 +02:00
/// May be interrupted (see <see cref="OsuGame"/>'s override).
2021-05-31 13:39:18 +09:00
/// </summary>
2022-06-19 12:34:14 +09:00
public virtual void AttemptExit ( )
2021-05-31 13:39:18 +09:00
{
if ( ! OnExiting ( ) )
Exit ( ) ;
else
2022-06-19 12:34:14 +09:00
Scheduler . AddDelayed ( AttemptExit , 2000 ) ;
2021-05-31 13:39:18 +09:00
}
2022-02-10 18:48:37 +09:00
public bool Migrate ( string path )
2021-05-31 13:39:18 +09:00
{
2021-06-29 20:16:57 +09:00
Logger . Log ( $@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""..." ) ;
2021-10-12 16:04:09 +09:00
IDisposable realmBlocker = null ;
try
2021-06-10 13:58:08 +09:00
{
2021-10-12 16:04:09 +09:00
ManualResetEventSlim readyToRun = new ManualResetEventSlim ( ) ;
Scheduler . Add ( ( ) = >
{
2022-07-02 12:35:29 +09:00
realmBlocker = realm . BlockAllOperations ( "migration" ) ;
2021-10-12 16:04:09 +09:00
readyToRun . Set ( ) ;
} , false ) ;
2022-06-23 14:33:20 +09:00
if ( ! readyToRun . Wait ( 30000 ) )
throw new TimeoutException ( "Attempting to block for migration took too long." ) ;
2021-10-12 16:04:09 +09:00
2022-02-10 18:48:37 +09:00
bool? cleanupSucceded = ( Storage as OsuStorage ) ? . Migrate ( Host . GetStorage ( path ) ) ;
Logger . Log ( @"Migration complete!" ) ;
return cleanupSucceded ! = false ;
2021-06-10 13:58:08 +09:00
}
2021-10-12 16:04:09 +09:00
finally
{
realmBlocker ? . Dispose ( ) ;
}
2021-05-31 13:39:18 +09:00
}
protected override UserInputManager CreateUserInputManager ( ) = > new OsuUserInputManager ( ) ;
protected virtual BatteryInfo CreateBatteryInfo ( ) = > null ;
protected virtual Container CreateScalingContainer ( ) = > new DrawSizePreservingFillContainer ( ) ;
protected override Storage CreateStorage ( GameHost host , Storage defaultStorage ) = > new OsuStorage ( host , defaultStorage ) ;
2022-01-14 23:16:07 +01:00
/// <summary>
/// Creates an input settings subsection for an <see cref="InputHandler"/>.
/// </summary>
/// <remarks>Should be overriden per-platform to provide settings for platform-specific handlers.</remarks>
public virtual SettingsSubsection CreateSettingsSubsectionFor ( InputHandler handler )
{
2022-10-28 17:26:53 +09:00
// One would think that this could be moved to the `OsuGameDesktop` class, but doing so means that
// OsuGameTestScenes will not show any input options (as they are based on OsuGame not OsuGameDesktop).
//
// This in turn makes it hard for ruleset creators to adjust input settings while testing their ruleset
// within the test browser interface.
2022-10-21 01:52:19 +03:00
if ( RuntimeInfo . IsDesktop )
{
switch ( handler )
{
case ITabletHandler th :
return new TabletSettings ( th ) ;
case MouseHandler mh :
return new MouseSettings ( mh ) ;
case JoystickHandler jh :
return new JoystickSettings ( jh ) ;
2023-01-24 13:37:12 +09:00
case TouchHandler th :
return new TouchSettings ( th ) ;
2022-10-21 01:52:19 +03:00
}
}
2022-01-14 23:16:07 +01:00
switch ( handler )
{
2022-06-24 21:25:23 +09:00
case MidiHandler :
2022-01-14 23:16:07 +01:00
return new InputSection . HandlerSection ( handler ) ;
// return null for handlers that shouldn't have settings.
default :
return null ;
}
}
2022-06-27 15:58:51 +09:00
private void onBeatmapChanged ( ValueChangedEvent < WorkingBeatmap > beatmap )
2022-01-18 12:20:52 +09:00
{
2022-01-18 16:11:03 +09:00
if ( IsLoaded & & ! ThreadSafety . IsUpdateThread )
2022-01-18 12:20:52 +09:00
throw new InvalidOperationException ( "Global beatmap bindable must be changed from update thread." ) ;
2022-06-27 15:58:51 +09:00
Logger . Log ( $"Game-wide working beatmap updated to {beatmap.NewValue}" ) ;
2022-01-18 12:20:52 +09:00
}
2019-12-12 18:53:25 +09:00
private void onRulesetChanged ( ValueChangedEvent < RulesetInfo > r )
{
2022-01-18 16:11:03 +09:00
if ( IsLoaded & & ! ThreadSafety . IsUpdateThread )
2022-01-18 12:20:52 +09:00
throw new InvalidOperationException ( "Global ruleset bindable must be changed from update thread." ) ;
2022-01-10 13:59:33 +09:00
Ruleset instance = null ;
2021-11-24 12:16:08 +09:00
2022-01-10 13:59:33 +09:00
try
{
if ( r . NewValue ? . Available = = true )
{
instance = r . NewValue . CreateInstance ( ) ;
}
}
catch ( Exception e )
{
Logger . Error ( e , "Ruleset load failed and has been rolled back" ) ;
}
if ( instance = = null )
2019-12-15 02:36:49 +09:00
{
2021-07-26 16:34:38 +09:00
// reject the change if the ruleset is not available.
2022-06-15 17:22:39 +02:00
revertRulesetChange ( ) ;
2021-07-26 16:34:38 +09:00
return ;
2019-12-15 02:36:49 +09:00
}
2019-12-12 18:53:25 +09:00
2021-07-26 16:34:38 +09:00
var dict = new Dictionary < ModType , IReadOnlyList < Mod > > ( ) ;
2022-06-15 17:22:39 +02:00
try
2021-11-24 12:16:08 +09:00
{
2022-12-26 20:36:39 +01:00
foreach ( ModType type in Enum . GetValues < ModType > ( ) )
2022-06-15 17:22:39 +02:00
{
dict [ type ] = instance . GetModsFor ( type )
2022-06-15 17:26:54 +02:00
// Rulesets should never return null mods, but let's be defensive just in case.
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
2022-06-15 17:22:39 +02:00
. Where ( mod = > mod ! = null )
. ToList ( ) ;
}
}
catch ( Exception e )
2021-11-24 12:16:08 +09:00
{
2022-06-15 17:22:39 +02:00
Logger . Error ( e , $"Could not load mods for \" { instance . RulesetInfo . Name } \ " ruleset. Current ruleset has been rolled back." ) ;
revertRulesetChange ( ) ;
return ;
2021-11-24 12:16:08 +09:00
}
2021-07-26 16:34:38 +09:00
2023-05-03 18:31:35 +02:00
AvailableMods . Value = dict ;
2023-04-25 21:05:40 +02:00
2023-05-03 18:31:35 +02:00
if ( SelectedMods . Disabled )
return ;
2022-10-14 02:02:24 +03:00
2023-05-03 18:31:35 +02:00
var convertedMods = SelectedMods . Value . Select ( mod = >
2023-04-25 21:28:03 +02:00
{
2023-05-03 18:31:35 +02:00
var newMod = instance . CreateModFromAcronym ( mod . Acronym ) ;
2023-05-04 18:15:12 +02:00
newMod ? . CopyCommonSettingsFrom ( mod ) ;
2023-05-03 18:31:35 +02:00
return newMod ;
} ) . Where ( newMod = > newMod ! = null ) . ToList ( ) ;
2023-04-25 21:28:03 +02:00
2023-05-03 18:31:35 +02:00
if ( ! ModUtils . CheckValidForGameplay ( convertedMods , out var invalid ) )
invalid . ForEach ( newMod = > convertedMods . Remove ( newMod ) ) ;
2023-04-25 21:05:40 +02:00
2023-05-03 18:31:35 +02:00
SelectedMods . Value = convertedMods ;
2023-04-25 21:05:40 +02:00
2022-06-15 17:22:39 +02:00
void revertRulesetChange ( ) = > Ruleset . Value = r . OldValue ? . Available = = true ? r . OldValue : RulesetStore . AvailableRulesets . First ( ) ;
2016-11-30 19:22:36 +09:00
}
2018-04-13 18:19:50 +09:00
2022-05-10 10:02:32 +03:00
private int allowableExceptions ;
/// <summary>
/// Allows a maximum of one unhandled exception, per second of execution.
/// </summary>
private bool onExceptionThrown ( Exception _ )
{
bool continueExecution = Interlocked . Decrement ( ref allowableExceptions ) > = 0 ;
2022-05-10 19:07:00 +09:00
Logger . Log ( $"Unhandled exception has been {(continueExecution ? $" allowed with { allowableExceptions } more allowable exceptions " : " denied ")} ." ) ;
2022-05-10 10:02:32 +03:00
// restore the stock of allowable exceptions after a short delay.
Task . Delay ( 1000 ) . ContinueWith ( _ = > Interlocked . Increment ( ref allowableExceptions ) ) ;
return continueExecution ;
2016-11-30 19:22:36 +09:00
}
2018-04-13 18:19:50 +09:00
2019-10-15 16:14:06 +09:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2020-10-19 19:03:22 +09:00
2019-10-15 16:14:06 +09:00
RulesetStore ? . Dispose ( ) ;
2020-10-19 19:03:22 +09:00
LocalConfig ? . Dispose ( ) ;
2020-05-11 21:37:07 +09:00
2022-07-04 18:13:53 +09:00
beatmapUpdater ? . Dispose ( ) ;
2022-01-24 19:59:58 +09:00
realm ? . Dispose ( ) ;
2022-05-10 10:02:32 +03:00
if ( Host ! = null )
Host . ExceptionThrown - = onExceptionThrown ;
2019-10-15 16:14:06 +09:00
}
2022-05-22 22:15:53 +09:00
2022-07-06 14:50:30 +09:00
ControlPointInfo IBeatSyncProvider . ControlPoints = > Beatmap . Value . BeatmapLoaded ? Beatmap . Value . Beatmap . ControlPointInfo : null ;
2022-08-26 17:44:33 +09:00
IClock IBeatSyncProvider . Clock = > beatmapClock ;
2022-08-02 17:54:42 +09:00
ChannelAmplitudes IHasAmplitudes . CurrentAmplitudes = > Beatmap . Value . TrackLoaded ? Beatmap . Value . Track . CurrentAmplitudes : ChannelAmplitudes . Empty ;
2016-09-24 00:39:20 +09:00
}
}