2019-01-24 08:43:03 +00: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 09:19:50 +00:00
2017-09-13 09:22:24 +00:00
using System.Collections.Generic ;
2022-11-08 05:34:28 +00:00
using System.IO ;
2017-09-13 09:22:24 +00:00
using System.Linq ;
2020-10-23 06:33:38 +00:00
using osu.Framework.Graphics.Textures ;
2020-03-13 06:29:11 +00:00
using osu.Game.Beatmaps ;
2022-03-02 17:33:46 +00:00
using osu.Game.Rulesets.Mods ;
2020-03-13 06:29:11 +00:00
using osu.Game.Storyboards.Drawables ;
2018-04-13 09:19:50 +00:00
2017-09-13 09:22:24 +00:00
namespace osu.Game.Storyboards
{
2019-05-10 07:31:22 +00:00
public class Storyboard
2017-09-13 09:22:24 +00:00
{
private readonly Dictionary < string , StoryboardLayer > layers = new Dictionary < string , StoryboardLayer > ( ) ;
public IEnumerable < StoryboardLayer > Layers = > layers . Values ;
2018-04-13 09:19:50 +00:00
2018-02-16 03:07:59 +00:00
public BeatmapInfo BeatmapInfo = new BeatmapInfo ( ) ;
2018-04-13 09:19:50 +00:00
2020-10-19 21:32:04 +00:00
/// <summary>
/// Whether the storyboard can fall back to skin sprites in case no matching storyboard sprites are found.
/// </summary>
public bool UseSkinSprites { get ; set ; }
2017-09-15 09:23:37 +00:00
public bool HasDrawable = > Layers . Any ( l = > l . Elements . Any ( e = > e . IsDrawable ) ) ;
2018-04-13 09:19:50 +00:00
2021-01-04 06:16:01 +00:00
/// <summary>
/// Across all layers, find the earliest point in time that a storyboard element exists at.
/// Will return null if there are no elements.
/// </summary>
/// <remarks>
/// This iterates all elements and as such should be used sparingly or stored locally.
/// </remarks>
2022-12-19 07:42:21 +00:00
public double? EarliestEventTime = > Layers . SelectMany ( l = > l . Elements ) . MinBy ( e = > e . StartTime ) ? . StartTime ;
2019-03-26 07:18:15 +00:00
2021-04-12 20:02:19 +00:00
/// <summary>
/// Across all layers, find the latest point in time that a storyboard element ends at.
/// Will return null if there are no elements.
/// </summary>
2021-04-17 16:34:38 +00:00
/// <remarks>
/// This iterates all elements and as such should be used sparingly or stored locally.
/// Videos and samples return StartTime as their EndTIme.
/// </remarks>
2022-12-19 07:42:21 +00:00
public double? LatestEventTime = > Layers . SelectMany ( l = > l . Elements ) . MaxBy ( e = > e . GetEndTime ( ) ) ? . GetEndTime ( ) ;
2021-04-14 04:04:03 +00:00
2020-05-18 19:01:13 +00:00
/// <summary>
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
/// </summary>
private int minimumLayerDepth ;
2017-09-13 09:22:24 +00:00
public Storyboard ( )
{
2021-05-25 09:50:33 +00:00
layers . Add ( "Video" , new StoryboardVideoLayer ( "Video" , 4 , false ) ) ;
2017-09-13 09:22:24 +00:00
layers . Add ( "Background" , new StoryboardLayer ( "Background" , 3 ) ) ;
2020-03-25 02:08:08 +00:00
layers . Add ( "Fail" , new StoryboardLayer ( "Fail" , 2 ) { VisibleWhenPassing = false , } ) ;
layers . Add ( "Pass" , new StoryboardLayer ( "Pass" , 1 ) { VisibleWhenFailing = false , } ) ;
2020-05-18 19:01:13 +00:00
layers . Add ( "Foreground" , new StoryboardLayer ( "Foreground" , minimumLayerDepth = 0 ) ) ;
layers . Add ( "Overlay" , new StoryboardLayer ( "Overlay" , int . MinValue ) ) ;
2017-09-13 09:22:24 +00:00
}
2018-04-13 09:19:50 +00:00
2017-09-13 09:22:24 +00:00
public StoryboardLayer GetLayer ( string name )
{
2019-11-12 10:22:35 +00:00
if ( ! layers . TryGetValue ( name , out var layer ) )
2020-05-18 19:01:13 +00:00
layers [ name ] = layer = new StoryboardLayer ( name , - - minimumLayerDepth ) ;
2018-04-13 09:19:50 +00:00
2017-09-13 09:22:24 +00:00
return layer ;
}
2018-04-13 09:19:50 +00:00
2017-09-25 08:40:22 +00:00
/// <summary>
/// Whether the beatmap's background should be hidden while this storyboard is being displayed.
/// </summary>
2018-02-16 03:07:59 +00:00
public bool ReplacesBackground
2017-09-25 08:40:22 +00:00
{
2018-02-16 03:07:59 +00:00
get
{
2022-02-18 07:24:18 +00:00
string backgroundPath = BeatmapInfo . Metadata . BackgroundFile ;
2021-11-04 08:24:40 +00:00
2021-11-04 05:50:39 +00:00
if ( string . IsNullOrEmpty ( backgroundPath ) )
2018-02-16 03:07:59 +00:00
return false ;
2018-04-13 09:19:50 +00:00
2021-11-04 08:24:40 +00:00
// Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints.
backgroundPath = backgroundPath . ToLowerInvariant ( ) ;
2020-03-13 06:29:11 +00:00
return GetLayer ( "Background" ) . Elements . Any ( e = > e . Path . ToLowerInvariant ( ) = = backgroundPath ) ;
2018-02-16 03:07:59 +00:00
}
2017-09-25 08:40:22 +00:00
}
2018-04-13 09:19:50 +00:00
2022-08-10 06:48:38 +00:00
public DrawableStoryboard CreateDrawable ( IReadOnlyList < Mod > ? mods = null ) = >
2022-03-02 17:33:46 +00:00
new DrawableStoryboard ( this , mods ) ;
2020-10-23 06:33:38 +00:00
2022-11-08 05:34:28 +00:00
private static readonly string [ ] image_extensions = { @".png" , @".jpg" } ;
2022-08-10 06:48:38 +00:00
public Texture ? GetTextureFromPath ( string path , TextureStore textureStore )
2020-10-23 06:33:38 +00:00
{
2022-11-08 05:34:28 +00:00
string? resolvedPath = null ;
if ( Path . HasExtension ( path ) )
{
resolvedPath = BeatmapInfo . BeatmapSet ? . GetPathForFile ( path ) ;
}
else
{
// Just doing this extension logic locally here for simplicity.
//
// A more "sane" path may be to use the ISkinSource.GetTexture path (which will use the extensions of the underlying TextureStore),
// but comes with potential complexity (what happens if the user has beatmap skins disabled?).
foreach ( string ext in image_extensions )
{
if ( ( resolvedPath = BeatmapInfo . BeatmapSet ? . GetPathForFile ( $"{path}{ext}" ) ) ! = null )
break ;
}
}
2020-10-23 06:33:38 +00:00
2022-11-08 05:34:28 +00:00
if ( ! string . IsNullOrEmpty ( resolvedPath ) )
return textureStore . Get ( resolvedPath ) ;
2020-10-23 06:33:38 +00:00
2022-04-07 08:26:31 +00:00
return null ;
2020-10-23 06:33:38 +00:00
}
2017-09-13 09:22:24 +00:00
}
}