mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
Merge branch 'master' into ingame-leaderboard-general-implementation
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -15,7 +16,11 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
public class BreakOverlay : Container
|
||||
{
|
||||
private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2;
|
||||
/// <summary>
|
||||
/// The duration of the break overlay fading.
|
||||
/// </summary>
|
||||
public const double BREAK_FADE_DURATION = BreakPeriod.MIN_BREAK_DURATION / 2;
|
||||
|
||||
private const float remaining_time_container_max_size = 0.3f;
|
||||
private const int vertical_margin = 25;
|
||||
|
||||
@ -29,12 +34,27 @@ namespace osu.Game.Screens.Play
|
||||
set
|
||||
{
|
||||
breaks = value;
|
||||
initializeBreaks();
|
||||
|
||||
// reset index in case the new breaks list is smaller than last one
|
||||
isBreakTime.Value = false;
|
||||
CurrentBreakIndex = 0;
|
||||
|
||||
if (IsLoaded)
|
||||
initializeBreaks();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the gameplay is currently in a break.
|
||||
/// </summary>
|
||||
public IBindable<bool> IsBreakTime => isBreakTime;
|
||||
|
||||
protected int CurrentBreakIndex;
|
||||
|
||||
private readonly BindableBool isBreakTime = new BindableBool();
|
||||
|
||||
private readonly Container remainingTimeAdjustmentBox;
|
||||
private readonly Container remainingTimeBox;
|
||||
private readonly RemainingTimeCounter remainingTimeCounter;
|
||||
@ -109,10 +129,36 @@ namespace osu.Game.Screens.Play
|
||||
initializeBreaks();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
updateBreakTimeBindable();
|
||||
}
|
||||
|
||||
private void updateBreakTimeBindable()
|
||||
{
|
||||
if (breaks == null || breaks.Count == 0)
|
||||
return;
|
||||
|
||||
var time = Clock.CurrentTime;
|
||||
|
||||
if (time > breaks[CurrentBreakIndex].EndTime)
|
||||
{
|
||||
while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
|
||||
CurrentBreakIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
|
||||
CurrentBreakIndex--;
|
||||
}
|
||||
|
||||
var currentBreak = breaks[CurrentBreakIndex];
|
||||
isBreakTime.Value = currentBreak.HasEffect && currentBreak.Contains(time);
|
||||
}
|
||||
|
||||
private void initializeBreaks()
|
||||
{
|
||||
if (!IsLoaded) return; // we need a clock.
|
||||
|
||||
FinishTransforms(true);
|
||||
Scheduler.CancelDelayedTasks();
|
||||
|
||||
@ -125,25 +171,25 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
using (BeginAbsoluteSequence(b.StartTime, true))
|
||||
{
|
||||
fadeContainer.FadeIn(fade_duration);
|
||||
breakArrows.Show(fade_duration);
|
||||
fadeContainer.FadeIn(BREAK_FADE_DURATION);
|
||||
breakArrows.Show(BREAK_FADE_DURATION);
|
||||
|
||||
remainingTimeAdjustmentBox
|
||||
.ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint)
|
||||
.Delay(b.Duration - fade_duration)
|
||||
.ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
|
||||
.Delay(b.Duration - BREAK_FADE_DURATION)
|
||||
.ResizeWidthTo(0);
|
||||
|
||||
remainingTimeBox
|
||||
.ResizeWidthTo(0, b.Duration - fade_duration)
|
||||
.ResizeWidthTo(0, b.Duration - BREAK_FADE_DURATION)
|
||||
.Then()
|
||||
.ResizeWidthTo(1);
|
||||
|
||||
remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration);
|
||||
|
||||
using (BeginDelayedSequence(b.Duration - fade_duration, true))
|
||||
using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION, true))
|
||||
{
|
||||
fadeContainer.FadeOut(fade_duration);
|
||||
breakArrows.Hide(fade_duration);
|
||||
fadeContainer.FadeOut(BREAK_FADE_DURATION);
|
||||
breakArrows.Hide(BREAK_FADE_DURATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
protected override bool ShowDimContent => ShowStoryboard.Value && UserDimLevel.Value < 1;
|
||||
protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1;
|
||||
|
||||
private void initializeStoryboard(bool async)
|
||||
{
|
||||
|
@ -304,6 +304,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private class Button : DialogButton
|
||||
{
|
||||
// required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved)
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
@ -312,5 +313,22 @@ namespace osu.Game.Screens.Play
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private GlobalActionContainer globalAction { get; set; }
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case ScrollEvent _:
|
||||
if (ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
|
||||
return globalAction.TriggerEvent(e);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return base.Handle(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
102
osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
Normal file
102
osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
Normal file
@ -0,0 +1,102 @@
|
||||
// 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.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class HitErrorDisplay : Container<HitErrorMeter>
|
||||
{
|
||||
private const int fade_duration = 200;
|
||||
private const int margin = 10;
|
||||
|
||||
private readonly Bindable<ScoreMeterType> type = new Bindable<ScoreMeterType>();
|
||||
|
||||
private readonly HitWindows hitWindows;
|
||||
|
||||
private readonly ScoreProcessor processor;
|
||||
|
||||
public HitErrorDisplay(ScoreProcessor processor, HitWindows hitWindows)
|
||||
{
|
||||
this.processor = processor;
|
||||
this.hitWindows = hitWindows;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
processor.NewJudgement += onNewJudgement;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
config.BindWith(OsuSetting.ScoreMeter, type);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
type.BindValueChanged(typeChanged, true);
|
||||
}
|
||||
|
||||
private void onNewJudgement(JudgementResult result)
|
||||
{
|
||||
if (result.HitObject.HitWindows == null)
|
||||
return;
|
||||
|
||||
foreach (var c in Children)
|
||||
c.OnNewJudgement(result);
|
||||
}
|
||||
|
||||
private void typeChanged(ValueChangedEvent<ScoreMeterType> type)
|
||||
{
|
||||
Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint));
|
||||
|
||||
if (hitWindows == null)
|
||||
return;
|
||||
|
||||
switch (type.NewValue)
|
||||
{
|
||||
case ScoreMeterType.HitErrorBoth:
|
||||
createBar(false);
|
||||
createBar(true);
|
||||
break;
|
||||
|
||||
case ScoreMeterType.HitErrorLeft:
|
||||
createBar(false);
|
||||
break;
|
||||
|
||||
case ScoreMeterType.HitErrorRight:
|
||||
createBar(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void createBar(bool rightAligned)
|
||||
{
|
||||
var display = new BarHitErrorMeter(hitWindows, rightAligned)
|
||||
{
|
||||
Margin = new MarginPadding(margin),
|
||||
Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft,
|
||||
Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft,
|
||||
Alpha = 0,
|
||||
};
|
||||
|
||||
Add(display);
|
||||
display.FadeInFromZero(fade_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
processor.NewJudgement -= onNewJudgement;
|
||||
}
|
||||
}
|
||||
}
|
283
osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
Normal file
283
osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
Normal file
@ -0,0 +1,283 @@
|
||||
// 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.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
{
|
||||
public class BarHitErrorMeter : HitErrorMeter
|
||||
{
|
||||
private readonly Anchor alignment;
|
||||
|
||||
private const int arrow_move_duration = 400;
|
||||
|
||||
private const int judgement_line_width = 6;
|
||||
|
||||
private const int bar_height = 200;
|
||||
|
||||
private const int bar_width = 2;
|
||||
|
||||
private const int spacing = 2;
|
||||
|
||||
private const float chevron_size = 8;
|
||||
|
||||
private SpriteIcon arrow;
|
||||
|
||||
private Container colourBarsEarly;
|
||||
private Container colourBarsLate;
|
||||
|
||||
private Container judgementsContainer;
|
||||
|
||||
private double maxHitWindow;
|
||||
|
||||
public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false)
|
||||
: base(hitWindows)
|
||||
{
|
||||
alignment = rightAligned ? Anchor.x0 : Anchor.x2;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
Height = bar_height,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(spacing, 0),
|
||||
Margin = new MarginPadding(2),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
judgementsContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.y1 | alignment,
|
||||
Origin = Anchor.y1 | alignment,
|
||||
Width = judgement_line_width,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
colourBars = new Container
|
||||
{
|
||||
Width = bar_width,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.y1 | alignment,
|
||||
Origin = Anchor.y1 | alignment,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
colourBarsEarly = new Container
|
||||
{
|
||||
Anchor = Anchor.y1 | alignment,
|
||||
Origin = alignment,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Scale = new Vector2(1, -1),
|
||||
},
|
||||
colourBarsLate = new Container
|
||||
{
|
||||
Anchor = Anchor.y1 | alignment,
|
||||
Origin = alignment,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Y = -10,
|
||||
Size = new Vector2(10),
|
||||
Icon = FontAwesome.Solid.ShippingFast,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Y = 10,
|
||||
Size = new Vector2(10),
|
||||
Icon = FontAwesome.Solid.Bicycle,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.y1 | alignment,
|
||||
Origin = Anchor.y1 | alignment,
|
||||
Width = chevron_size,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Child = arrow = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
Y = 0.5f,
|
||||
Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft,
|
||||
Size = new Vector2(chevron_size),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
createColourBars(colours);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
colourBars.Height = 0;
|
||||
colourBars.ResizeHeightTo(1, 800, Easing.OutQuint);
|
||||
|
||||
arrow.Alpha = 0;
|
||||
arrow.Delay(200).FadeInFromZero(600);
|
||||
}
|
||||
|
||||
private void createColourBars(OsuColour colours)
|
||||
{
|
||||
var windows = HitWindows.GetAllAvailableWindows().ToArray();
|
||||
|
||||
maxHitWindow = windows.First().length;
|
||||
|
||||
for (var i = 0; i < windows.Length; i++)
|
||||
{
|
||||
var (result, length) = windows[i];
|
||||
|
||||
colourBarsEarly.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0));
|
||||
colourBarsLate.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0));
|
||||
}
|
||||
|
||||
// a little nub to mark the centre point.
|
||||
var centre = createColourBar(windows.Last().result, 0.01f);
|
||||
centre.Anchor = centre.Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2);
|
||||
centre.Width = 2.5f;
|
||||
colourBars.Add(centre);
|
||||
|
||||
Color4 getColour(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Meh:
|
||||
return colours.Yellow;
|
||||
|
||||
case HitResult.Ok:
|
||||
return colours.Green;
|
||||
|
||||
case HitResult.Good:
|
||||
return colours.GreenLight;
|
||||
|
||||
case HitResult.Great:
|
||||
return colours.Blue;
|
||||
|
||||
default:
|
||||
return colours.BlueLight;
|
||||
}
|
||||
}
|
||||
|
||||
Drawable createColourBar(HitResult result, float height, bool first = false)
|
||||
{
|
||||
var colour = getColour(result);
|
||||
|
||||
if (first)
|
||||
{
|
||||
// the first bar needs gradient rendering.
|
||||
const float gradient_start = 0.8f;
|
||||
|
||||
return new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = getColour(result),
|
||||
Height = height * gradient_start
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(colour, colour.Opacity(0)),
|
||||
Y = gradient_start,
|
||||
Height = height * (1 - gradient_start)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colour,
|
||||
Height = height
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private double floatingAverage;
|
||||
private Container colourBars;
|
||||
|
||||
public override void OnNewJudgement(JudgementResult judgement)
|
||||
{
|
||||
if (!judgement.IsHit)
|
||||
return;
|
||||
|
||||
judgementsContainer.Add(new JudgementLine
|
||||
{
|
||||
Y = getRelativeJudgementPosition(judgement.TimeOffset),
|
||||
Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2,
|
||||
Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2),
|
||||
});
|
||||
|
||||
arrow.MoveToY(
|
||||
getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1)
|
||||
, arrow_move_duration, Easing.Out);
|
||||
}
|
||||
|
||||
private float getRelativeJudgementPosition(double value) => (float)((value / maxHitWindow) + 1) / 2;
|
||||
|
||||
private class JudgementLine : CompositeDrawable
|
||||
{
|
||||
private const int judgement_fade_duration = 10000;
|
||||
|
||||
public JudgementLine()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
RelativePositionAxes = Axes.Y;
|
||||
Height = 3;
|
||||
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Width = 0;
|
||||
|
||||
this.ResizeWidthTo(1, 200, Easing.OutElasticHalf);
|
||||
this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
Normal file
21
osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
{
|
||||
public abstract class HitErrorMeter : CompositeDrawable
|
||||
{
|
||||
protected readonly HitWindows HitWindows;
|
||||
|
||||
protected HitErrorMeter(HitWindows hitWindows)
|
||||
{
|
||||
HitWindows = hitWindows;
|
||||
}
|
||||
|
||||
public abstract void OnNewJudgement(JudgementResult judgement);
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ namespace osu.Game.Screens.Play
|
||||
public readonly HealthDisplay HealthDisplay;
|
||||
public readonly SongProgress Progress;
|
||||
public readonly ModDisplay ModDisplay;
|
||||
public readonly HitErrorDisplay HitErrorDisplay;
|
||||
public readonly HoldForMenuButton HoldToQuit;
|
||||
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
||||
|
||||
@ -84,6 +85,7 @@ namespace osu.Game.Screens.Play
|
||||
HealthDisplay = CreateHealthDisplay(),
|
||||
Progress = CreateProgress(),
|
||||
ModDisplay = CreateModsContainer(),
|
||||
HitErrorDisplay = CreateHitErrorDisplayOverlay(),
|
||||
}
|
||||
},
|
||||
PlayerSettingsOverlay = CreatePlayerSettingsOverlay(),
|
||||
@ -256,6 +258,8 @@ namespace osu.Game.Screens.Play
|
||||
Margin = new MarginPadding { Top = 20, Right = 10 },
|
||||
};
|
||||
|
||||
protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.FirstAvailableHitWindows);
|
||||
|
||||
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
|
||||
|
||||
protected virtual void BindProcessor(ScoreProcessor processor)
|
||||
|
@ -60,12 +60,16 @@ namespace osu.Game.Screens.Play
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
private RulesetInfo ruleset;
|
||||
private RulesetInfo rulesetInfo;
|
||||
|
||||
private Ruleset ruleset;
|
||||
|
||||
private IAPIProvider api;
|
||||
|
||||
private SampleChannel sampleRestart;
|
||||
|
||||
private BreakOverlay breakOverlay;
|
||||
|
||||
protected ScoreProcessor ScoreProcessor { get; private set; }
|
||||
protected DrawableRuleset DrawableRuleset { get; private set; }
|
||||
|
||||
@ -119,22 +123,54 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime);
|
||||
|
||||
GameplayClockContainer.Children = new[]
|
||||
addUnderlayComponents(GameplayClockContainer);
|
||||
addGameplayComponents(GameplayClockContainer, working);
|
||||
addOverlayComponents(GameplayClockContainer, working);
|
||||
|
||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
|
||||
|
||||
// bind clock into components that require it
|
||||
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
|
||||
|
||||
// Bind ScoreProcessor to ourselves
|
||||
ScoreProcessor.AllJudged += onCompletion;
|
||||
ScoreProcessor.Failed += onFail;
|
||||
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||
mod.ApplyToScoreProcessor(ScoreProcessor);
|
||||
}
|
||||
|
||||
private void addUnderlayComponents(Container target)
|
||||
{
|
||||
target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
private void addGameplayComponents(Container target, WorkingBeatmap working)
|
||||
{
|
||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin);
|
||||
|
||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||
// full access to all skin sources.
|
||||
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
target.Add(new ScalingContainer(ScalingMode.Gameplay)
|
||||
.WithChild(beatmapSkinProvider
|
||||
.WithChild(target = rulesetSkinProvider)));
|
||||
|
||||
target.AddRange(new Drawable[]
|
||||
{
|
||||
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both },
|
||||
new ScalingContainer(ScalingMode.Gameplay)
|
||||
{
|
||||
Child = new LocalSkinOverrideContainer(working.Skin)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
DrawableRuleset,
|
||||
new ComboEffects(ScoreProcessor)
|
||||
}
|
||||
}
|
||||
},
|
||||
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
||||
DrawableRuleset,
|
||||
new ComboEffects(ScoreProcessor)
|
||||
});
|
||||
}
|
||||
|
||||
private void addOverlayComponents(Container target, WorkingBeatmap working)
|
||||
{
|
||||
target.AddRange(new[]
|
||||
{
|
||||
breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -142,6 +178,7 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
// display the cursor above some HUD elements.
|
||||
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
|
||||
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
|
||||
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value)
|
||||
{
|
||||
HoldToQuit =
|
||||
@ -192,19 +229,7 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
},
|
||||
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
|
||||
};
|
||||
|
||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
|
||||
|
||||
// bind clock into components that require it
|
||||
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
|
||||
|
||||
// Bind ScoreProcessor to ourselves
|
||||
ScoreProcessor.AllJudged += onCompletion;
|
||||
ScoreProcessor.Failed += onFail;
|
||||
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||
mod.ApplyToScoreProcessor(ScoreProcessor);
|
||||
});
|
||||
}
|
||||
|
||||
private WorkingBeatmap loadBeatmap()
|
||||
@ -220,20 +245,20 @@ namespace osu.Game.Screens.Play
|
||||
if (beatmap == null)
|
||||
throw new InvalidOperationException("Beatmap was not loaded");
|
||||
|
||||
ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
rulesetInfo = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
|
||||
ruleset = rulesetInfo.CreateInstance();
|
||||
|
||||
try
|
||||
{
|
||||
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value);
|
||||
DrawableRuleset = ruleset.CreateDrawableRulesetWith(working, Mods.Value);
|
||||
}
|
||||
catch (BeatmapInvalidForRulesetException)
|
||||
{
|
||||
// we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset
|
||||
// let's try again forcing the beatmap's ruleset.
|
||||
ruleset = beatmap.BeatmapInfo.Ruleset;
|
||||
rulesetInstance = ruleset.CreateInstance();
|
||||
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value);
|
||||
rulesetInfo = beatmap.BeatmapInfo.Ruleset;
|
||||
ruleset = rulesetInfo.CreateInstance();
|
||||
DrawableRuleset = ruleset.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value);
|
||||
}
|
||||
|
||||
if (!DrawableRuleset.Objects.Any())
|
||||
@ -255,7 +280,7 @@ namespace osu.Game.Screens.Play
|
||||
private void performImmediateExit()
|
||||
{
|
||||
// if a restart has been requested, cancel any pending completion (user has shown intent to restart).
|
||||
onCompletionEvent = null;
|
||||
completionProgressDelegate?.Cancel();
|
||||
|
||||
ValidForResume = false;
|
||||
|
||||
@ -275,20 +300,16 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
sampleRestart?.Play();
|
||||
|
||||
// if a restart has been requested, cancel any pending completion (user has shown intent to restart).
|
||||
onCompletionEvent = null;
|
||||
|
||||
ValidForResume = false;
|
||||
RestartRequested?.Invoke();
|
||||
this.Exit();
|
||||
performImmediateExit();
|
||||
}
|
||||
|
||||
private ScheduledDelegate onCompletionEvent;
|
||||
private ScheduledDelegate completionProgressDelegate;
|
||||
|
||||
private void onCompletion()
|
||||
{
|
||||
// Only show the completion screen if the player hasn't failed
|
||||
if (ScoreProcessor.HasFailed || onCompletionEvent != null)
|
||||
if (ScoreProcessor.HasFailed || completionProgressDelegate != null)
|
||||
return;
|
||||
|
||||
ValidForResume = false;
|
||||
@ -297,7 +318,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
using (BeginDelayedSequence(1000))
|
||||
{
|
||||
onCompletionEvent = Schedule(delegate
|
||||
completionProgressDelegate = Schedule(delegate
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
@ -306,8 +327,6 @@ namespace osu.Game.Screens.Play
|
||||
scoreManager.Import(score).Wait();
|
||||
|
||||
this.Push(CreateResults(score));
|
||||
|
||||
onCompletionEvent = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -317,7 +336,7 @@ namespace osu.Game.Screens.Play
|
||||
var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo
|
||||
{
|
||||
Beatmap = Beatmap.Value.BeatmapInfo,
|
||||
Ruleset = ruleset,
|
||||
Ruleset = rulesetInfo,
|
||||
Mods = Mods.Value.ToArray(),
|
||||
User = api.LocalUser.Value,
|
||||
};
|
||||
@ -417,8 +436,7 @@ namespace osu.Game.Screens.Play
|
||||
PauseOverlay.Hide();
|
||||
|
||||
// breaks and time-based conditions may allow instant resume.
|
||||
double time = GameplayClockContainer.GameplayClock.CurrentTime;
|
||||
if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
|
||||
if (breakOverlay.IsBreakTime.Value || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
|
||||
completeResume();
|
||||
else
|
||||
DrawableRuleset.RequestResume(completeResume);
|
||||
@ -471,10 +489,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
if (onCompletionEvent != null)
|
||||
if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed)
|
||||
{
|
||||
// Proceed to result screen if beatmap already finished playing
|
||||
onCompletionEvent.RunTask();
|
||||
// proceed to result screen if beatmap already finished playing
|
||||
completionProgressDelegate.RunTask();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play
|
||||
return base.Invalidate(invalidation, source, shallPropagate);
|
||||
}
|
||||
|
||||
private Cached layout = new Cached();
|
||||
private readonly Cached layout = new Cached();
|
||||
private ScheduledDelegate scheduledCreate;
|
||||
|
||||
protected override void Update()
|
||||
@ -103,6 +103,7 @@ namespace osu.Game.Screens.Play
|
||||
var newColumns = new BufferedContainer<Column>
|
||||
{
|
||||
CacheDrawnFrameBuffer = true,
|
||||
RedrawOnScale = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user