2019-09-11 09:12:43 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 08:43:03 +00:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
2018-11-07 08:24:05 +00:00
using System.Linq ;
2018-11-20 07:51:59 +00:00
using osuTK.Graphics ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Game.Graphics ;
using osu.Game.Rulesets.Objects.Drawables ;
2018-07-02 03:31:41 +00:00
using osu.Framework.Allocation ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Input.Bindings ;
using osu.Game.Rulesets.Judgements ;
2019-09-11 09:20:41 +00:00
using osu.Game.Rulesets.Mania.Objects.Drawables ;
2018-06-07 11:49:06 +00:00
using osu.Game.Rulesets.Mania.UI.Components ;
2018-04-13 09:19:50 +00:00
using osu.Game.Rulesets.UI.Scrolling ;
2020-03-30 14:14:30 +00:00
using osu.Game.Skinning ;
2018-11-26 01:44:48 +00:00
using osuTK ;
2020-03-31 03:53:17 +00:00
using osu.Game.Rulesets.Mania.Beatmaps ;
2020-05-21 06:15:24 +00:00
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces ;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Rulesets.Mania.UI
{
2020-03-30 14:07:32 +00:00
[Cached]
2018-11-06 06:46:36 +00:00
public class Column : ScrollingPlayfield , IKeyBindingHandler < ManiaAction > , IHasAccentColour
2018-04-13 09:19:50 +00:00
{
2019-09-11 09:20:41 +00:00
public const float COLUMN_WIDTH = 80 ;
2020-04-01 04:38:03 +00:00
public const float SPECIAL_COLUMN_WIDTH = 70 ;
2018-04-13 09:19:50 +00:00
2018-11-12 09:24:18 +00:00
/// <summary>
/// The index of this column as part of the whole playfield.
/// </summary>
public readonly int Index ;
2018-07-02 03:31:41 +00:00
public readonly Bindable < ManiaAction > Action = new Bindable < ManiaAction > ( ) ;
2018-04-13 09:19:50 +00:00
2018-06-07 12:40:12 +00:00
private readonly ColumnHitObjectArea hitObjectArea ;
2018-04-13 09:19:50 +00:00
internal readonly Container TopLevelContainer ;
2020-05-18 08:47:47 +00:00
public Container UnderlayElements = > hitObjectArea . UnderlayElements ;
2018-11-12 09:24:18 +00:00
public Column ( int index )
2018-04-13 09:19:50 +00:00
{
2018-11-12 09:24:18 +00:00
Index = index ;
2018-04-13 09:19:50 +00:00
RelativeSizeAxes = Axes . Y ;
2020-04-08 09:23:24 +00:00
Width = COLUMN_WIDTH ;
2018-06-07 02:19:36 +00:00
2020-04-02 09:39:49 +00:00
Drawable background = new SkinnableDrawable ( new ManiaSkinComponent ( ManiaSkinComponents . ColumnBackground , Index ) , _ = > new DefaultColumnBackground ( ) )
2020-03-30 14:14:30 +00:00
{
RelativeSizeAxes = Axes . Both
} ;
2018-06-07 12:13:29 +00:00
InternalChildren = new [ ]
2018-04-13 09:19:50 +00:00
{
2018-06-07 12:13:29 +00:00
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background . CreateProxy ( ) ,
2020-04-02 09:39:49 +00:00
hitObjectArea = new ColumnHitObjectArea ( Index , HitObjectContainer ) { RelativeSizeAxes = Axes . Both } ,
new SkinnableDrawable ( new ManiaSkinComponent ( ManiaSkinComponents . KeyArea , Index ) , _ = > new DefaultKeyArea ( ) )
2018-04-13 09:19:50 +00:00
{
2020-03-31 02:23:33 +00:00
RelativeSizeAxes = Axes . Both
2018-04-13 09:19:50 +00:00
} ,
2018-06-07 12:13:29 +00:00
background ,
2018-04-13 09:19:50 +00:00
TopLevelContainer = new Container { RelativeSizeAxes = Axes . Both }
} ;
2020-03-31 03:17:44 +00:00
TopLevelContainer . Add ( hitObjectArea . Explosions . CreateProxy ( ) ) ;
2018-04-13 09:19:50 +00:00
}
public override Axes RelativeSizeAxes = > Axes . Y ;
2020-04-01 04:38:03 +00:00
public ColumnType ColumnType { get ; set ; }
2019-02-28 04:31:40 +00:00
2020-04-01 04:38:03 +00:00
public bool IsSpecial = > ColumnType = = ColumnType . Special ;
2020-03-31 03:53:17 +00:00
2020-03-31 06:23:59 +00:00
public Color4 AccentColour { get ; set ; }
2018-04-13 09:19:50 +00:00
2018-07-11 08:07:14 +00:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent )
2018-07-02 03:31:41 +00:00
{
2018-07-11 08:07:14 +00:00
var dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
2018-07-02 03:31:41 +00:00
dependencies . CacheAs < IBindable < ManiaAction > > ( Action ) ;
return dependencies ;
}
2018-04-13 09:19:50 +00:00
/// <summary>
/// Adds a DrawableHitObject to this Playfield.
/// </summary>
/// <param name="hitObject">The DrawableHitObject to add.</param>
public override void Add ( DrawableHitObject hitObject )
{
2019-07-22 05:45:25 +00:00
hitObject . AccentColour . Value = AccentColour ;
2018-08-06 01:54:16 +00:00
hitObject . OnNewResult + = OnNewResult ;
2018-04-13 09:19:50 +00:00
2018-11-07 08:24:05 +00:00
HitObjectContainer . Add ( hitObject ) ;
2018-04-13 09:19:50 +00:00
}
2018-11-19 07:20:21 +00:00
public override bool Remove ( DrawableHitObject h )
{
if ( ! base . Remove ( h ) )
return false ;
h . OnNewResult - = OnNewResult ;
return true ;
}
2018-08-06 01:54:16 +00:00
internal void OnNewResult ( DrawableHitObject judgedObject , JudgementResult result )
2018-04-13 09:19:50 +00:00
{
2019-02-21 09:56:34 +00:00
if ( ! result . IsHit | | ! judgedObject . DisplayResult | | ! DisplayJudgements . Value )
2018-04-13 09:19:50 +00:00
return ;
2020-04-02 09:39:49 +00:00
var explosion = new SkinnableDrawable ( new ManiaSkinComponent ( ManiaSkinComponents . HitExplosion , Index ) , _ = >
2020-04-01 10:19:32 +00:00
new DefaultHitExplosion ( judgedObject . AccentColour . Value , judgedObject is DrawableHoldNoteTick ) )
2018-06-08 06:23:47 +00:00
{
2020-04-01 10:19:32 +00:00
RelativeSizeAxes = Axes . Both
2020-04-02 06:57:02 +00:00
} ;
hitObjectArea . Explosions . Add ( explosion ) ;
explosion . Delay ( 200 ) . Expire ( true ) ;
2018-04-13 09:19:50 +00:00
}
public bool OnPressed ( ManiaAction action )
{
2019-02-21 09:56:34 +00:00
if ( action ! = Action . Value )
2018-04-13 09:19:50 +00:00
return false ;
2018-06-06 05:18:38 +00:00
var nextObject =
2018-11-07 08:24:05 +00:00
HitObjectContainer . AliveObjects . FirstOrDefault ( h = > h . HitObject . StartTime > Time . Current ) ? ?
2018-06-06 05:18:38 +00:00
// fallback to non-alive objects to find next off-screen object
2018-11-07 08:24:05 +00:00
HitObjectContainer . Objects . FirstOrDefault ( h = > h . HitObject . StartTime > Time . Current ) ? ?
HitObjectContainer . Objects . LastOrDefault ( ) ;
2018-06-06 05:18:38 +00:00
nextObject ? . PlaySamples ( ) ;
2018-04-13 09:19:50 +00:00
return true ;
}
2020-01-22 04:22:34 +00:00
public void OnReleased ( ManiaAction action )
{
}
2018-11-13 05:13:29 +00:00
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos )
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
2020-04-08 05:08:34 +00:00
= > DrawRectangle . Inflate ( new Vector2 ( Stage . COLUMN_SPACING / 2 , 0 ) ) . Contains ( ToLocalSpace ( screenSpacePos ) ) ;
2020-05-21 04:33:02 +00:00
2020-05-21 06:16:30 +00:00
/// <summary>
/// Given a time, return the screen space position within this column.
/// </summary>
public Vector2 ScreenSpacePositionAtTime ( double time )
2020-05-21 04:33:02 +00:00
{
var pos = ScrollingInfo . Algorithm . PositionAt ( time , Time . Current , ScrollingInfo . TimeRange . Value , HitObjectContainer . DrawHeight ) ;
2020-05-21 06:15:24 +00:00
switch ( ScrollingInfo . Direction . Value )
2020-05-21 04:33:02 +00:00
{
2020-05-21 06:15:24 +00:00
case ScrollingDirection . Down :
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
// so when scrolling downwards the coordinates need to be flipped.
pos = HitObjectContainer . DrawHeight - pos ;
// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
pos - = DefaultNotePiece . NOTE_HEIGHT / 2 ;
break ;
case ScrollingDirection . Up :
pos + = DefaultNotePiece . NOTE_HEIGHT / 2 ;
break ;
2020-05-21 04:33:02 +00:00
}
return HitObjectContainer . ToScreenSpace ( new Vector2 ( HitObjectContainer . DrawWidth / 2 , pos ) ) ;
}
2020-05-21 05:25:37 +00:00
2020-05-21 06:16:30 +00:00
/// <summary>
/// Given a position in screen space, return the time within this column.
/// </summary>
2020-05-21 05:25:37 +00:00
public double TimeAtScreenSpacePosition ( Vector2 screenSpacePosition )
{
// convert to local space of column so we can snap and fetch correct location.
Vector2 localPosition = HitObjectContainer . ToLocalSpace ( screenSpacePosition ) ;
2020-05-21 06:15:24 +00:00
switch ( ScrollingInfo . Direction . Value )
2020-05-21 05:25:37 +00:00
{
2020-05-21 06:15:24 +00:00
case ScrollingDirection . Down :
// as above
localPosition . Y = HitObjectContainer . DrawHeight - localPosition . Y ;
break ;
2020-05-21 05:25:37 +00:00
}
2020-05-21 06:15:24 +00:00
// offset for the fact that blueprints are centered, as above.
localPosition . Y - = DefaultNotePiece . NOTE_HEIGHT / 2 ;
2020-05-21 05:25:37 +00:00
return ScrollingInfo . Algorithm . TimeAt ( localPosition . Y , Time . Current , ScrollingInfo . TimeRange . Value , HitObjectContainer . DrawHeight ) ;
}
2018-04-13 09:19:50 +00:00
}
}