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
2020-10-23 14:33:38 +08:00
using System ;
2018-04-13 17:19:50 +08:00
using System.Collections.Generic ;
using System.Linq ;
2020-10-23 14:33:38 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Sprites ;
using osu.Framework.Graphics.Textures ;
2020-03-13 14:29:11 +08:00
using osu.Game.Beatmaps ;
2021-11-19 15:07:55 +08:00
using osu.Game.Extensions ;
2020-10-23 14:33:38 +08:00
using osu.Game.Skinning ;
2020-03-13 14:29:11 +08:00
using osu.Game.Storyboards.Drawables ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Storyboards
{
2019-05-10 15:31:22 +08:00
public class Storyboard
2018-04-13 17:19:50 +08:00
{
private readonly Dictionary < string , StoryboardLayer > layers = new Dictionary < string , StoryboardLayer > ( ) ;
public IEnumerable < StoryboardLayer > Layers = > layers . Values ;
public BeatmapInfo BeatmapInfo = new BeatmapInfo ( ) ;
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 ; }
2018-04-13 17:19:50 +08:00
public bool HasDrawable = > Layers . Any ( l = > l . Elements . Any ( e = > e . IsDrawable ) ) ;
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>
public double? EarliestEventTime = > Layers . SelectMany ( l = > l . Elements ) . OrderBy ( e = > e . StartTime ) . FirstOrDefault ( ) ? . 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>
2021-04-18 03:27:22 +08:00
public double? LatestEventTime = > Layers . SelectMany ( l = > l . Elements ) . OrderBy ( e = > e . GetEndTime ( ) ) . LastOrDefault ( ) ? . 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 ;
2018-04-13 17:19:50 +08:00
public Storyboard ( )
{
2021-05-25 17:50:33 +08:00
layers . Add ( "Video" , new StoryboardVideoLayer ( "Video" , 4 , false ) ) ;
2018-04-13 17:19:50 +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 ) ) ;
2018-04-13 17:19:50 +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
return layer ;
}
/// <summary>
/// Whether the beatmap's background should be hidden while this storyboard is being displayed.
/// </summary>
public bool ReplacesBackground
{
get
{
2021-11-04 16:24:40 +08:00
string backgroundPath = BeatmapInfo . BeatmapSet ? . Metadata ? . BackgroundFile ;
2021-11-04 13:50:39 +08:00
if ( string . IsNullOrEmpty ( backgroundPath ) )
2018-04-13 17:19:50 +08:00
return false ;
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-04-13 17:19:50 +08:00
}
}
2021-11-15 17:46:11 +08:00
public DrawableStoryboard CreateDrawable ( IWorkingBeatmap working = null ) = >
2021-05-25 15:06:39 +08:00
new DrawableStoryboard ( this ) ;
2020-10-23 14:33:38 +08:00
public Drawable CreateSpriteFromResourcePath ( string path , TextureStore textureStore )
{
Drawable drawable = null ;
2021-11-22 15:10:13 +08:00
string storyboardPath = BeatmapInfo . BeatmapSet ? . Files . FirstOrDefault ( f = > f . Filename . Equals ( path , StringComparison . OrdinalIgnoreCase ) ) ? . FileInfo . GetStoragePath ( ) ;
2020-10-23 14:33:38 +08:00
2021-11-04 16:24:40 +08:00
if ( ! string . IsNullOrEmpty ( storyboardPath ) )
2020-10-23 14:33:38 +08:00
drawable = new Sprite { Texture = textureStore . Get ( storyboardPath ) } ;
// if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy.
else if ( UseSkinSprites )
drawable = new SkinnableSprite ( path ) ;
return drawable ;
}
2018-04-13 17:19:50 +08:00
}
}