1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 01:22:59 +08:00

Merge branch 'master' into queue-list-counter

This commit is contained in:
Salman Ahmed 2022-03-21 05:01:09 +03:00 committed by GitHub
commit 5c1e11c153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 448 additions and 122 deletions

72
.github/ISSUE_TEMPLATE/bug-issue.yml vendored Normal file
View File

@ -0,0 +1,72 @@
name: Bug report
description: Report a very clearly broken issue.
body:
- type: markdown
attributes:
value: |
# osu! bug report
Important to note that your issue may have already been reported before. Please check:
- Pinned issues, at the top of https://github.com/ppy/osu/issues.
- Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0).
- And most importantly, search for your issue. If you find that it already exists, respond with a reaction or add any further information that may be helpful.
- type: dropdown
attributes:
label: Type
options:
- Crash to desktop
- Game behaviour
- Performance
- Cosmetic
- Other
validations:
required: true
- type: textarea
attributes:
label: Bug description
description: How did you find the bug? Any additional details that might help?
validations:
required: true
- type: textarea
attributes:
label: Screenshots or videos
description: Add screenshots or videos that show the bug here.
placeholder: Drag and drop the screenshots/videos into this box.
validations:
required: false
- type: input
attributes:
label: Version
description: The version you encountered this bug on. This is shown at the bottom of the main menu and also at the end of the settings screen.
validations:
required: true
- type: markdown
attributes:
value: |
## Logs
Attaching log files is required for every reported bug. See instructions below on how to find them.
If the game has not yet been closed since you found the bug:
1. Head on to game settings and click on "Open osu! folder"
2. Then open the `logs` folder located there
**Logs are reset when you reopen the game.** If the game crashed or has been closed since you found the bug, retrieve the logs using the file explorer instead.
The default places to find the logs are as follows:
- `%AppData%/osu/logs` *on Windows*
- `~/.local/share/osu/logs` *on Linux & macOS*
- `Android/data/sh.ppy.osulazer/files/logs` *on Android*
- *On iOS*, they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
If you have selected a custom location for the game files, you can find the `logs` folder there.
After locating the `logs` folder, select all log files inside and drag them into the "Logs" box below.
- type: textarea
attributes:
label: Logs
placeholder: Drag and drop the log files into this box.
validations:
required: true

View File

@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Fun;
public override string Description => "No need to chase the circle the circle chases you!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) };
private IFrameStableClock gameplayClock;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
{
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModAimAssist) }).ToArray();
/// <summary>
/// How early before a hitobject's start time to trigger a hit.

View File

@ -133,6 +133,7 @@ namespace osu.Game.Tests.Resources
StarRating = diff,
Length = length,
BPM = bpm,
MaxCombo = 1000,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo,
Metadata = metadata,

View File

@ -70,6 +70,56 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
[Test]
public void TestSeekToGameplayStartFramesArriveAfterPlayerLoad()
{
const double gameplay_start = 10000;
loadSpectatingScreen();
start();
waitForPlayer();
sendFrames(startTime: gameplay_start);
AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start);
}
/// <summary>
/// Tests the same as <see cref="TestSeekToGameplayStartFramesArriveAfterPlayerLoad"/> but with the frames arriving just as <see cref="Player"/> is transitioning into existence.
/// </summary>
[Test]
public void TestSeekToGameplayStartFramesArriveAsPlayerLoaded()
{
const double gameplay_start = 10000;
loadSpectatingScreen();
start();
AddUntilStep("wait for player loader", () => (Stack.CurrentScreen as PlayerLoader)?.IsLoaded == true);
AddUntilStep("queue send frames on player load", () =>
{
var loadingPlayer = (Stack.CurrentScreen as PlayerLoader)?.CurrentPlayer;
if (loadingPlayer == null)
return false;
loadingPlayer.OnLoadComplete += _ =>
{
spectatorClient.SendFramesFromUser(streamingUser.Id, 10, gameplay_start);
};
return true;
});
waitForPlayer();
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start);
}
[Test]
public void TestFrameStarvationAndResume()
{
@ -319,9 +369,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private void checkPaused(bool state) =>
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
private void sendFrames(int count = 10)
private void sendFrames(int count = 10, double startTime = 0)
{
AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count));
AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count, startTime));
}
private void loadSpectatingScreen()

View File

@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user is idle (match not started)", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle);
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddUntilStep("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
}
[TestCase(true)]

View File

@ -122,7 +122,19 @@ namespace osu.Game.Rulesets.Scoring
public static class HitResultExtensions
{
/// <summary>
/// Whether a <see cref="HitResult"/> increases/decreases the combo, and affects the combo portion of the score.
/// Whether a <see cref="HitResult"/> increases the combo.
/// </summary>
public static bool IncreasesCombo(this HitResult result)
=> AffectsCombo(result) && IsHit(result);
/// <summary>
/// Whether a <see cref="HitResult"/> breaks the combo and resets it back to zero.
/// </summary>
public static bool BreaksCombo(this HitResult result)
=> AffectsCombo(result) && !IsHit(result);
/// <summary>
/// Whether a <see cref="HitResult"/> increases/breaks the combo, and affects the combo portion of the score.
/// </summary>
public static bool AffectsCombo(this HitResult result)
{

View File

@ -166,20 +166,10 @@ namespace osu.Game.Rulesets.Scoring
if (!result.Type.IsScorable())
return;
if (result.Type.AffectsCombo())
{
switch (result.Type)
{
case HitResult.Miss:
case HitResult.LargeTickMiss:
Combo.Value = 0;
break;
default:
Combo.Value++;
break;
}
}
if (result.Type.IncreasesCombo())
Combo.Value++;
else if (result.Type.BreaksCombo())
Combo.Value = 0;
double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0;

View File

@ -4,12 +4,16 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
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.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osuTK;
@ -19,11 +23,27 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
public class BarHitErrorMeter : HitErrorMeter
{
private const int judgement_line_width = 14;
private const int judgement_line_height = 4;
[SettingSource("Judgement line thickness", "How thick the individual lines should be.")]
public BindableNumber<float> JudgementLineThickness { get; } = new BindableNumber<float>(4)
{
MinValue = 1,
MaxValue = 8,
Precision = 0.1f,
};
[SettingSource("Show moving average arrow", "Whether an arrow should move beneath the bar showing the average error.")]
public Bindable<bool> ShowMovingAverage { get; } = new BindableBool(true);
[SettingSource("Centre marker style", "How to signify the centre of the display")]
public Bindable<CentreMarkerStyles> CentreMarkerStyle { get; } = new Bindable<CentreMarkerStyles>(CentreMarkerStyles.Circle);
[SettingSource("Label style", "How to show early/late extremities")]
public Bindable<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(LabelStyles.Icons);
private SpriteIcon arrow;
private SpriteIcon iconEarly;
private SpriteIcon iconLate;
private Drawable labelEarly;
private Drawable labelLate;
private Container colourBarsEarly;
private Container colourBarsLate;
@ -32,6 +52,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
private double maxHitWindow;
private double floatingAverage;
private Container colourBars;
private Container arrowContainer;
private (HitResult result, double length)[] hitWindows;
private const int max_concurrent_judgements = 50;
private Drawable[] centreMarkerDrawables;
private const int centre_marker_size = 8;
public BarHitErrorMeter()
{
AutoSizeAxes = Axes.Both;
@ -40,13 +72,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[BackgroundDependencyLoader]
private void load()
{
const int centre_marker_size = 8;
const int bar_height = 200;
const int bar_width = 2;
const float chevron_size = 8;
const float icon_size = 14;
var hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
InternalChild = new Container
{
@ -65,22 +95,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
iconEarly = new SpriteIcon
{
Y = -10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
},
iconLate = new SpriteIcon
{
Y = 10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
},
colourBarsEarly = new Container
{
Anchor = Anchor.Centre,
@ -98,14 +112,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
},
new Circle
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(centre_marker_size),
},
judgementsContainer = new Container
{
Name = "judgements",
@ -114,24 +120,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y,
Width = judgement_line_width,
},
new Circle
{
Name = "middle marker in front",
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(centre_marker_size),
Scale = new Vector2(0.5f),
},
}
},
new Container
arrowContainer = new Container
{
Name = "average chevron",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Width = chevron_size,
X = chevron_size,
RelativeSizeAxes = Axes.Y,
Alpha = 0,
Scale = new Vector2(0, 1),
Child = arrow = new SpriteIcon
{
Anchor = Anchor.TopCentre,
@ -155,8 +155,180 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
colourBars.Height = 0;
colourBars.ResizeHeightTo(1, 800, Easing.OutQuint);
arrow.Alpha = 0;
arrow.Delay(200).FadeInFromZero(600);
CentreMarkerStyle.BindValueChanged(style => recreateCentreMarker(style.NewValue), true);
LabelStyle.BindValueChanged(style => recreateLabels(style.NewValue), true);
// delay the appearance animations for only the initial appearance.
using (arrowContainer.BeginDelayedSequence(450))
{
ShowMovingAverage.BindValueChanged(visible =>
{
arrowContainer.FadeTo(visible.NewValue ? 1 : 0, 250, Easing.OutQuint);
arrowContainer.ScaleTo(visible.NewValue ? new Vector2(1) : new Vector2(0, 1), 250, Easing.OutQuint);
}, true);
}
}
private void recreateCentreMarker(CentreMarkerStyles style)
{
if (centreMarkerDrawables != null)
{
foreach (var d in centreMarkerDrawables)
{
d.ScaleTo(0, 500, Easing.OutQuint)
.FadeOut(500, Easing.OutQuint);
d.Expire();
}
centreMarkerDrawables = null;
}
switch (style)
{
case CentreMarkerStyles.None:
break;
case CentreMarkerStyles.Circle:
centreMarkerDrawables = new Drawable[]
{
new Circle
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MaxValue,
Size = new Vector2(centre_marker_size),
},
new Circle
{
Name = "middle marker in front",
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MinValue,
Size = new Vector2(centre_marker_size / 2f),
},
};
break;
case CentreMarkerStyles.Line:
const float border_size = 1.5f;
centreMarkerDrawables = new Drawable[]
{
new Box
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MaxValue,
Size = new Vector2(judgement_line_width, centre_marker_size / 3f),
},
new Box
{
Name = "middle marker in front",
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MinValue,
Size = new Vector2(judgement_line_width - border_size, centre_marker_size / 3f - border_size),
},
};
break;
default:
throw new ArgumentOutOfRangeException(nameof(style), style, null);
}
if (centreMarkerDrawables != null)
{
foreach (var d in centreMarkerDrawables)
{
colourBars.Add(d);
d.FadeInFromZero(500, Easing.OutQuint)
.ScaleTo(0).ScaleTo(1, 1000, Easing.OutElasticHalf);
}
}
}
private void recreateLabels(LabelStyles style)
{
const float icon_size = 14;
labelEarly?.Expire();
labelEarly = null;
labelLate?.Expire();
labelLate = null;
switch (style)
{
case LabelStyles.None:
break;
case LabelStyles.Icons:
labelEarly = new SpriteIcon
{
Y = -10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
};
labelLate = new SpriteIcon
{
Y = 10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
};
break;
case LabelStyles.Text:
labelEarly = new OsuSpriteText
{
Y = -10,
Text = "Early",
Font = OsuFont.Default.With(size: 10),
Height = 12,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
};
labelLate = new OsuSpriteText
{
Y = 10,
Text = "Late",
Font = OsuFont.Default.With(size: 10),
Height = 12,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
};
break;
default:
throw new ArgumentOutOfRangeException(nameof(style), style, null);
}
if (labelEarly != null)
{
colourBars.Add(labelEarly);
labelEarly.FadeInFromZero(500);
}
if (labelLate != null)
{
colourBars.Add(labelLate);
labelLate.FadeInFromZero(500);
}
}
protected override void Update()
@ -164,8 +336,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
base.Update();
// undo any layout rotation to display icons in the correct orientation
iconEarly.Rotation = -Rotation;
iconLate.Rotation = -Rotation;
if (labelEarly != null) labelEarly.Rotation = -Rotation;
if (labelLate != null) labelLate.Rotation = -Rotation;
}
private void createColourBars((HitResult result, double length)[] windows)
@ -224,11 +396,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
}
}
private double floatingAverage;
private Container colourBars;
private const int max_concurrent_judgements = 50;
protected override void OnNewJudgement(JudgementResult judgement)
{
const int arrow_move_duration = 800;
@ -255,6 +422,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
judgementsContainer.Add(new JudgementLine
{
JudgementLineThickness = { BindTarget = JudgementLineThickness },
Y = getRelativeJudgementPosition(judgement.TimeOffset),
Colour = GetColourForHitResult(judgement.Type),
});
@ -268,11 +436,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
internal class JudgementLine : CompositeDrawable
{
public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat();
public JudgementLine()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
Height = judgement_line_height;
Blending = BlendingParameters.Additive;
@ -295,6 +464,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Alpha = 0;
Width = 0;
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
this
.FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
.ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint)
@ -306,5 +477,19 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
}
public override void Clear() => judgementsContainer.Clear();
public enum CentreMarkerStyles
{
None,
Circle,
Line
}
public enum LabelStyles
{
None,
Icons,
Text
}
}
}

View File

@ -84,7 +84,7 @@ namespace osu.Game.Screens.Play.HUD
protected override bool OnMouseMove(MouseMoveEvent e)
{
positionalAdjust = Vector2.Distance(e.ScreenSpaceMousePosition, button.ScreenSpaceDrawQuad.Centre) / 200;
positionalAdjust = Vector2.Distance(e.MousePosition, button.ToSpaceOfOtherDrawable(button.DrawRectangle.Centre, Parent)) / 100;
return base.OnMouseMove(e);
}

View File

@ -615,16 +615,22 @@ namespace osu.Game.Screens.Play
/// <param name="time">The destination time to seek to.</param>
internal void NonFrameStableSeek(double time)
{
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
// TODO: This schedule should not be required and is a temporary hotfix.
// See https://github.com/ppy/osu/issues/17267 for the issue.
// See https://github.com/ppy/osu/pull/17302 for a better fix which needs some more time.
ScheduleAfterChildren(() =>
{
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
Seek(time);
Seek(time);
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
});
}
/// <summary>

View File

@ -1,10 +1,11 @@
// 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.
#nullable enable
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using JetBrains.Annotations;
using ManagedBass.Fx;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -48,31 +49,31 @@ namespace osu.Game.Screens.Play
public override bool HandlePositionalInput => true;
// We show the previous screen status
protected override UserActivity InitialActivity => null;
protected override UserActivity? InitialActivity => null;
protected override bool PlayResumeSound => false;
protected BeatmapMetadataDisplay MetadataInfo { get; private set; }
protected BeatmapMetadataDisplay MetadataInfo { get; private set; } = null!;
/// <summary>
/// A fill flow containing the player settings groups, exposed for the ability to hide it from inheritors of the player loader.
/// </summary>
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; }
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; } = null!;
protected VisualSettings VisualSettings { get; private set; }
protected VisualSettings VisualSettings { get; private set; } = null!;
protected AudioSettings AudioSettings { get; private set; }
protected AudioSettings AudioSettings { get; private set; } = null!;
protected Task LoadTask { get; private set; }
protected Task? LoadTask { get; private set; }
protected Task DisposalTask { get; private set; }
protected Task? DisposalTask { get; private set; }
private bool backgroundBrightnessReduction;
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
private AudioFilter lowPassFilter;
private AudioFilter highPassFilter;
private AudioFilter lowPassFilter = null!;
private AudioFilter highPassFilter = null!;
protected bool BackgroundBrightnessReduction
{
@ -90,47 +91,49 @@ namespace osu.Game.Screens.Play
private bool readyForPush =>
!playerConsumed
// don't push unless the player is completely loaded
&& player?.LoadState == LoadState.Ready
&& CurrentPlayer?.LoadState == LoadState.Ready
// don't push if the user is hovering one of the panes, unless they are idle.
&& (IsHovered || idleTracker.IsIdle.Value)
// don't push if the user is dragging a slider or otherwise.
&& inputManager?.DraggedDrawable == null
&& inputManager.DraggedDrawable == null
// don't push if a focused overlay is visible, like settings.
&& inputManager?.FocusedDrawable == null;
&& inputManager.FocusedDrawable == null;
private readonly Func<Player> createPlayer;
private Player player;
/// <summary>
/// The <see cref="Player"/> instance being loaded by this screen.
/// </summary>
public Player? CurrentPlayer { get; private set; }
/// <summary>
/// Whether the curent player instance has been consumed via <see cref="consumePlayer"/>.
/// Whether the current player instance has been consumed via <see cref="consumePlayer"/>.
/// </summary>
private bool playerConsumed;
private LogoTrackingContainer content;
private LogoTrackingContainer content = null!;
private bool hideOverlays;
private InputManager inputManager;
private InputManager inputManager = null!;
private IdleTracker idleTracker;
private IdleTracker idleTracker = null!;
private ScheduledDelegate scheduledPushPlayer;
private ScheduledDelegate? scheduledPushPlayer;
[CanBeNull]
private EpilepsyWarning epilepsyWarning;
private EpilepsyWarning? epilepsyWarning;
[Resolved(CanBeNull = true)]
private NotificationOverlay notificationOverlay { get; set; }
private NotificationOverlay? notificationOverlay { get; set; }
[Resolved(CanBeNull = true)]
private VolumeOverlay volumeOverlay { get; set; }
private VolumeOverlay? volumeOverlay { get; set; }
[Resolved]
private AudioManager audioManager { get; set; }
private AudioManager audioManager { get; set; } = null!;
[Resolved(CanBeNull = true)]
private BatteryInfo batteryInfo { get; set; }
private BatteryInfo? batteryInfo { get; set; }
public PlayerLoader(Func<Player> createPlayer)
{
@ -237,12 +240,14 @@ namespace osu.Game.Screens.Play
{
base.OnResuming(last);
var lastScore = player.Score;
Debug.Assert(CurrentPlayer != null);
var lastScore = CurrentPlayer.Score;
AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo;
// prepare for a retry.
player = null;
CurrentPlayer = null;
playerConsumed = false;
cancelLoad();
@ -344,9 +349,10 @@ namespace osu.Game.Screens.Play
private Player consumePlayer()
{
Debug.Assert(!playerConsumed);
Debug.Assert(CurrentPlayer != null);
playerConsumed = true;
return player;
return CurrentPlayer;
}
private void prepareNewPlayer()
@ -354,11 +360,11 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen())
return;
player = createPlayer();
player.RestartCount = restartCount++;
player.RestartRequested = restartRequested;
CurrentPlayer = createPlayer();
CurrentPlayer.RestartCount = restartCount++;
CurrentPlayer.RestartRequested = restartRequested;
LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false);
LoadTask = LoadComponentAsync(CurrentPlayer, _ => MetadataInfo.Loading = false);
}
private void restartRequested()
@ -472,7 +478,7 @@ namespace osu.Game.Screens.Play
if (isDisposing)
{
// if the player never got pushed, we should explicitly dispose it.
DisposalTask = LoadTask?.ContinueWith(_ => player?.Dispose());
DisposalTask = LoadTask?.ContinueWith(_ => CurrentPlayer?.Dispose());
}
}
@ -480,7 +486,7 @@ namespace osu.Game.Screens.Play
#region Mute warning
private Bindable<bool> muteWarningShownOnce;
private Bindable<bool> muteWarningShownOnce = null!;
private int restartCount;
@ -535,7 +541,7 @@ namespace osu.Game.Screens.Play
#region Low battery warning
private Bindable<bool> batteryWarningShownOnce;
private Bindable<bool> batteryWarningShownOnce = null!;
private void showBatteryWarningIfNeeded()
{

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Ranking.Expanded
var topStatistics = new List<StatisticDisplay>
{
new AccuracyStatistic(score.Accuracy),
new ComboStatistic(score.MaxCombo, !score.Statistics.TryGetValue(HitResult.Miss, out int missCount) || missCount == 0),
new ComboStatistic(score.MaxCombo, beatmap.MaxCombo, score.Statistics.All(stat => !stat.Key.BreaksCombo() || stat.Value == 0)),
new PerformanceStatistic(score),
};

View File

@ -25,9 +25,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
/// Creates a new <see cref="ComboStatistic"/>.
/// </summary>
/// <param name="combo">The combo to be displayed.</param>
/// <param name="maxCombo">The maximum value of <paramref name="combo"/>.</param>
/// <param name="isPerfect">Whether this is a perfect combo.</param>
public ComboStatistic(int combo, bool isPerfect)
: base("combo", combo)
public ComboStatistic(int combo, int? maxCombo, bool isPerfect)
: base("combo", combo, maxCombo)
{
this.isPerfect = isPerfect;
}

View File

@ -51,7 +51,7 @@ namespace osu.Game.Skinning.Editor
fill.Clear();
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes()
.Where(t => !t.IsInterface)
.Where(t => !t.IsInterface && !t.IsAbstract)
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
.OrderBy(t => t.Name)
.ToArray();

View File

@ -88,7 +88,8 @@ namespace osu.Game.Tests.Visual.Spectator
/// </summary>
/// <param name="userId">The user to send frames for.</param>
/// <param name="count">The total number of frames to send.</param>
public void SendFramesFromUser(int userId, int count)
/// <param name="startTime">The time to start gameplay frames from.</param>
public void SendFramesFromUser(int userId, int count, double startTime = 0)
{
var frames = new List<LegacyReplayFrame>();
@ -102,7 +103,7 @@ namespace osu.Game.Tests.Visual.Spectator
flush();
var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1;
frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
frames.Add(new LegacyReplayFrame(currentFrameIndex * 100 + startTime, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
}
flush();

View File

@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
@ -82,7 +83,8 @@ namespace osu.Game.Updater
break;
case RuntimeInfo.Platform.macOS:
bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip", StringComparison.Ordinal));
string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "Apple.Silicon" : "Intel";
bestAsset = release.Assets?.Find(f => f.Name.EndsWith($".app.{arch}.zip", StringComparison.Ordinal));
break;
case RuntimeInfo.Platform.Linux: