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
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
2019-05-28 18:59:21 +09:00
using System.Threading.Tasks ;
2018-04-13 18:19:50 +09:00
using osu.Framework.Allocation ;
using osu.Framework.Audio ;
2019-02-21 19:04:31 +09:00
using osu.Framework.Bindables ;
2019-06-20 12:48:45 +09:00
using osu.Framework.Development ;
2020-07-29 12:39:18 +09:00
using osu.Framework.Extensions ;
2018-04-13 18:19:50 +09:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.IO.Stores ;
using osu.Framework.Platform ;
using osu.Game.Beatmaps ;
using osu.Game.Configuration ;
using osu.Game.Graphics ;
using osu.Game.Graphics.Cursor ;
using osu.Game.Online.API ;
using osu.Framework.Graphics.Performance ;
using osu.Framework.Graphics.Textures ;
2018-07-09 15:26:22 +09:00
using osu.Framework.Input ;
2018-04-13 18:19:50 +09:00
using osu.Framework.Logging ;
2018-05-09 14:52:46 +03:00
using osu.Game.Audio ;
2018-04-13 18:19:50 +09:00
using osu.Game.Database ;
using osu.Game.Input ;
using osu.Game.Input.Bindings ;
using osu.Game.IO ;
2020-12-24 17:58:38 +09:00
using osu.Game.Online ;
2021-02-12 14:54:19 +09:00
using osu.Game.Online.Chat ;
2020-12-25 13:38:11 +09:00
using osu.Game.Online.Multiplayer ;
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 ;
2019-12-28 21:13:18 +08:00
using osu.Game.Resources ;
2018-04-13 18:19:50 +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-04-13 18:19:50 +09:00
using osu.Game.Skinning ;
2021-04-08 19:34:35 -04:00
using osu.Game.Utils ;
2018-11-20 16:51:59 +09:00
using osuTK.Input ;
2020-08-03 18:48:10 +09:00
using RuntimeInfo = osu . Framework . RuntimeInfo ;
2018-04-13 18:19:50 +09:00
namespace osu.Game
{
/// <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>
public class OsuGameBase : Framework . Game , ICanAcceptFiles
{
2019-06-03 13:16:05 +09:00
public const string CLIENT_STREAM_NAME = "lazer" ;
2020-03-22 02:16:28 +09:00
public const int SAMPLE_CONCURRENCY = 6 ;
2020-12-24 17:58:38 +09:00
public bool UseDevelopmentServer { get ; }
2018-04-13 18:19:50 +09:00
protected OsuConfigManager LocalConfig ;
2021-04-19 11:30:55 +09:00
protected SessionStatics SessionStatics { get ; private set ; }
2018-04-13 18:19:50 +09:00
protected BeatmapManager BeatmapManager ;
2018-11-28 16:47:10 +09:00
protected ScoreManager ScoreManager ;
2020-11-06 13:14:23 +09:00
protected BeatmapDifficultyCache DifficultyCache ;
2020-08-28 19:16:46 +09:00
2020-11-06 16:38:57 +09:00
protected UserLookupCache UserCache ;
2018-04-13 18:19:50 +09:00
protected SkinManager SkinManager ;
protected RulesetStore RulesetStore ;
protected FileStore FileStore ;
protected KeyBindingStore KeyBindingStore ;
protected SettingsStore SettingsStore ;
2018-06-11 15:07:42 +09:00
protected RulesetConfigCache RulesetConfigCache ;
2019-09-25 15:00:08 +09:00
protected IAPIProvider API ;
2018-12-06 12:17:08 +09:00
2020-10-22 13:41:54 +09:00
private SpectatorStreamingClient spectatorStreaming ;
2020-12-21 00:21:41 +09:00
private StatefulMultiplayerClient multiplayerClient ;
2020-10-22 13:41:54 +09:00
2018-05-25 21:13:40 +02:00
protected MenuCursorContainer MenuCursorContainer ;
2018-04-13 18:19:50 +09:00
2020-08-06 19:01:23 +09:00
protected MusicController MusicController ;
2018-04-13 18:19:50 +09:00
private Container content ;
protected override Container < Drawable > Content = > content ;
2019-09-25 15:00:08 +09:00
protected Storage Storage { get ; set ; }
2019-05-15 13:00:11 +09:00
[Cached]
[Cached(typeof(IBindable<RulesetInfo>))]
protected readonly Bindable < RulesetInfo > Ruleset = new Bindable < RulesetInfo > ( ) ;
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>
public readonly Bindable < Dictionary < ModType , IReadOnlyList < Mod > > > AvailableMods = new Bindable < Dictionary < ModType , IReadOnlyList < Mod > > > ( ) ;
2019-11-12 17:45:42 +08:00
protected Bindable < WorkingBeatmap > Beatmap { get ; private set ; } // cached via load() method
2018-04-13 18:19:50 +09:00
private Bindable < bool > fpsDisplayVisible ;
2019-03-01 20:15:09 +09:00
public virtual Version AssemblyVersion = > Assembly . GetEntryAssembly ( ) ? . GetName ( ) . Version ? ? new Version ( ) ;
2018-04-13 18:19:50 +09:00
2020-07-29 12:39:18 +09:00
/// <summary>
/// MD5 representation of the game executable.
/// </summary>
public string VersionHash { get ; private set ; }
2019-03-01 19:59:39 +09:00
public bool IsDeployedBuild = > AssemblyVersion . Major > 0 ;
2018-04-13 18:19:50 +09:00
2020-03-05 14:46:07 +09:00
public virtual string Version
2018-04-13 18:19:50 +09:00
{
get
{
if ( ! IsDeployedBuild )
2019-06-20 12:48:45 +09:00
return @"local " + ( DebugUtils . IsDebugBuild ? @"debug" : @"release" ) ;
2018-04-13 18:19:50 +09:00
2019-03-01 19:59:39 +09:00
var version = AssemblyVersion ;
return $@"{version.Major}.{version.Minor}.{version.Build}" ;
2018-04-13 18:19:50 +09:00
}
}
public OsuGameBase ( )
{
2020-12-24 17:58:38 +09:00
UseDevelopmentServer = DebugUtils . IsDebugBuild ;
2018-04-13 18:19:50 +09:00
Name = @"osu!lazer" ;
}
private DependencyContainer dependencies ;
2018-07-11 17:07:14 +09:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent ) = >
dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
2018-04-13 18:19:50 +09:00
2020-05-12 12:39:52 +09:00
private DatabaseContextFactory contextFactory ;
2018-04-13 18:19:50 +09:00
2018-07-09 15:26:22 +09:00
protected override UserInputManager CreateUserInputManager ( ) = > new OsuUserInputManager ( ) ;
2021-04-12 10:52:12 -04:00
protected virtual BatteryInfo CreateBatteryInfo ( ) = > null ;
2021-04-11 08:57:44 +03:00
2021-02-17 20:40:15 +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>
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5 ;
private readonly BindableNumber < double > globalTrackVolumeAdjust = new BindableNumber < double > ( GLOBAL_TRACK_VOLUME_ADJUST ) ;
2021-02-11 15:02:34 +09:00
2018-04-13 18:19:50 +09:00
[BackgroundDependencyLoader]
private void load ( )
{
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
2020-05-12 12:39:52 +09:00
dependencies . Cache ( contextFactory = new DatabaseContextFactory ( Storage ) ) ;
2018-04-13 18:19:50 +09:00
2020-05-04 17:01:05 +09:00
dependencies . CacheAs ( Storage ) ;
2019-01-25 11:41:44 +09:00
var largeStore = new LargeTextureStore ( Host . CreateTextureLoaderStore ( new NamespacedResourceStore < byte [ ] > ( Resources , @"Textures" ) ) ) ;
largeStore . AddStore ( Host . CreateTextureLoaderStore ( new OnlineStore ( ) ) ) ;
2018-09-09 02:41:47 +09:00
dependencies . Cache ( largeStore ) ;
2018-04-13 18:19:50 +09:00
dependencies . CacheAs ( this ) ;
2020-12-24 17:58:38 +09:00
dependencies . CacheAs ( LocalConfig ) ;
2018-04-13 18:19:50 +09:00
2019-11-29 02:15:13 +09:00
AddFont ( Resources , @"Fonts/osuFont" ) ;
2020-03-03 18:04:12 +09:00
AddFont ( Resources , @"Fonts/Torus-Regular" ) ;
AddFont ( Resources , @"Fonts/Torus-Light" ) ;
2020-03-13 13:31:59 +09:00
AddFont ( Resources , @"Fonts/Torus-SemiBold" ) ;
AddFont ( Resources , @"Fonts/Torus-Bold" ) ;
2020-03-03 18:04:12 +09:00
2020-03-08 14:43:27 +09:00
AddFont ( Resources , @"Fonts/Noto-Basic" ) ;
AddFont ( Resources , @"Fonts/Noto-Hangul" ) ;
AddFont ( Resources , @"Fonts/Noto-CJK-Basic" ) ;
AddFont ( Resources , @"Fonts/Noto-CJK-Compatibility" ) ;
2020-07-15 13:35:40 +09:00
AddFont ( Resources , @"Fonts/Noto-Thai" ) ;
2020-03-08 14:43:27 +09:00
2019-11-29 02:15:13 +09:00
AddFont ( Resources , @"Fonts/Venera-Light" ) ;
2020-03-04 11:46:48 +09:00
AddFont ( Resources , @"Fonts/Venera-Bold" ) ;
AddFont ( Resources , @"Fonts/Venera-Black" ) ;
2018-04-13 18:19:50 +09:00
2020-03-22 02:16:28 +09:00
Audio . Samples . PlaybackConcurrency = SAMPLE_CONCURRENCY ;
2018-09-01 17:39:54 +09:00
runMigrations ( ) ;
2020-05-12 12:39:52 +09:00
dependencies . Cache ( SkinManager = new SkinManager ( Storage , contextFactory , Host , Audio , new NamespacedResourceStore < byte [ ] > ( Resources , "Skins/Legacy" ) ) ) ;
2018-09-01 17:39:54 +09:00
dependencies . CacheAs < ISkinSource > ( SkinManager ) ;
2020-11-16 16:43:17 +09:00
// needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo.
SkinManager . ItemRemoved . BindValueChanged ( weakRemovedInfo = >
{
if ( weakRemovedInfo . NewValue . TryGetTarget ( out var removedInfo ) )
{
2020-11-16 16:49:31 +09:00
Schedule ( ( ) = >
{
// check the removed skin is not the current user choice. if it is, switch back to default.
if ( removedInfo . ID = = SkinManager . CurrentSkinInfo . Value . ID )
SkinManager . CurrentSkinInfo . Value = SkinInfo . Default ;
} ) ;
2020-11-16 16:43:17 +09:00
}
} ) ;
2020-12-24 17:58:38 +09:00
EndpointConfiguration endpoints = UseDevelopmentServer ? ( EndpointConfiguration ) new DevelopmentEndpointConfiguration ( ) : new ProductionEndpointConfiguration ( ) ;
2021-02-12 14:54:19 +09:00
MessageFormatter . WebsiteRootUrl = endpoints . WebsiteRootUrl ;
2021-02-14 23:31:57 +09:00
dependencies . CacheAs ( API ? ? = new APIAccess ( LocalConfig , endpoints , VersionHash ) ) ;
2018-09-01 17:39:54 +09:00
2020-12-24 17:58:38 +09:00
dependencies . CacheAs ( spectatorStreaming = new SpectatorStreamingClient ( endpoints ) ) ;
2020-12-25 13:38:11 +09:00
dependencies . CacheAs ( multiplayerClient = new MultiplayerClient ( endpoints ) ) ;
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
2020-05-12 12:39:52 +09:00
dependencies . Cache ( RulesetStore = new RulesetStore ( contextFactory , Storage ) ) ;
dependencies . Cache ( FileStore = new FileStore ( contextFactory , Storage ) ) ;
2019-05-09 15:15:28 +09:00
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
2020-11-06 13:14:23 +09:00
dependencies . Cache ( ScoreManager = new ScoreManager ( RulesetStore , ( ) = > BeatmapManager , Storage , API , contextFactory , Host , ( ) = > DifficultyCache , LocalConfig ) ) ;
2020-09-09 20:11:29 +09:00
dependencies . Cache ( BeatmapManager = new BeatmapManager ( Storage , contextFactory , RulesetStore , API , Audio , Host , defaultBeatmap , true ) ) ;
2019-05-09 15:15:28 +09:00
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
// allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete.
List < ScoreInfo > getBeatmapScores ( BeatmapSetInfo set )
{
var beatmapIds = BeatmapManager . QueryBeatmaps ( b = > b . BeatmapSetInfoID = = set . ID ) . Select ( b = > b . ID ) . ToList ( ) ;
return ScoreManager . QueryScores ( s = > beatmapIds . Contains ( s . Beatmap . ID ) ) . ToList ( ) ;
}
2020-05-19 16:44:22 +09:00
BeatmapManager . ItemRemoved . BindValueChanged ( i = >
{
if ( i . NewValue . TryGetTarget ( out var item ) )
ScoreManager . Delete ( getBeatmapScores ( item ) , true ) ;
} ) ;
2020-05-27 16:08:47 +09:00
BeatmapManager . ItemUpdated . BindValueChanged ( i = >
2020-05-19 16:44:22 +09:00
{
if ( i . NewValue . TryGetTarget ( out var item ) )
ScoreManager . Undelete ( getBeatmapScores ( item ) , true ) ;
} ) ;
2019-05-09 15:15:28 +09:00
2020-11-06 13:14:23 +09:00
dependencies . Cache ( DifficultyCache = new BeatmapDifficultyCache ( ) ) ;
AddInternal ( DifficultyCache ) ;
2020-07-21 23:13:04 +09:00
2020-11-06 16:38:57 +09:00
dependencies . Cache ( UserCache = new UserLookupCache ( ) ) ;
AddInternal ( UserCache ) ;
2020-11-06 13:15:33 +09:00
var scorePerformanceManager = new ScorePerformanceCache ( ) ;
2020-11-02 14:49:25 +09:00
dependencies . Cache ( scorePerformanceManager ) ;
AddInternal ( scorePerformanceManager ) ;
2020-09-27 12:44:29 +02:00
2020-05-12 12:39:52 +09:00
dependencies . Cache ( KeyBindingStore = new KeyBindingStore ( contextFactory , RulesetStore ) ) ;
dependencies . Cache ( SettingsStore = new SettingsStore ( contextFactory ) ) ;
2018-09-01 17:39:54 +09:00
dependencies . Cache ( RulesetConfigCache = new RulesetConfigCache ( SettingsStore ) ) ;
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 ( ) ) ;
2018-09-01 17:39:54 +09:00
dependencies . Cache ( new OsuColour ( ) ) ;
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
FileStore . Cleanup ( ) ;
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 )
AddInternal ( apiAccess ) ;
2020-10-22 14:54:27 +09:00
AddInternal ( spectatorStreaming ) ;
2020-12-21 00:21:41 +09:00
AddInternal ( multiplayerClient ) ;
2020-10-22 14:54:27 +09:00
2019-09-06 01:13:58 +09:00
AddInternal ( RulesetConfigCache ) ;
2018-04-22 04:10:06 +09:00
2021-04-08 15:17:53 +09:00
GlobalActionContainer globalBindings ;
var mainContent = new Drawable [ ]
2018-04-22 04:10:06 +09:00
{
2021-04-08 15:17:53 +09:00
MenuCursorContainer = new MenuCursorContainer { 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
} ;
2021-04-07 19:00:04 +09:00
MenuCursorContainer . Child = content = new OsuTooltipContainer ( MenuCursorContainer . Cursor ) { RelativeSizeAxes = Axes . Both } ;
2021-04-08 15:17:53 +09:00
base . Content . Add ( CreateScalingContainer ( ) . WithChildren ( mainContent ) ) ;
2018-04-22 04:10:06 +09:00
2021-04-08 15:17:53 +09:00
KeyBindingStore . Register ( globalBindings ) ;
dependencies . Cache ( globalBindings ) ;
2018-05-09 14:52:46 +03:00
PreviewTrackManager previewTrackManager ;
dependencies . Cache ( previewTrackManager = new PreviewTrackManager ( ) ) ;
Add ( previewTrackManager ) ;
2019-12-12 18:53:25 +09:00
2020-08-06 19:01:23 +09:00
AddInternal ( MusicController = new MusicController ( ) ) ;
dependencies . CacheAs ( MusicController ) ;
2019-12-12 18:53:25 +09:00
Ruleset . BindValueChanged ( onRulesetChanged ) ;
}
private void onRulesetChanged ( ValueChangedEvent < RulesetInfo > r )
{
var dict = new Dictionary < ModType , IReadOnlyList < Mod > > ( ) ;
2019-12-15 02:36:49 +09:00
if ( r . NewValue ? . Available = = true )
{
foreach ( ModType type in Enum . GetValues ( typeof ( ModType ) ) )
dict [ type ] = r . NewValue . CreateInstance ( ) . GetModsFor ( type ) . ToList ( ) ;
}
2019-12-12 18:53:25 +09:00
2019-12-14 00:42:31 +09:00
if ( ! SelectedMods . Disabled )
SelectedMods . Value = Array . Empty < Mod > ( ) ;
2021-01-18 17:50:32 +09:00
2019-12-12 18:53:25 +09:00
AvailableMods . Value = dict ;
2018-04-13 18:19:50 +09:00
}
2019-11-11 13:58:35 +09:00
protected virtual Container CreateScalingContainer ( ) = > new DrawSizePreservingFillContainer ( ) ;
2018-05-23 14:56:40 +09:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
// TODO: This is temporary until we reimplement the local FPS display.
// It's just to allow end-users to access the framework FPS display without knowing the shortcut key.
fpsDisplayVisible = LocalConfig . GetBindable < bool > ( OsuSetting . ShowFpsDisplay ) ;
2019-05-14 13:13:51 +09:00
fpsDisplayVisible . ValueChanged + = visible = > { FrameStatistics . Value = visible . NewValue ? FrameStatisticsMode . Minimal : FrameStatisticsMode . None ; } ;
2018-05-23 14:56:40 +09:00
fpsDisplayVisible . TriggerChange ( ) ;
2019-05-14 13:13:51 +09:00
FrameStatistics . ValueChanged + = e = > fpsDisplayVisible . Value = e . NewValue ! = FrameStatisticsMode . None ;
2018-05-23 14:56:40 +09:00
}
2018-04-13 18:19:50 +09:00
private void runMigrations ( )
{
try
{
2020-05-12 12:39:52 +09:00
using ( var db = contextFactory . GetForWrite ( false ) )
2018-04-13 18:19:50 +09:00
db . Context . Migrate ( ) ;
}
2018-06-04 02:07:02 +09:00
catch ( Exception e )
2018-04-13 18:19:50 +09:00
{
Logger . Error ( e . InnerException ? ? e , "Migration failed! We'll be starting with a fresh database." , LoggingTarget . Database ) ;
// if we failed, let's delete the database and start fresh.
// todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this.
2020-05-12 12:39:52 +09:00
contextFactory . ResetDatabase ( ) ;
2018-06-04 02:07:02 +09:00
2018-07-24 12:11:20 +02:00
Logger . Log ( "Database purged successfully." , LoggingTarget . Database ) ;
2018-04-13 18:19:50 +09:00
2018-06-04 02:07:02 +09:00
// only run once more, then hard bail.
2020-05-12 12:39:52 +09:00
using ( var db = contextFactory . GetForWrite ( false ) )
2018-04-13 18:19:50 +09:00
db . Context . Migrate ( ) ;
}
}
public override void SetHost ( GameHost host )
{
base . SetHost ( host ) ;
2019-09-25 15:00:08 +09:00
2020-06-03 16:48:44 +09:00
// may be non-null for certain tests
2020-07-01 17:12:07 +09:00
Storage ? ? = host . Storage ;
2019-09-25 15:00:08 +09:00
2020-12-24 17:58:38 +09:00
LocalConfig ? ? = UseDevelopmentServer
? new DevelopmentOsuConfigManager ( Storage )
: new OsuConfigManager ( Storage ) ;
2018-04-13 18:19:50 +09:00
}
2020-12-26 15:36:21 +01:00
/// <summary>
/// Use to programatically exit the game as if the user was triggering via alt-f4.
/// Will keep persisting until an exit occurs (exit may be blocked multiple times).
/// </summary>
public void GracefullyExit ( )
{
if ( ! OnExiting ( ) )
Exit ( ) ;
else
Scheduler . AddDelayed ( GracefullyExit , 2000 ) ;
}
2020-07-01 17:12:07 +09:00
protected override Storage CreateStorage ( GameHost host , Storage defaultStorage ) = > new OsuStorage ( host , defaultStorage ) ;
2018-04-13 18:19:50 +09:00
private readonly List < ICanAcceptFiles > fileImporters = new List < ICanAcceptFiles > ( ) ;
2020-10-02 16:08:11 +09:00
/// <summary>
2020-10-02 16:14:21 +09:00
/// Register a global handler for file imports. Most recently registered will have precedence.
2020-10-02 16:08:11 +09:00
/// </summary>
/// <param name="handler">The handler to register.</param>
2020-10-02 16:14:21 +09:00
public void RegisterImportHandler ( ICanAcceptFiles handler ) = > fileImporters . Insert ( 0 , handler ) ;
2020-10-02 16:08:11 +09:00
/// <summary>
/// Unregister a global handler for file imports.
/// </summary>
/// <param name="handler">The previously registered handler.</param>
public void UnregisterImportHandler ( ICanAcceptFiles handler ) = > fileImporters . Remove ( handler ) ;
2018-04-13 18:19:50 +09:00
2019-05-28 18:59:21 +09:00
public async Task Import ( params string [ ] paths )
2018-04-13 18:19:50 +09:00
{
2021-03-31 14:57:28 +09:00
if ( paths . Length = = 0 )
return ;
2021-04-13 14:03:42 +09:00
var filesPerExtension = paths . GroupBy ( p = > Path . GetExtension ( p ) . ToLowerInvariant ( ) ) ;
2018-04-13 18:19:50 +09:00
2021-04-13 14:03:42 +09:00
foreach ( var groups in filesPerExtension )
2019-11-11 19:53:22 +08:00
{
2021-04-13 14:03:42 +09:00
foreach ( var importer in fileImporters )
{
if ( importer . HandledExtensions . Contains ( groups . Key ) )
await importer . Import ( groups . ToArray ( ) ) . ConfigureAwait ( false ) ;
}
2019-11-11 19:53:22 +08:00
}
2018-04-13 18:19:50 +09:00
}
2020-12-16 14:28:16 +01:00
public virtual async Task Import ( params ImportTask [ ] tasks )
2020-12-01 20:28:15 +01:00
{
2021-01-16 23:42:28 +01:00
var tasksPerExtension = tasks . GroupBy ( t = > Path . GetExtension ( t . Path ) . ToLowerInvariant ( ) ) ;
await Task . WhenAll ( tasksPerExtension . Select ( taskGroup = >
2020-12-01 20:28:15 +01:00
{
2021-01-16 23:42:28 +01:00
var importer = fileImporters . FirstOrDefault ( i = > i . HandledExtensions . Contains ( taskGroup . Key ) ) ;
return importer ? . Import ( taskGroup . ToArray ( ) ) ? ? Task . CompletedTask ;
2021-03-08 12:57:16 +09:00
} ) ) . ConfigureAwait ( false ) ;
2020-12-01 20:28:15 +01:00
}
2020-10-02 16:17:10 +09:00
public IEnumerable < string > HandledExtensions = > fileImporters . SelectMany ( i = > i . HandledExtensions ) ;
2018-05-28 17:55:41 +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-05-22 17:26:37 +03:00
BeatmapManager ? . Dispose ( ) ;
2020-10-19 19:03:22 +09:00
LocalConfig ? . Dispose ( ) ;
2020-05-11 21:37:07 +09:00
2020-05-12 12:39:52 +09:00
contextFactory . FlushConnections ( ) ;
2019-10-15 16:14:06 +09:00
}
2018-07-09 15:26:22 +09:00
private class OsuUserInputManager : UserInputManager
{
2020-01-22 23:04:37 +09:00
protected override MouseButtonEventManager CreateButtonEventManagerFor ( MouseButton button )
2018-07-09 15:26:22 +09:00
{
switch ( button )
{
case MouseButton . Right :
return new RightMouseManager ( button ) ;
}
2020-01-22 23:04:37 +09:00
return base . CreateButtonEventManagerFor ( button ) ;
2018-07-09 15:26:22 +09:00
}
private class RightMouseManager : MouseButtonEventManager
{
public RightMouseManager ( MouseButton button )
: base ( button )
{
}
public override bool EnableDrag = > true ; // allow right-mouse dragging for absolute scroll in scroll containers.
public override bool EnableClick = > false ;
public override bool ChangeFocusOnClick = > false ;
}
}
2020-05-11 21:37:07 +09:00
public void Migrate ( string path )
{
2020-05-12 12:39:52 +09:00
contextFactory . FlushConnections ( ) ;
2020-07-01 22:57:16 +02:00
( Storage as OsuStorage ) ? . Migrate ( Host . GetStorage ( path ) ) ;
2020-05-11 21:37:07 +09:00
}
2018-04-13 18:19:50 +09:00
}
}