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:
commit
5c1e11c153
72
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
72
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal 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
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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),
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user