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 ;
2017-09-13 17:22:24 +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 ;
2022-03-03 01:33:46 +08:00
using osu.Game.Rulesets.Mods ;
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
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>
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 ;
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-03-04 11:05:02 +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
public Drawable CreateSpriteFromResourcePath ( string path , TextureStore textureStore )
{
Drawable drawable = null ;
2021-11-22 15:10:13 +08:00
2022-01-12 17:05:25 +08:00
string storyboardPath = BeatmapInfo . BeatmapSet ? . Files . FirstOrDefault ( f = > f . Filename . Equals ( path , StringComparison . OrdinalIgnoreCase ) ) ? . File . 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 )
2022-03-14 08:35:08 +08:00
{
drawable = new SkinnableSprite ( path )
{
RelativeSizeAxes = Axes . None ,
AutoSizeAxes = Axes . Both ,
} ;
}
2020-10-23 14:33:38 +08:00
return drawable ;
}
2017-09-13 17:22:24 +08:00
}
}