2022-03-27 05:43:17 +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.
2022-03-28 04:55:52 +08:00
#nullable enable
2022-03-27 05:43:17 +08:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
using osu.Framework.Input.Events ;
using osu.Framework.Layout ;
using osu.Game.Configuration ;
using osu.Game.Graphics.Containers ;
using osu.Game.Graphics.UserInterface ;
using osu.Game.Rulesets.Mods ;
using osuTK ;
using osuTK.Input ;
namespace osu.Game.Overlays.Mods
{
2022-04-04 01:42:52 +08:00
public abstract class ModSelectScreen : OsuFocusedOverlayContainer
2022-03-27 05:43:17 +08:00
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider ( OverlayColourScheme . Green ) ;
[Cached]
2022-03-27 06:21:17 +08:00
public Bindable < IReadOnlyList < Mod > > SelectedMods { get ; private set ; } = new Bindable < IReadOnlyList < Mod > > ( Array . Empty < Mod > ( ) ) ;
2022-03-27 05:43:17 +08:00
2022-04-05 02:02:47 +08:00
protected override bool StartHidden = > true ;
2022-03-28 04:55:52 +08:00
private Func < Mod , bool > isValidMod = m = > true ;
public Func < Mod , bool > IsValidMod
{
get = > isValidMod ;
set
{
isValidMod = value ? ? throw new ArgumentNullException ( nameof ( value ) ) ;
if ( IsLoaded )
updateAvailableMods ( ) ;
}
}
/// <summary>
/// Whether configurable <see cref="Mod"/>s can be configured by the local user.
/// </summary>
protected virtual bool AllowConfiguration = > true ;
/// <summary>
/// Whether the total score multiplier calculated from the current selected set of mods should be shown.
/// </summary>
protected virtual bool ShowTotalMultiplier = > true ;
protected virtual ModColumn CreateModColumn ( ModType modType , Key [ ] ? toggleKeys = null ) = > new ModColumn ( modType , false , toggleKeys ) ;
2022-03-27 05:43:17 +08:00
private readonly BindableBool customisationVisible = new BindableBool ( ) ;
2022-03-28 04:55:52 +08:00
private DifficultyMultiplierDisplay ? multiplierDisplay ;
private ModSettingsArea modSettingsArea = null ! ;
private Container aboveColumnsContainer = null ! ;
private FillFlowContainer < ModColumn > columnFlow = null ! ;
private GridContainer grid = null ! ;
private Container mainContent = null ! ;
2022-03-27 05:43:17 +08:00
2022-03-28 04:55:52 +08:00
private PopupScreenTitle header = null ! ;
private Container footer = null ! ;
2022-04-04 14:45:44 +08:00
2022-03-27 05:43:17 +08:00
[BackgroundDependencyLoader]
private void load ( )
{
RelativeSizeAxes = Axes . Both ;
RelativePositionAxes = Axes . Both ;
InternalChildren = new Drawable [ ]
{
mainContent = new Container
{
Origin = Anchor . BottomCentre ,
Anchor = Anchor . BottomCentre ,
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
{
grid = new GridContainer
{
RelativeSizeAxes = Axes . Both ,
RowDimensions = new [ ]
{
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( GridSizeMode . AutoSize ) ,
new Dimension ( ) ,
new Dimension ( GridSizeMode . Absolute , 75 ) ,
} ,
Content = new [ ]
{
new Drawable [ ]
{
2022-04-04 14:45:44 +08:00
header = new PopupScreenTitle
2022-03-27 05:43:17 +08:00
{
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
Title = "Mod Select" ,
Description = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun." ,
Close = Hide
}
} ,
new Drawable [ ]
{
2022-03-28 04:55:52 +08:00
aboveColumnsContainer = new Container
2022-03-27 05:43:17 +08:00
{
2022-04-05 17:25:27 +08:00
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
AutoSizeAxes = Axes . X ,
RelativePositionAxes = Axes . X ,
X = 0.3f ,
Height = DifficultyMultiplierDisplay . HEIGHT ,
Margin = new MarginPadding
{
Horizontal = 100 ,
Vertical = 10
2022-03-27 05:43:17 +08:00
}
}
} ,
new Drawable [ ]
{
2022-04-04 19:42:12 +08:00
new Container
2022-03-27 05:43:17 +08:00
{
2022-04-05 17:27:34 +08:00
Depth = float . MaxValue ,
2022-03-27 05:43:17 +08:00
RelativeSizeAxes = Axes . Both ,
RelativePositionAxes = Axes . Both ,
Children = new Drawable [ ]
{
new OsuScrollContainer ( Direction . Horizontal )
{
RelativeSizeAxes = Axes . Both ,
Masking = false ,
2022-04-05 15:40:09 +08:00
ClampExtension = 100 ,
2022-03-27 05:43:17 +08:00
ScrollbarOverlapsContent = false ,
Child = columnFlow = new ModColumnContainer
{
2022-04-05 16:14:59 +08:00
Direction = FillDirection . Horizontal ,
2022-03-27 05:43:17 +08:00
RelativeSizeAxes = Axes . Y ,
AutoSizeAxes = Axes . X ,
Spacing = new Vector2 ( 10 , 0 ) ,
2022-04-05 17:44:25 +08:00
Margin = new MarginPadding { Right = 70 } ,
2022-03-27 05:43:17 +08:00
Children = new [ ]
{
2022-03-28 04:55:52 +08:00
CreateModColumn ( ModType . DifficultyReduction , new [ ] { Key . Q , Key . W , Key . E , Key . R , Key . T , Key . Y , Key . U , Key . I , Key . O , Key . P } ) ,
CreateModColumn ( ModType . DifficultyIncrease , new [ ] { Key . A , Key . S , Key . D , Key . F , Key . G , Key . H , Key . J , Key . K , Key . L } ) ,
CreateModColumn ( ModType . Automation , new [ ] { Key . Z , Key . X , Key . C , Key . V , Key . B , Key . N , Key . M } ) ,
CreateModColumn ( ModType . Conversion ) ,
CreateModColumn ( ModType . Fun )
2022-03-27 05:43:17 +08:00
}
}
}
}
}
} ,
new [ ] { Empty ( ) }
}
} ,
2022-04-04 14:45:44 +08:00
footer = new Container
2022-03-27 05:43:17 +08:00
{
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Anchor = Anchor . BottomCentre ,
Origin = Anchor . BottomCentre ,
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . X ,
Height = 50 ,
Anchor = Anchor . BottomCentre ,
Origin = Anchor . BottomCentre ,
Colour = colourProvider . Background5
} ,
new ShearedToggleButton ( 200 )
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
Margin = new MarginPadding { Vertical = 14 , Left = 70 } ,
Text = "Mod Customisation" ,
Active = { BindTarget = customisationVisible }
}
}
} ,
new ClickToReturnContainer
{
RelativeSizeAxes = Axes . Both ,
HandleMouse = { BindTarget = customisationVisible } ,
OnClicked = ( ) = > customisationVisible . Value = false
}
}
} ,
modSettingsArea = new ModSettingsArea
{
Anchor = Anchor . BottomCentre ,
2022-04-05 02:02:47 +08:00
Origin = Anchor . BottomCentre ,
Height = 0
2022-03-27 05:43:17 +08:00
}
} ;
2022-03-28 04:55:52 +08:00
if ( ShowTotalMultiplier )
{
aboveColumnsContainer . Add ( multiplierDisplay = new DifficultyMultiplierDisplay
{
Anchor = Anchor . CentreRight ,
Origin = Anchor . CentreRight
} ) ;
}
2022-03-27 05:43:17 +08:00
columnFlow . Shear = new Vector2 ( ModPanel . SHEAR_X , 0 ) ;
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2022-03-27 06:21:17 +08:00
( ( IBindable < IReadOnlyList < Mod > > ) modSettingsArea . SelectedMods ) . BindTo ( SelectedMods ) ;
2022-03-28 06:16:10 +08:00
2022-03-27 06:21:17 +08:00
SelectedMods . BindValueChanged ( val = >
2022-03-27 05:43:17 +08:00
{
updateMultiplier ( ) ;
updateCustomisation ( val ) ;
2022-03-28 06:16:10 +08:00
updateSelectionFromBindable ( ) ;
2022-03-27 05:43:17 +08:00
} , true ) ;
2022-03-28 06:16:10 +08:00
foreach ( var column in columnFlow )
{
column . SelectedMods . BindValueChanged ( _ = > updateBindableFromSelection ( ) ) ;
}
2022-03-27 05:43:17 +08:00
customisationVisible . BindValueChanged ( _ = > updateCustomisationVisualState ( ) , true ) ;
2022-03-28 04:55:52 +08:00
updateAvailableMods ( ) ;
2022-03-27 05:43:17 +08:00
}
private void updateMultiplier ( )
{
2022-03-28 04:55:52 +08:00
if ( multiplierDisplay = = null )
return ;
2022-03-27 05:43:17 +08:00
double multiplier = 1.0 ;
2022-03-27 06:21:17 +08:00
foreach ( var mod in SelectedMods . Value )
2022-03-27 05:43:17 +08:00
multiplier * = mod . ScoreMultiplier ;
multiplierDisplay . Current . Value = multiplier ;
}
2022-03-28 04:55:52 +08:00
private void updateAvailableMods ( )
{
foreach ( var column in columnFlow )
column . Filter = isValidMod ;
}
2022-03-27 05:43:17 +08:00
private void updateCustomisation ( ValueChangedEvent < IReadOnlyList < Mod > > valueChangedEvent )
{
2022-03-28 04:55:52 +08:00
if ( ! AllowConfiguration )
return ;
2022-03-27 05:43:17 +08:00
bool anyCustomisableMod = false ;
bool anyModWithRequiredCustomisationAdded = false ;
2022-03-27 06:21:17 +08:00
foreach ( var mod in SelectedMods . Value )
2022-03-27 05:43:17 +08:00
{
anyCustomisableMod | = mod . GetSettingsSourceProperties ( ) . Any ( ) ;
anyModWithRequiredCustomisationAdded | = ! valueChangedEvent . OldValue . Contains ( mod ) & & mod . RequiresConfiguration ;
}
if ( anyCustomisableMod )
{
customisationVisible . Disabled = false ;
if ( anyModWithRequiredCustomisationAdded & & ! customisationVisible . Value )
customisationVisible . Value = true ;
}
else
{
if ( customisationVisible . Value )
customisationVisible . Value = false ;
customisationVisible . Disabled = true ;
}
}
private void updateCustomisationVisualState ( )
{
2022-04-05 15:56:24 +08:00
const double transition_duration = 300 ;
2022-04-04 14:50:40 +08:00
grid . FadeColour ( customisationVisible . Value ? Colour4 . Gray : Colour4 . White , transition_duration , Easing . InOutCubic ) ;
2022-03-27 05:43:17 +08:00
float modAreaHeight = customisationVisible . Value ? ModSettingsArea . HEIGHT : 0 ;
2022-04-04 14:50:40 +08:00
modSettingsArea . ResizeHeightTo ( modAreaHeight , transition_duration , Easing . InOutCubic ) ;
mainContent . TransformTo ( nameof ( Margin ) , new MarginPadding { Bottom = modAreaHeight } , transition_duration , Easing . InOutCubic ) ;
2022-03-27 05:43:17 +08:00
}
2022-03-28 06:16:10 +08:00
private bool selectionBindableSyncInProgress ;
private void updateSelectionFromBindable ( )
{
if ( selectionBindableSyncInProgress )
return ;
selectionBindableSyncInProgress = true ;
foreach ( var column in columnFlow )
column . SelectedMods . Value = SelectedMods . Value . Where ( mod = > mod . Type = = column . ModType ) . ToArray ( ) ;
selectionBindableSyncInProgress = false ;
}
private void updateBindableFromSelection ( )
{
if ( selectionBindableSyncInProgress )
return ;
selectionBindableSyncInProgress = true ;
SelectedMods . Value = columnFlow . SelectMany ( column = > column . SelectedMods . Value ) . ToArray ( ) ;
selectionBindableSyncInProgress = false ;
}
2022-03-27 05:43:17 +08:00
protected override void PopIn ( )
{
2022-04-05 17:38:31 +08:00
const double fade_in_duration = 400 ;
2022-04-04 14:45:44 +08:00
2022-03-27 05:43:17 +08:00
base . PopIn ( ) ;
2022-04-05 17:38:31 +08:00
this . FadeIn ( fade_in_duration , Easing . OutQuint ) ;
2022-04-04 14:45:44 +08:00
header . MoveToY ( 0 , fade_in_duration , Easing . OutQuint ) ;
footer . MoveToY ( 0 , fade_in_duration , Easing . OutQuint ) ;
2022-03-28 04:55:52 +08:00
multiplierDisplay ?
2022-04-05 17:38:31 +08:00
. Delay ( fade_in_duration * 0.65f )
. FadeIn ( fade_in_duration / 2 , Easing . OutQuint )
2022-04-05 17:25:27 +08:00
. ScaleTo ( 1 , fade_in_duration , Easing . OutElastic ) ;
2022-04-05 17:27:34 +08:00
for ( int i = 0 ; i < columnFlow . Count ; i + + )
{
columnFlow [ i ] . TopLevelContent
. Delay ( i * 30 )
2022-04-05 17:38:31 +08:00
. MoveToY ( 0 , fade_in_duration , Easing . OutQuint )
. FadeIn ( fade_in_duration , Easing . OutQuint ) ;
2022-04-05 17:27:34 +08:00
}
2022-03-27 05:43:17 +08:00
}
protected override void PopOut ( )
{
2022-04-04 14:45:44 +08:00
const double fade_out_duration = 500 ;
2022-03-27 05:43:17 +08:00
base . PopOut ( ) ;
2022-04-05 17:38:31 +08:00
this . FadeOut ( fade_out_duration , Easing . OutQuint ) ;
2022-04-04 14:45:44 +08:00
2022-03-28 04:55:52 +08:00
multiplierDisplay ?
2022-04-05 17:38:31 +08:00
. FadeOut ( fade_out_duration / 2 , Easing . OutQuint )
2022-04-05 17:25:27 +08:00
. ScaleTo ( 0.75f , fade_out_duration , Easing . OutQuint ) ;
2022-04-04 14:45:44 +08:00
header . MoveToY ( - header . DrawHeight , fade_out_duration , Easing . OutQuint ) ;
footer . MoveToY ( footer . DrawHeight , fade_out_duration , Easing . OutQuint ) ;
2022-04-05 17:27:34 +08:00
for ( int i = 0 ; i < columnFlow . Count ; i + + )
{
const float distance = 700 ;
2022-04-05 17:38:31 +08:00
columnFlow [ i ] . TopLevelContent
. MoveToY ( i % 2 = = 0 ? - distance : distance , fade_out_duration , Easing . OutQuint )
. FadeOut ( fade_out_duration , Easing . OutQuint ) ;
2022-04-05 17:27:34 +08:00
}
2022-03-27 05:43:17 +08:00
}
private class ModColumnContainer : FillFlowContainer < ModColumn >
{
private readonly LayoutValue drawSizeLayout = new LayoutValue ( Invalidation . DrawSize ) ;
public ModColumnContainer ( )
{
AddLayout ( drawSizeLayout ) ;
}
public override void Add ( ModColumn column )
{
base . Add ( column ) ;
Debug . Assert ( column ! = null ) ;
column . Shear = Vector2 . Zero ;
}
protected override void Update ( )
{
base . Update ( ) ;
if ( ! drawSizeLayout . IsValid )
{
Padding = new MarginPadding
{
Left = DrawHeight * ModPanel . SHEAR_X ,
Bottom = 10
} ;
drawSizeLayout . Validate ( ) ;
}
}
}
private class ClickToReturnContainer : Container
{
public BindableBool HandleMouse { get ; } = new BindableBool ( ) ;
2022-03-28 04:55:52 +08:00
public Action ? OnClicked { get ; set ; }
2022-03-27 05:43:17 +08:00
protected override bool Handle ( UIEvent e )
{
if ( ! HandleMouse . Value )
return base . Handle ( e ) ;
switch ( e )
{
case ClickEvent _ :
OnClicked ? . Invoke ( ) ;
return true ;
case MouseEvent _ :
return true ;
}
return base . Handle ( e ) ;
}
}
}
}