Merge branch 'master' into catch-hide-combo-workaround

This commit is contained in:
Salman Ahmed
2021-05-18 17:47:20 +03:00
62 changed files with 874 additions and 487 deletions

View File

@ -35,8 +35,12 @@ namespace osu.Game.Screens.Play
/// </summary>
public float TopScoringElementsHeight { get; private set; }
/// <summary>
/// The total height of all the bottom of screen scoring elements.
/// </summary>
public float BottomScoringElementsHeight { get; private set; }
public readonly KeyCounterDisplay KeyCounter;
public readonly SongProgress Progress;
public readonly ModDisplay ModDisplay;
public readonly HoldForMenuButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
@ -59,8 +63,6 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce;
public Action<double> RequestSeek;
private readonly FillFlowContainer bottomRightElements;
private readonly FillFlowContainer topRightElements;
@ -85,45 +87,22 @@ namespace osu.Game.Screens.Play
visibilityContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new GridContainer
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Content = new[]
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
// still need to be migrated; a bit more involved.
new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows),
}
},
}
},
},
new Drawable[]
{
Progress = CreateProgress(),
// still need to be migrated; a bit more involved.
new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows),
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
}
},
}
},
topRightElements = new FillFlowContainer
{
@ -164,10 +143,6 @@ namespace osu.Game.Screens.Play
if (drawableRuleset != null)
{
BindDrawableRuleset(drawableRuleset);
Progress.Objects = drawableRuleset.Objects;
Progress.RequestSeek = time => RequestSeek(time);
Progress.ReferenceClock = drawableRuleset.FrameStableClock;
}
ModDisplay.Current.Value = mods;
@ -206,26 +181,43 @@ namespace osu.Game.Screens.Play
{
base.Update();
Vector2 lowestScreenSpace = Vector2.Zero;
Vector2? lowestTopScreenSpace = null;
Vector2? highestBottomScreenSpace = null;
// LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
foreach (var element in mainComponents.Components.Cast<Drawable>())
{
// for now align top-right components with the bottom-edge of the lowest top-anchored hud element.
if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X))
if (!element.RelativeSizeAxes.HasFlagFast(Axes.X))
continue;
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
if (element is LegacyHealthDisplay)
continue;
if (element.Anchor.HasFlagFast(Anchor.TopRight))
{
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
if (element is LegacyHealthDisplay)
continue;
var bottomRight = element.ScreenSpaceDrawQuad.BottomRight;
if (bottomRight.Y > lowestScreenSpace.Y)
lowestScreenSpace = bottomRight;
var bottomRight = element.ScreenSpaceDrawQuad.BottomRight;
if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y)
lowestTopScreenSpace = bottomRight;
}
else if (element.Anchor.HasFlagFast(Anchor.y2))
{
var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
highestBottomScreenSpace = topLeft;
}
}
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y;
bottomRightElements.Y = -Progress.Height;
if (lowestTopScreenSpace.HasValue)
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y;
else
topRightElements.Y = 0;
if (highestBottomScreenSpace.HasValue)
bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y);
else
bottomRightElements.Y = 0;
}
private void updateVisibility()
@ -281,8 +273,6 @@ namespace osu.Game.Screens.Play
(drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter);
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
Progress.BindDrawableRuleset(drawableRuleset);
}
protected FailingLayer CreateFailingLayer() => new FailingLayer
@ -296,13 +286,6 @@ namespace osu.Game.Screens.Play
Origin = Anchor.BottomRight,
};
protected SongProgress CreateProgress() => new SongProgress
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
};
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
{
Anchor = Anchor.BottomRight,

View File

@ -154,6 +154,9 @@ namespace osu.Game.Screens.Play
{
base.LoadComplete();
if (!LoadedBeatmapSuccessfully)
return;
// replays should never be recorded or played back when autoplay is enabled
if (!Mods.Value.Any(m => m is ModAutoplay))
PrepareReplay();
@ -198,6 +201,7 @@ namespace osu.Game.Screens.Play
LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
dependencies.CacheAs(DrawableRuleset);
ScoreProcessor = ruleset.CreateScoreProcessor();
ScoreProcessor.ApplyBeatmap(playableBeatmap);
@ -357,11 +361,6 @@ namespace osu.Game.Screens.Play
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
IsCounting = false
},
RequestSeek = time =>
{
GameplayClockContainer.Seek(time);
GameplayClockContainer.Start();
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
@ -566,6 +565,12 @@ namespace osu.Game.Screens.Play
updateSampleDisabledState();
}
/// <summary>
/// Seek to a specific time in gameplay.
/// </summary>
/// <param name="time">The destination time to seek to.</param>
public void Seek(double time) => GameplayClockContainer.Seek(time);
/// <summary>
/// Restart gameplay via a parent <see cref="PlayerLoader"/>.
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
@ -924,11 +929,11 @@ namespace osu.Game.Screens.Play
/// </summary>
/// <param name="score">The <see cref="Score"/> to import.</param>
/// <returns>The imported score.</returns>
protected virtual Task ImportScore(Score score)
protected virtual async Task ImportScore(Score score)
{
// Replays are already populated and present in the game's database, so should not be re-imported.
if (DrawableRuleset.ReplayScore != null)
return Task.CompletedTask;
return;
LegacyByteArrayReader replayReader;
@ -938,7 +943,18 @@ namespace osu.Game.Screens.Play
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
}
return scoreManager.Import(score.ScoreInfo, replayReader);
// For the time being, online ID responses are not really useful for anything.
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
//
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
// conflicts across various systems (ie. solo and multiplayer).
long? onlineScoreId = score.ScoreInfo.OnlineScoreID;
score.ScoreInfo.OnlineScoreID = null;
await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false);
// ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen).
score.ScoreInfo.OnlineScoreID = onlineScoreId;
}
/// <summary>

View File

@ -14,10 +14,11 @@ using osu.Framework.Timing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play
{
public class SongProgress : OverlayContainer
public class SongProgress : OverlayContainer, ISkinnableDrawable
{
private const int info_height = 20;
private const int bottom_bar_height = 5;
@ -39,9 +40,6 @@ namespace osu.Game.Screens.Play
public readonly Bindable<bool> ShowGraph = new Bindable<bool>();
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
private double lastHitTime => objects.Last().GetEndTime() + 1;
public override bool HandleNonPositionalInput => AllowSeeking.Value;
public override bool HandlePositionalInput => AllowSeeking.Value;
@ -49,6 +47,9 @@ namespace osu.Game.Screens.Play
private double firstHitTime => objects.First().StartTime;
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
private double lastHitTime => objects.Last().GetEndTime() + 1;
private IEnumerable<HitObject> objects;
public IEnumerable<HitObject> Objects
@ -65,51 +66,58 @@ namespace osu.Game.Screens.Play
}
}
public IClock ReferenceClock;
[Resolved(canBeNull: true)]
private Player player { get; set; }
private IClock gameplayClock;
[Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
private IClock referenceClock;
public SongProgress()
{
RelativeSizeAxes = Axes.X;
Anchor = Anchor.BottomRight;
Origin = Anchor.BottomRight;
Children = new Drawable[]
{
new SongProgressDisplay
info = new SongProgressInfo
{
Children = new Drawable[]
{
info = new SongProgressInfo
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = info_height,
},
graph = new SongProgressGraph
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height },
},
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = time => RequestSeek?.Invoke(time),
},
}
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = info_height,
},
graph = new SongProgressGraph
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height },
},
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config)
private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset)
{
base.LoadComplete();
if (clock != null)
gameplayClock = clock;
if (drawableRuleset != null)
{
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
referenceClock = drawableRuleset.FrameStableClock;
Objects = drawableRuleset.Objects;
}
config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph);
@ -124,11 +132,6 @@ namespace osu.Game.Screens.Play
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
}
public void BindDrawableRuleset(DrawableRuleset drawableRuleset)
{
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
}
protected override void PopIn()
{
this.FadeIn(500, Easing.OutQuint);
@ -147,7 +150,7 @@ namespace osu.Game.Screens.Play
return;
double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current;
double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime;
double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime;
double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime));
@ -179,19 +182,5 @@ namespace osu.Game.Screens.Play
float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0);
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
}
public class SongProgressDisplay : Container
{
public SongProgressDisplay()
{
// TODO: move actual implementation into this.
// exists for skin customisation purposes (interface should be added to this container).
Masking = true;
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
}
}
}
}

View File

@ -116,12 +116,7 @@ namespace osu.Game.Screens.Play
request.Success += s =>
{
// For the time being, online ID responses are not really useful for anything.
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
//
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
// conflicts across various systems (ie. solo and multiplayer).
// score.ScoreInfo.OnlineScoreID = s.ID;
score.ScoreInfo.OnlineScoreID = s.ID;
tcs.SetResult(true);
};