2019-01-24 16:43:03 +08: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 17:19:50 +08:00
2017-09-13 17:22:24 +08:00
using System.Collections.Generic ;
2022-11-08 13:34:28 +08:00
using System.IO ;
2017-09-13 17:22:24 +08:00
using System.Linq ;
2020-10-23 14:33:38 +08:00
using osu.Framework.Graphics.Textures ;
2020-03-13 14:29:11 +08:00
using osu.Game.Beatmaps ;
2022-03-03 01:33:46 +08:00
using osu.Game.Rulesets.Mods ;
2020-03-13 14:29:11 +08:00
using osu.Game.Storyboards.Drawables ;
2018-04-13 17:19:50 +08:00
2017-09-13 17:22:24 +08:00
namespace osu.Game.Storyboards
{
2019-05-10 15:31:22 +08:00
public class Storyboard
2017-09-13 17:22:24 +08:00
{
private readonly Dictionary < string , StoryboardLayer > layers = new Dictionary < string , StoryboardLayer > ( ) ;
public IEnumerable < StoryboardLayer > Layers = > layers . Values ;
2018-04-13 17:19:50 +08:00
2018-02-16 11:07:59 +08:00
public BeatmapInfo BeatmapInfo = new BeatmapInfo ( ) ;
2018-04-13 17:19:50 +08:00
2020-10-20 05:32:04 +08: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 17:23:37 +08:00
public bool HasDrawable = > Layers . Any ( l = > l . Elements . Any ( e = > e . IsDrawable ) ) ;
2018-04-13 17:19:50 +08:00
2021-01-04 14:16:01 +08: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 15:42:21 +08:00
public double? EarliestEventTime = > Layers . SelectMany ( l = > l . Elements ) . MinBy ( e = > e . StartTime ) ? . StartTime ;
2019-03-26 15:18:15 +08:00
2021-04-13 04:02:19 +08: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-18 00:34:38 +08: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 15:42:21 +08:00
public double? LatestEventTime = > Layers . SelectMany ( l = > l . Elements ) . MaxBy ( e = > e . GetEndTime ( ) ) ? . GetEndTime ( ) ;
2021-04-14 12:04:03 +08:00
2020-05-19 03:01:13 +08:00
/// <summary>
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
/// </summary>
private int minimumLayerDepth ;
2017-09-13 17:22:24 +08:00
public Storyboard ( )
{
2021-05-25 17:50:33 +08:00
layers . Add ( "Video" , new StoryboardVideoLayer ( "Video" , 4 , false ) ) ;
2017-09-13 17:22:24 +08:00
layers . Add ( "Background" , new StoryboardLayer ( "Background" , 3 ) ) ;
2020-03-25 10:08:08 +08:00
layers . Add ( "Fail" , new StoryboardLayer ( "Fail" , 2 ) { VisibleWhenPassing = false , } ) ;
layers . Add ( "Pass" , new StoryboardLayer ( "Pass" , 1 ) { VisibleWhenFailing = false , } ) ;
2020-05-19 03:01:13 +08:00
layers . Add ( "Foreground" , new StoryboardLayer ( "Foreground" , minimumLayerDepth = 0 ) ) ;
layers . Add ( "Overlay" , new StoryboardLayer ( "Overlay" , int . MinValue ) ) ;
2017-09-13 17:22:24 +08:00
}
2018-04-13 17:19:50 +08:00
2017-09-13 17:22:24 +08:00
public StoryboardLayer GetLayer ( string name )
{
2019-11-12 18:22:35 +08:00
if ( ! layers . TryGetValue ( name , out var layer ) )
2020-05-19 03:01:13 +08:00
layers [ name ] = layer = new StoryboardLayer ( name , - - minimumLayerDepth ) ;
2018-04-13 17:19:50 +08:00
2017-09-13 17:22:24 +08:00
return layer ;
}
2018-04-13 17:19:50 +08:00
2017-09-25 16:40:22 +08:00
/// <summary>
/// Whether the beatmap's background should be hidden while this storyboard is being displayed.
/// </summary>
2018-02-16 11:07:59 +08:00
public bool ReplacesBackground
2017-09-25 16:40:22 +08:00
{
2018-02-16 11:07:59 +08:00
get
{
2022-02-18 15:24:18 +08:00
string backgroundPath = BeatmapInfo . Metadata . BackgroundFile ;
2021-11-04 16:24:40 +08:00
2021-11-04 13:50:39 +08:00
if ( string . IsNullOrEmpty ( backgroundPath ) )
2018-02-16 11:07:59 +08:00
return false ;
2018-04-13 17:19:50 +08:00
2021-11-04 16:24:40 +08: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 14:29:11 +08:00
return GetLayer ( "Background" ) . Elements . Any ( e = > e . Path . ToLowerInvariant ( ) = = backgroundPath ) ;
2018-02-16 11:07:59 +08:00
}
2017-09-25 16:40:22 +08:00
}
2018-04-13 17:19:50 +08:00
2022-08-10 14:48:38 +08:00
public DrawableStoryboard CreateDrawable ( IReadOnlyList < Mod > ? mods = null ) = >
2022-03-03 01:33:46 +08:00
new DrawableStoryboard ( this , mods ) ;
2020-10-23 14:33:38 +08:00
2022-11-08 13:34:28 +08:00
private static readonly string [ ] image_extensions = { @".png" , @".jpg" } ;
2022-08-10 14:48:38 +08:00
public Texture ? GetTextureFromPath ( string path , TextureStore textureStore )
2020-10-23 14:33:38 +08:00
{
2022-11-08 13:34:28 +08: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 14:33:38 +08:00
2022-11-08 13:34:28 +08:00
if ( ! string . IsNullOrEmpty ( resolvedPath ) )
return textureStore . Get ( resolvedPath ) ;
2020-10-23 14:33:38 +08:00
2022-04-07 16:26:31 +08:00
return null ;
2020-10-23 14:33:38 +08:00
}
2017-09-13 17:22:24 +08:00
}
}