mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 10:52:53 +08:00
Merge branch 'master' into argon-health-rework
This commit is contained in:
commit
c406135487
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.110.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.114.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -30,12 +30,19 @@ namespace osu.Desktop
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// NVIDIA profiles are based on the executable name of a process.
|
||||
// Lazer and stable share the same executable name.
|
||||
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
|
||||
NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||
|
||||
// run Squirrel first, as the app may exit after these run
|
||||
/*
|
||||
* WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
|
||||
*
|
||||
* Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
|
||||
* To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
|
||||
* namely by checking loaded assemblies:
|
||||
* https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
|
||||
*
|
||||
* If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
|
||||
* the app will then do completely broken things like:
|
||||
* - not creating system shortcuts (as the logic is if'd out if "running tests")
|
||||
* - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
|
||||
*/
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var windowsVersion = Environment.OSVersion.Version;
|
||||
@ -59,6 +66,11 @@ namespace osu.Desktop
|
||||
setupSquirrel();
|
||||
}
|
||||
|
||||
// NVIDIA profiles are based on the executable name of a process.
|
||||
// Lazer and stable share the same executable name.
|
||||
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
|
||||
NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||
|
||||
// Back up the cwd before DesktopGameHost changes it
|
||||
string cwd = Environment.CurrentDirectory;
|
||||
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
@ -62,13 +63,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
||||
}
|
||||
|
||||
int difficultyPeppyStars = (int)Math.Round(
|
||||
(baseBeatmap.Difficulty.DrainRate
|
||||
+ baseBeatmap.Difficulty.OverallDifficulty
|
||||
+ baseBeatmap.Difficulty.CircleSize
|
||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
||||
|
||||
scoreMultiplier = difficultyPeppyStars;
|
||||
scoreMultiplier = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength);
|
||||
|
||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||
|
||||
|
@ -1,22 +1,23 @@
|
||||
// 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 disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
private readonly ColumnFlow<Column> columnFlow;
|
||||
|
||||
private readonly JudgementContainer<DrawableManiaJudgement> judgements;
|
||||
private readonly DrawablePool<DrawableManiaJudgement> judgementPool;
|
||||
private readonly JudgementPooler<DrawableManiaJudgement> judgementPooler;
|
||||
|
||||
private readonly Drawable barLineContainer;
|
||||
|
||||
@ -48,6 +49,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private readonly int firstColumnIndex;
|
||||
|
||||
private ISkinSource currentSkin = null!;
|
||||
|
||||
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||
{
|
||||
this.firstColumnIndex = firstColumnIndex;
|
||||
@ -65,7 +68,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
judgementPool = new DrawablePool<DrawableManiaJudgement>(2),
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
@ -104,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground), _ => null)
|
||||
new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
@ -137,11 +139,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
AddNested(column);
|
||||
}
|
||||
|
||||
var hitWindows = new ManiaHitWindows();
|
||||
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r))));
|
||||
|
||||
RegisterPool<BarLine, DrawableBarLine>(50, 200);
|
||||
}
|
||||
|
||||
private ISkinSource currentSkin;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
@ -170,7 +174,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (currentSkin != null)
|
||||
if (currentSkin.IsNotNull())
|
||||
currentSkin.SourceChanged -= onSkinChanged;
|
||||
}
|
||||
|
||||
@ -196,13 +200,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
return;
|
||||
|
||||
judgements.Clear(false);
|
||||
judgements.Add(judgementPool.Get(j =>
|
||||
judgements.Add(judgementPooler.Get(result.Type, j =>
|
||||
{
|
||||
j.Apply(result, judgedObject);
|
||||
|
||||
j.Anchor = Anchor.Centre;
|
||||
j.Origin = Anchor.Centre;
|
||||
}));
|
||||
})!);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -1,51 +0,0 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// This test covers autoplay working correctly in the editor on fast streams.
|
||||
/// Might seem like a weird test, but frame stability being toggled can cause autoplay to operation incorrectly.
|
||||
/// This is clearly a bug with the autoplay algorithm, but is worked around at an editor level for now.
|
||||
/// </summary>
|
||||
public partial class TestSceneEditorAutoplayFastStreams : EditorTestScene
|
||||
{
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var testBeatmap = new TestBeatmap(ruleset, false);
|
||||
testBeatmap.HitObjects.AddRange(new[]
|
||||
{
|
||||
new HitCircle { StartTime = 500 },
|
||||
new HitCircle { StartTime = 530 },
|
||||
new HitCircle { StartTime = 560 },
|
||||
new HitCircle { StartTime = 590 },
|
||||
new HitCircle { StartTime = 620 },
|
||||
});
|
||||
|
||||
return testBeatmap;
|
||||
}
|
||||
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestAllHit()
|
||||
{
|
||||
AddStep("start playback", () => EditorClock.Start());
|
||||
AddUntilStep("wait for all hit", () =>
|
||||
{
|
||||
DrawableHitCircle[] hitCircles = Editor.ChildrenOfType<DrawableHitCircle>().OrderBy(s => s.HitObject.StartTime).ToArray();
|
||||
|
||||
return hitCircles.Length == 5 && hitCircles.All(h => h.IsHit);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -46,12 +46,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
moveMouseToObject(() => slider);
|
||||
|
||||
AddStep("seek after end", () => EditorClock.Seek(750));
|
||||
AddUntilStep("wait for seek", () => !EditorClock.IsSeeking);
|
||||
AddStep("left click", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0);
|
||||
|
||||
AddStep("seek to visible", () => EditorClock.Seek(650));
|
||||
AddUntilStep("wait for seek", () => !EditorClock.IsSeeking);
|
||||
AddStep("left click", () => InputManager.Click(MouseButton.Left));
|
||||
AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider);
|
||||
}
|
||||
|
@ -1,8 +1,18 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
@ -21,5 +31,51 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestSliderDimsOnlyAfterStartTime()
|
||||
{
|
||||
bool sliderDimmedBeforeStartTime = false;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModFlashlight(),
|
||||
PassCondition = () =>
|
||||
{
|
||||
sliderDimmedBeforeStartTime |=
|
||||
Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>().Single().FlashlightDim > 0;
|
||||
return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime;
|
||||
},
|
||||
Beatmap = new OsuBeatmap
|
||||
{
|
||||
HitObjects = new List<OsuHitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 0, },
|
||||
new Slider
|
||||
{
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(),
|
||||
new PathControlPoint(new Vector2(100))
|
||||
})
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
StackLeniency = 0,
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(990, new Vector2()),
|
||||
new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(2000, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(2001, new Vector2(100)),
|
||||
},
|
||||
Autoplay = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -62,13 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
||||
}
|
||||
|
||||
int difficultyPeppyStars = (int)Math.Round(
|
||||
(baseBeatmap.Difficulty.DrainRate
|
||||
+ baseBeatmap.Difficulty.OverallDifficulty
|
||||
+ baseBeatmap.Difficulty.CircleSize
|
||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
||||
|
||||
scoreMultiplier = difficultyPeppyStars;
|
||||
scoreMultiplier = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength);
|
||||
|
||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||
|
||||
|
@ -4,20 +4,15 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -41,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public Action<DragEvent> DragInProgress;
|
||||
public Action DragEnded;
|
||||
|
||||
public List<PathControlPoint> PointsInSegment;
|
||||
|
||||
public readonly BindableBool IsSelected = new BindableBool();
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
|
||||
@ -56,27 +49,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
private IBindable<Vector2> hitObjectPosition;
|
||||
private IBindable<float> hitObjectScale;
|
||||
|
||||
[UsedImplicitly]
|
||||
private readonly IBindable<int> hitObjectVersion;
|
||||
|
||||
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
ControlPoint = controlPoint;
|
||||
|
||||
// we don't want to run the path type update on construction as it may inadvertently change the hit object.
|
||||
cachePoints(hitObject);
|
||||
|
||||
hitObjectVersion = hitObject.Path.Version.GetBoundCopy();
|
||||
|
||||
// schedule ensure that updates are only applied after all operations from a single frame are applied.
|
||||
// this avoids inadvertently changing the hit object path type for batch operations.
|
||||
hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
|
||||
{
|
||||
cachePoints(hitObject);
|
||||
updatePathType();
|
||||
}));
|
||||
|
||||
controlPoint.Changed += updateMarkerDisplay;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
@ -214,28 +191,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
|
||||
|
||||
private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint);
|
||||
|
||||
/// <summary>
|
||||
/// Handles correction of invalid path types.
|
||||
/// </summary>
|
||||
private void updatePathType()
|
||||
{
|
||||
if (ControlPoint.Type != PathType.PERFECT_CURVE)
|
||||
return;
|
||||
|
||||
if (PointsInSegment.Count > 3)
|
||||
ControlPoint.Type = PathType.BEZIER;
|
||||
|
||||
if (PointsInSegment.Count != 3)
|
||||
return;
|
||||
|
||||
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray();
|
||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||
ControlPoint.Type = PathType.BEZIER;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the circular control point marker.
|
||||
/// </summary>
|
||||
|
@ -14,10 +14,12 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -76,6 +78,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles correction of invalid path types.
|
||||
/// </summary>
|
||||
public void EnsureValidPathTypes()
|
||||
{
|
||||
List<PathControlPoint> pointsInCurrentSegment = new List<PathControlPoint>();
|
||||
|
||||
foreach (var controlPoint in controlPoints)
|
||||
{
|
||||
if (controlPoint.Type != null)
|
||||
{
|
||||
pointsInCurrentSegment.Add(controlPoint);
|
||||
ensureValidPathType(pointsInCurrentSegment);
|
||||
pointsInCurrentSegment.Clear();
|
||||
}
|
||||
|
||||
pointsInCurrentSegment.Add(controlPoint);
|
||||
}
|
||||
|
||||
ensureValidPathType(pointsInCurrentSegment);
|
||||
}
|
||||
|
||||
private void ensureValidPathType(IReadOnlyList<PathControlPoint> segment)
|
||||
{
|
||||
if (segment.Count == 0)
|
||||
return;
|
||||
|
||||
var first = segment[0];
|
||||
|
||||
if (first.Type != PathType.PERFECT_CURVE)
|
||||
return;
|
||||
|
||||
if (segment.Count > 3)
|
||||
first.Type = PathType.BEZIER;
|
||||
|
||||
if (segment.Count != 3)
|
||||
return;
|
||||
|
||||
ReadOnlySpan<Vector2> points = segment.Select(p => p.Position).ToArray();
|
||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||
first.Type = PathType.BEZIER;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
|
||||
/// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
|
||||
@ -240,7 +286,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
||||
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
||||
{
|
||||
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
|
||||
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
|
||||
int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
|
||||
|
||||
if (type?.Type == SplineType.PerfectCurve)
|
||||
{
|
||||
@ -249,8 +296,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
// and one segment of the previous type.
|
||||
int thirdPointIndex = indexInSegment + 2;
|
||||
|
||||
if (piece.PointsInSegment.Count > thirdPointIndex + 1)
|
||||
piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type;
|
||||
if (pointsInSegment.Count > thirdPointIndex + 1)
|
||||
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
||||
}
|
||||
|
||||
hitObject.Path.ExpectedDistance.Value = null;
|
||||
@ -339,6 +386,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
|
||||
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
||||
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
|
||||
|
||||
EnsureValidPathTypes();
|
||||
}
|
||||
|
||||
public void DragEnded() => changeHandler?.EndChange();
|
||||
@ -412,6 +461,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||
updatePathType(p, type);
|
||||
|
||||
EnsureValidPathTypes();
|
||||
});
|
||||
|
||||
if (countOfState == totalCount)
|
||||
|
@ -267,6 +267,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
segmentStart.Type = PathType.BEZIER;
|
||||
break;
|
||||
}
|
||||
|
||||
controlPointVisualiser.EnsureValidPathTypes();
|
||||
}
|
||||
|
||||
private void updateCursor()
|
||||
|
@ -254,6 +254,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
// Move the control points from the insertion index onwards to make room for the insertion
|
||||
controlPoints.Insert(insertionIndex, pathControlPoint);
|
||||
|
||||
ControlPointVisualiser?.EnsureValidPathTypes();
|
||||
|
||||
HitObject.SnapTo(distanceSnapProvider);
|
||||
|
||||
return pathControlPoint;
|
||||
@ -275,6 +277,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
controlPoints.Remove(c);
|
||||
}
|
||||
|
||||
ControlPointVisualiser?.EnsureValidPathTypes();
|
||||
|
||||
// Snap the slider to the current beat divisor before checking length validity.
|
||||
HitObject.SnapTo(distanceSnapProvider);
|
||||
|
||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
if (drawable is DrawableSlider s)
|
||||
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
|
||||
s.Tracking.ValueChanged += _ => flashlight.OnSliderTrackingChange(s);
|
||||
}
|
||||
|
||||
private partial class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
|
||||
@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
FlashlightSmoothness = 1.4f;
|
||||
}
|
||||
|
||||
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
|
||||
public void OnSliderTrackingChange(DrawableSlider e)
|
||||
{
|
||||
// If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield.
|
||||
FlashlightDim = e.NewValue ? 0.8f : 0.0f;
|
||||
FlashlightDim = Time.Current >= e.HitObject.StartTime && e.Tracking.Value ? 0.8f : 0.0f;
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private void load(OsuRulesetConfigManager? rulesetConfig)
|
||||
{
|
||||
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorRipples, showRipples);
|
||||
|
||||
AddInternal(ripplePool);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
|
@ -4,13 +4,11 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -35,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||
|
||||
private readonly JudgementPooler<DrawableOsuJudgement> judgementPooler;
|
||||
|
||||
public SmokeContainer Smoke { get; }
|
||||
public FollowPointRenderer FollowPoints { get; }
|
||||
|
||||
@ -42,8 +42,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer();
|
||||
|
||||
private readonly IDictionary<HitResult, DrawablePool<DrawableOsuJudgement>> poolDictionary = new Dictionary<HitResult, DrawablePool<DrawableOsuJudgement>>();
|
||||
|
||||
private readonly Container judgementAboveHitObjectLayer;
|
||||
|
||||
public OsuPlayfield()
|
||||
@ -65,24 +63,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
HitPolicy = new StartTimeOrderedHitPolicy();
|
||||
|
||||
foreach (var result in Enum.GetValues<HitResult>().Where(r =>
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case HitResult.Great:
|
||||
case HitResult.Ok:
|
||||
case HitResult.Meh:
|
||||
case HitResult.Miss:
|
||||
case HitResult.LargeTickMiss:
|
||||
case HitResult.IgnoreMiss:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}))
|
||||
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
|
||||
|
||||
AddRangeInternal(poolDictionary.Values);
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableOsuJudgement>(new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
HitResult.Meh,
|
||||
HitResult.Miss,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.IgnoreMiss,
|
||||
}, onJudgementLoaded));
|
||||
|
||||
NewResult += onNewResult;
|
||||
}
|
||||
@ -182,10 +171,10 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
if (!poolDictionary.TryGetValue(result.Type, out var pool))
|
||||
return;
|
||||
var explosion = judgementPooler.Get(result.Type, doj => doj.Apply(result, judgedObject));
|
||||
|
||||
DrawableOsuJudgement explosion = pool.Get(doj => doj.Apply(result, judgedObject));
|
||||
if (explosion == null)
|
||||
return;
|
||||
|
||||
judgementLayer.Add(explosion);
|
||||
|
||||
@ -201,31 +190,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||
}
|
||||
|
||||
private partial class DrawableJudgementPool : DrawablePool<DrawableOsuJudgement>
|
||||
{
|
||||
private readonly HitResult result;
|
||||
private readonly Action<DrawableOsuJudgement> onLoaded;
|
||||
|
||||
public DrawableJudgementPool(HitResult result, Action<DrawableOsuJudgement> onLoaded)
|
||||
: base(20)
|
||||
{
|
||||
this.result = result;
|
||||
this.onLoaded = onLoaded;
|
||||
}
|
||||
|
||||
protected override DrawableOsuJudgement CreateNewDrawable()
|
||||
{
|
||||
var judgement = base.CreateNewDrawable();
|
||||
|
||||
// just a placeholder to initialise the correct drawable hierarchy for this pool.
|
||||
judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null);
|
||||
|
||||
onLoaded?.Invoke(judgement);
|
||||
|
||||
return judgement;
|
||||
}
|
||||
}
|
||||
|
||||
private class OsuHitObjectLifetimeEntry : HitObjectLifetimeEntry
|
||||
{
|
||||
public OsuHitObjectLifetimeEntry(HitObject hitObject)
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
@ -65,11 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
|
||||
}
|
||||
|
||||
difficultyPeppyStars = (int)Math.Round(
|
||||
(baseBeatmap.Difficulty.DrainRate
|
||||
+ baseBeatmap.Difficulty.OverallDifficulty
|
||||
+ baseBeatmap.Difficulty.CircleSize
|
||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
||||
difficultyPeppyStars = LegacyRulesetExtensions.CalculateDifficultyPeppyStars(baseBeatmap.Difficulty, objectCount, drainLength);
|
||||
|
||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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 disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -10,7 +8,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -42,29 +39,29 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
public Container UnderlayElements { get; private set; } = null!;
|
||||
|
||||
private Container<HitExplosion> hitExplosionContainer;
|
||||
private Container<KiaiHitExplosion> kiaiExplosionContainer;
|
||||
private JudgementContainer<DrawableTaikoJudgement> judgementContainer;
|
||||
private ScrollingHitObjectContainer drumRollHitContainer;
|
||||
internal Drawable HitTarget;
|
||||
private SkinnableDrawable mascot;
|
||||
private Container<HitExplosion> hitExplosionContainer = null!;
|
||||
private Container<KiaiHitExplosion> kiaiExplosionContainer = null!;
|
||||
private JudgementContainer<DrawableTaikoJudgement> judgementContainer = null!;
|
||||
private ScrollingHitObjectContainer drumRollHitContainer = null!;
|
||||
internal Drawable HitTarget = null!;
|
||||
private SkinnableDrawable mascot = null!;
|
||||
|
||||
private readonly IDictionary<HitResult, DrawablePool<DrawableTaikoJudgement>> judgementPools = new Dictionary<HitResult, DrawablePool<DrawableTaikoJudgement>>();
|
||||
private JudgementPooler<DrawableTaikoJudgement> judgementPooler = null!;
|
||||
private readonly IDictionary<HitResult, HitExplosionPool> explosionPools = new Dictionary<HitResult, HitExplosionPool>();
|
||||
|
||||
private ProxyContainer topLevelHitContainer;
|
||||
private InputDrum inputDrum;
|
||||
private Container rightArea;
|
||||
private ProxyContainer topLevelHitContainer = null!;
|
||||
private InputDrum inputDrum = null!;
|
||||
private Container rightArea = null!;
|
||||
|
||||
/// <remarks>
|
||||
/// <see cref="Playfield.AddNested"/> is purposefully not called on this to prevent i.e. being able to interact
|
||||
/// with bar lines in the editor.
|
||||
/// </remarks>
|
||||
private BarLinePlayfield barLinePlayfield;
|
||||
private BarLinePlayfield barLinePlayfield = null!;
|
||||
|
||||
private Container barLineContent;
|
||||
private Container hitObjectContent;
|
||||
private Container overlayContent;
|
||||
private Container barLineContent = null!;
|
||||
private Container hitObjectContent = null!;
|
||||
private Container overlayContent = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
@ -202,13 +199,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
var hitWindows = new TaikoHitWindows();
|
||||
|
||||
foreach (var result in Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)))
|
||||
{
|
||||
judgementPools.Add(result, new DrawablePool<DrawableTaikoJudgement>(15));
|
||||
explosionPools.Add(result, new HitExplosionPool(result));
|
||||
}
|
||||
HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)).ToArray();
|
||||
|
||||
AddRangeInternal(judgementPools.Values);
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableTaikoJudgement>(usableHitResults));
|
||||
|
||||
foreach (var result in usableHitResults)
|
||||
explosionPools.Add(result, new HitExplosionPool(result));
|
||||
AddRangeInternal(explosionPools.Values);
|
||||
}
|
||||
|
||||
@ -339,7 +335,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
if (!result.Type.IsScorable())
|
||||
break;
|
||||
|
||||
judgementContainer.Add(judgementPools[result.Type].Get(j => j.Apply(result, judgedObject)));
|
||||
var judgement = judgementPooler.Get(result.Type, j => j.Apply(result, judgedObject));
|
||||
|
||||
if (judgement == null)
|
||||
return;
|
||||
|
||||
judgementContainer.Add(judgement);
|
||||
|
||||
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
|
||||
addExplosion(judgedObject, result.Type, type);
|
||||
|
3
osu.Game.Tests/Resources/mania-skin-broken-array.ini
Normal file
3
osu.Game.Tests/Resources/mania-skin-broken-array.ini
Normal file
@ -0,0 +1,3 @@
|
||||
[Mania]
|
||||
Keys: 4
|
||||
ColumnLineWidth: 3,,3,3,3
|
@ -114,5 +114,25 @@ namespace osu.Game.Tests.Skins
|
||||
Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParseArrayWithSomeEmptyElements()
|
||||
{
|
||||
var decoder = new LegacyManiaSkinDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("mania-skin-broken-array.ini"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var configs = decoder.Decode(stream);
|
||||
|
||||
Assert.That(configs.Count, Is.EqualTo(1));
|
||||
Assert.That(configs[0].ColumnLineWidth.Length, Is.EqualTo(5));
|
||||
Assert.That(configs[0].ColumnLineWidth[0], Is.EqualTo(3));
|
||||
Assert.That(configs[0].ColumnLineWidth[1], Is.EqualTo(0)); // malformed entry, should be parsed as zero
|
||||
Assert.That(configs[0].ColumnLineWidth[2], Is.EqualTo(3));
|
||||
Assert.That(configs[0].ColumnLineWidth[3], Is.EqualTo(3));
|
||||
Assert.That(configs[0].ColumnLineWidth[4], Is.EqualTo(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +147,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("Assert max judgement hidden", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().First().Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoDuplicates()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||
AddAssert("Check no duplicates",
|
||||
() => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Count(),
|
||||
() => Is.EqualTo(counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Select(c => c.ResultName.Text).Distinct().Count()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCycleDisplayModes()
|
||||
{
|
||||
@ -163,7 +173,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private int hiddenCount()
|
||||
{
|
||||
var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Type == HitResult.LargeTickHit);
|
||||
var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Types.Contains(HitResult.LargeTickHit));
|
||||
return num.Result.ResultCount.Value;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
@ -360,11 +359,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AllowImportCompletion = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart)
|
||||
{
|
||||
ShouldValidatePlaybackRate = false,
|
||||
};
|
||||
|
||||
protected override async Task ImportScore(Score score)
|
||||
{
|
||||
ScoreImportStarted = true;
|
||||
|
@ -10,12 +10,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene
|
||||
{
|
||||
private FreeModSelectOverlay freeModSelectOverlay;
|
||||
private FooterButtonFreeMods footerButtonFreeMods;
|
||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -119,11 +122,46 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectAllViaFooterButtonThenDeselectFromOverlay()
|
||||
{
|
||||
createFreeModSelect();
|
||||
|
||||
AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
||||
|
||||
AddStep("click footer select all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(footerButtonFreeMods);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
|
||||
AddAssert("footer button displays all", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "all"));
|
||||
|
||||
AddStep("click deselect all button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any());
|
||||
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
||||
}
|
||||
|
||||
private void createFreeModSelect()
|
||||
{
|
||||
AddStep("create free mod select screen", () => Child = freeModSelectOverlay = new FreeModSelectOverlay
|
||||
AddStep("create free mod select screen", () => Children = new Drawable[]
|
||||
{
|
||||
State = { Value = Visibility.Visible }
|
||||
freeModSelectOverlay = new FreeModSelectOverlay
|
||||
{
|
||||
State = { Value = Visibility.Visible }
|
||||
},
|
||||
footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay)
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Current = { BindTarget = freeModSelectOverlay.SelectedMods },
|
||||
},
|
||||
});
|
||||
AddUntilStep("all column content loaded",
|
||||
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
||||
@ -134,10 +172,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
var allAvailableMods = availableMods.Value
|
||||
.Where(pair => pair.Key != ModType.System)
|
||||
.SelectMany(pair => pair.Value)
|
||||
.SelectMany(pair => ModUtils.FlattenMods(pair.Value))
|
||||
.Where(mod => mod.UserPlayable && mod.HasImplementation)
|
||||
.ToList();
|
||||
|
||||
if (freeModSelectOverlay.SelectedMods.Value.Count != allAvailableMods.Count)
|
||||
return false;
|
||||
|
||||
foreach (var availableMod in allAvailableMods)
|
||||
{
|
||||
if (freeModSelectOverlay.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType()))
|
||||
|
@ -29,7 +29,9 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
@ -1009,6 +1011,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayStartsWhileInSongSelectWithDifferentRuleset()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
AllowedMods = new[] { new APIMod { Acronym = "HD" } },
|
||||
},
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
|
||||
AllowedMods = new[] { new APIMod { Acronym = "HD" } },
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("select hidden", () => multiplayerClient.ChangeUserMods(new[] { new APIMod { Acronym = "HD" } }));
|
||||
AddStep("make user ready", () => multiplayerClient.ChangeState(MultiplayerUserState.Ready));
|
||||
AddStep("press edit on second item", () => this.ChildrenOfType<DrawableRoomPlaylistItem>().Single(i => i.Item.RulesetID == 1)
|
||||
.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().Single().TriggerClick());
|
||||
|
||||
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID == 1);
|
||||
|
||||
AddStep("start match", () => multiplayerClient.StartMatch().WaitSafely());
|
||||
|
||||
AddUntilStep("wait for loading", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad);
|
||||
AddUntilStep("wait for gameplay to start", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Playing);
|
||||
AddAssert("hidden is selected", () => SelectedMods.Value, () => Has.One.TypeOf(typeof(OsuModHidden)));
|
||||
}
|
||||
|
||||
private void enterGameplay()
|
||||
{
|
||||
pressReadyButton();
|
||||
|
@ -268,6 +268,26 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("update not received", () => update == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed()
|
||||
{
|
||||
int userId = getUserId();
|
||||
long scoreId = getScoreId();
|
||||
setUpUser(userId);
|
||||
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddUntilStep("update received", () => update != null);
|
||||
AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
}
|
||||
|
||||
private int nextUserId = 2000;
|
||||
private long nextScoreId = 50000;
|
||||
|
||||
|
@ -76,6 +76,20 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionsCleared()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "3"))));
|
||||
|
||||
AddAssert("check count 5", () => control.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(5));
|
||||
|
||||
AddStep("delete all collections", () => writeAndRefresh(r => r.RemoveAll<BeatmapCollection>()));
|
||||
|
||||
AddAssert("check count 2", () => control.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRemovedFromDropdown()
|
||||
{
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Collections
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (int i in changes.DeletedIndices)
|
||||
foreach (int i in changes.DeletedIndices.OrderByDescending(i => i))
|
||||
filters.RemoveAt(i + 1);
|
||||
|
||||
foreach (int i in changes.InsertedIndices)
|
||||
|
@ -59,7 +59,8 @@ namespace osu.Game.Extensions
|
||||
/// <returns>A short relative string representing the input time.</returns>
|
||||
public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff)
|
||||
{
|
||||
if (time == default)
|
||||
// covers all `DateTimeOffset` instances with the date portion of 0001-01-01.
|
||||
if (time.Date == default)
|
||||
return "-";
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
@ -95,6 +95,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
case HitResult.LargeTickHit:
|
||||
case HitResult.SliderTailHit:
|
||||
case HitResult.Great:
|
||||
return Blue;
|
||||
|
||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Input
|
||||
{
|
||||
private Bindable<ConfineMouseMode> frameworkConfineMode;
|
||||
private Bindable<WindowMode> frameworkWindowMode;
|
||||
private Bindable<bool> frameworkMinimiseOnFocusLossInFullscreen;
|
||||
|
||||
private Bindable<OsuConfineMouseMode> osuConfineMode;
|
||||
private IBindable<bool> localUserPlaying;
|
||||
@ -31,7 +32,9 @@ namespace osu.Game.Input
|
||||
{
|
||||
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
|
||||
frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
||||
frameworkMinimiseOnFocusLossInFullscreen = frameworkConfigManager.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen);
|
||||
frameworkWindowMode.BindValueChanged(_ => updateConfineMode());
|
||||
frameworkMinimiseOnFocusLossInFullscreen.BindValueChanged(_ => updateConfineMode());
|
||||
|
||||
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
|
||||
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
|
||||
@ -46,7 +49,8 @@ namespace osu.Game.Input
|
||||
if (frameworkConfineMode.Disabled)
|
||||
return;
|
||||
|
||||
if (frameworkWindowMode.Value == WindowMode.Fullscreen)
|
||||
// override confine mode only when clicking outside the window minimises it.
|
||||
if (frameworkWindowMode.Value == WindowMode.Fullscreen && frameworkMinimiseOnFocusLossInFullscreen.Value)
|
||||
{
|
||||
frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
|
||||
return;
|
||||
|
@ -152,9 +152,13 @@ namespace osu.Game.Localisation
|
||||
/// <summary>
|
||||
/// "In order to change the renderer, the game will close. Please open it again."
|
||||
/// </summary>
|
||||
public static LocalisableString ChangeRendererConfirmation =>
|
||||
new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
|
||||
public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
/// <summary>
|
||||
/// "Minimise osu! when switching to another app"
|
||||
/// </summary>
|
||||
public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ namespace osu.Game.Online.API
|
||||
public IBindable<APIUser> LocalUser => localUser;
|
||||
public IBindableList<APIUser> Friends => friends;
|
||||
public IBindable<UserActivity> Activity => activity;
|
||||
public IBindable<UserStatistics> Statistics => statistics;
|
||||
|
||||
public Language Language => game.CurrentLanguage.Value;
|
||||
|
||||
@ -65,6 +66,8 @@ namespace osu.Game.Online.API
|
||||
private Bindable<UserStatus?> configStatus { get; } = new Bindable<UserStatus?>();
|
||||
private Bindable<UserStatus?> localUserStatus { get; } = new Bindable<UserStatus?>();
|
||||
|
||||
private Bindable<UserStatistics> statistics { get; } = new Bindable<UserStatistics>();
|
||||
|
||||
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
||||
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
@ -517,9 +520,21 @@ namespace osu.Game.Online.API
|
||||
flushQueue();
|
||||
}
|
||||
|
||||
public void UpdateStatistics(UserStatistics newStatistics)
|
||||
{
|
||||
statistics.Value = newStatistics;
|
||||
|
||||
if (IsLoggedIn)
|
||||
localUser.Value.Statistics = newStatistics;
|
||||
}
|
||||
|
||||
private static APIUser createGuestUser() => new GuestUser();
|
||||
|
||||
private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
|
||||
private void setLocalUser(APIUser user) => Scheduler.Add(() =>
|
||||
{
|
||||
localUser.Value = user;
|
||||
statistics.Value = user.Statistics;
|
||||
}, false);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Online.API
|
||||
|
||||
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||
|
||||
public Bindable<UserStatistics?> Statistics { get; } = new Bindable<UserStatistics?>();
|
||||
|
||||
public Language Language => Language.en;
|
||||
|
||||
public string AccessToken => "token";
|
||||
@ -115,6 +117,12 @@ namespace osu.Game.Online.API
|
||||
Id = DUMMY_USER_ID,
|
||||
};
|
||||
|
||||
Statistics.Value = new UserStatistics
|
||||
{
|
||||
GlobalRank = 1,
|
||||
CountryRank = 1
|
||||
};
|
||||
|
||||
state.Value = APIState.Online;
|
||||
}
|
||||
|
||||
@ -126,6 +134,14 @@ namespace osu.Game.Online.API
|
||||
LocalUser.Value = new GuestUser();
|
||||
}
|
||||
|
||||
public void UpdateStatistics(UserStatistics newStatistics)
|
||||
{
|
||||
Statistics.Value = newStatistics;
|
||||
|
||||
if (IsLoggedIn)
|
||||
LocalUser.Value.Statistics = newStatistics;
|
||||
}
|
||||
|
||||
public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;
|
||||
|
||||
public NotificationsClientConnector GetNotificationsConnector() => new PollingNotificationsClientConnector(this);
|
||||
@ -141,6 +157,7 @@ namespace osu.Game.Online.API
|
||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||
IBindableList<APIUser> IAPIProvider.Friends => Friends;
|
||||
IBindable<UserActivity> IAPIProvider.Activity => Activity;
|
||||
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;
|
||||
|
||||
/// <summary>
|
||||
/// During the next simulated login, the process will fail immediately.
|
||||
|
@ -28,6 +28,11 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
IBindable<UserActivity> Activity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current user's online statistics.
|
||||
/// </summary>
|
||||
IBindable<UserStatistics?> Statistics { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The language supplied by this provider to API requests.
|
||||
/// </summary>
|
||||
@ -111,6 +116,11 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
void Logout();
|
||||
|
||||
/// <summary>
|
||||
/// Sets Statistics bindable.
|
||||
/// </summary>
|
||||
void UpdateStatistics(UserStatistics newStatistics);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="IHubClientConnector"/>. May be null if not supported.
|
||||
/// </summary>
|
||||
|
@ -127,6 +127,8 @@ namespace osu.Game.Online.Solo
|
||||
{
|
||||
string rulesetName = callback.Score.Ruleset.ShortName;
|
||||
|
||||
api.UpdateStatistics(updatedStatistics);
|
||||
|
||||
if (latestStatistics == null)
|
||||
return;
|
||||
|
||||
|
@ -447,7 +447,7 @@ namespace osu.Game.Overlays.Mods
|
||||
private void filterMods()
|
||||
{
|
||||
foreach (var modState in AllAvailableMods)
|
||||
modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
|
||||
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
|
||||
}
|
||||
|
||||
private void updateMultiplier()
|
||||
|
@ -41,8 +41,8 @@ namespace osu.Game.Overlays.Mods
|
||||
private void updateEnabledState()
|
||||
{
|
||||
Enabled.Value = availableMods.Value
|
||||
.Where(pair => pair.Key != ModType.System)
|
||||
.SelectMany(pair => pair.Value)
|
||||
.Where(modState => modState.ValidForSelection.Value)
|
||||
.Any(modState => !modState.Active.Value && modState.Visible);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
private SettingsDropdown<Size> resolutionDropdown = null!;
|
||||
private SettingsDropdown<Display> displayDropdown = null!;
|
||||
private SettingsDropdown<WindowMode> windowModeDropdown = null!;
|
||||
private SettingsCheckbox minimiseOnFocusLossCheckbox = null!;
|
||||
private SettingsCheckbox safeAreaConsiderationsCheckbox = null!;
|
||||
|
||||
private Bindable<float> scalingPositionX = null!;
|
||||
@ -106,6 +107,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
ItemSource = resolutions,
|
||||
Current = sizeFullscreen
|
||||
},
|
||||
minimiseOnFocusLossCheckbox = new SettingsCheckbox
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss,
|
||||
Current = config.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen),
|
||||
Keywords = new[] { "alt-tab", "minimize", "focus", "hide" },
|
||||
},
|
||||
safeAreaConsiderationsCheckbox = new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Shrink game to avoid cameras and notches",
|
||||
@ -255,6 +262,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
{
|
||||
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
||||
minimiseOnFocusLossCheckbox.CanBeShown.Value = RuntimeInfo.IsDesktop && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
/// <param name="fullState">A <see cref="KeyCombination"/> generated from the full input state.</param>
|
||||
/// <param name="triggerKey">The key which triggered this update, and should be used as the binding.</param>
|
||||
public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) =>
|
||||
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey)));
|
||||
// TODO: Distinct() can be removed after https://github.com/ppy/osu-framework/pull/6130 is merged.
|
||||
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey).Distinct().ToArray()));
|
||||
|
||||
public void UpdateKeyCombination(KeyCombination newCombination)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
private Bindable<double> localSensitivity;
|
||||
|
||||
private Bindable<WindowMode> windowMode;
|
||||
private Bindable<bool> minimiseOnFocusLoss;
|
||||
private SettingsEnumDropdown<OsuConfineMouseMode> confineMouseModeSetting;
|
||||
private Bindable<bool> relativeMode;
|
||||
|
||||
@ -47,6 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
|
||||
relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
|
||||
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
|
||||
minimiseOnFocusLoss = config.GetBindable<bool>(FrameworkSetting.MinimiseOnFocusLossInFullscreen);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -98,21 +100,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
|
||||
localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
|
||||
|
||||
windowMode.BindValueChanged(mode =>
|
||||
{
|
||||
bool isFullscreen = mode.NewValue == WindowMode.Fullscreen;
|
||||
|
||||
if (isFullscreen)
|
||||
{
|
||||
confineMouseModeSetting.Current.Disabled = true;
|
||||
confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen;
|
||||
}
|
||||
else
|
||||
{
|
||||
confineMouseModeSetting.Current.Disabled = false;
|
||||
confineMouseModeSetting.TooltipText = string.Empty;
|
||||
}
|
||||
}, true);
|
||||
windowMode.BindValueChanged(_ => updateConfineMouseModeSettingVisibility());
|
||||
minimiseOnFocusLoss.BindValueChanged(_ => updateConfineMouseModeSettingVisibility(), true);
|
||||
|
||||
highPrecisionMouse.Current.BindValueChanged(highPrecision =>
|
||||
{
|
||||
@ -126,6 +115,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
}, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates disabled state and tooltip of <see cref="confineMouseModeSetting"/> to match when <see cref="ConfineMouseTracker"/> is overriding the confine mode.
|
||||
/// </summary>
|
||||
private void updateConfineMouseModeSettingVisibility()
|
||||
{
|
||||
bool confineModeOverriden = windowMode.Value == WindowMode.Fullscreen && minimiseOnFocusLoss.Value;
|
||||
|
||||
if (confineModeOverriden)
|
||||
{
|
||||
confineMouseModeSetting.Current.Disabled = true;
|
||||
confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen;
|
||||
}
|
||||
else
|
||||
{
|
||||
confineMouseModeSetting.Current.Disabled = false;
|
||||
confineMouseModeSetting.TooltipText = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SensitivitySetting : SettingsSlider<double, SensitivitySlider>
|
||||
{
|
||||
public SensitivitySetting()
|
||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
public abstract partial class ToolbarButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public const float PADDING = 3;
|
||||
|
||||
protected GlobalAction? Hotkey { get; set; }
|
||||
|
||||
public void SetIcon(Drawable icon)
|
||||
@ -63,6 +65,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
protected virtual Anchor TooltipAnchor => Anchor.TopLeft;
|
||||
|
||||
protected readonly Container ButtonContent;
|
||||
protected ConstrainedIconContainer IconContainer;
|
||||
protected SpriteText DrawableText;
|
||||
protected Box HoverBackground;
|
||||
@ -80,59 +83,66 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
protected ToolbarButton()
|
||||
{
|
||||
Width = Toolbar.HEIGHT;
|
||||
AutoSizeAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
Padding = new MarginPadding(3);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
BackgroundContent = new Container
|
||||
ButtonContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 6,
|
||||
CornerExponent = 3f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
HoverBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(80).Opacity(180),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
flashBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Colour = Color4.White.Opacity(100),
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
}
|
||||
},
|
||||
Flow = new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 },
|
||||
Width = Toolbar.HEIGHT,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding(PADDING),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
IconContainer = new ConstrainedIconContainer
|
||||
BackgroundContent = new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(20),
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 6,
|
||||
CornerExponent = 3f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
HoverBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(80).Opacity(180),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
flashBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Colour = Color4.White.Opacity(100),
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
}
|
||||
},
|
||||
DrawableText = new OsuSpriteText
|
||||
Flow = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 },
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
IconContainer = new ConstrainedIconContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(20),
|
||||
Alpha = 0,
|
||||
},
|
||||
DrawableText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -42,52 +42,59 @@ namespace osu.Game.Overlays.Toolbar
|
||||
clockDisplayMode = config.GetBindable<ToolbarClockDisplayMode>(OsuSetting.ToolbarClockDisplayMode);
|
||||
prefer24HourTime = config.GetBindable<bool>(OsuSetting.Prefer24HourTime);
|
||||
|
||||
Padding = new MarginPadding(3);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 6,
|
||||
CornerExponent = 3f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hoverBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(80).Opacity(180),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
flashBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Colour = Color4.White.Opacity(100),
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Padding = new MarginPadding(ToolbarButton.PADDING),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
analog = new AnalogClockDisplay
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 6,
|
||||
CornerExponent = 3f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hoverBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(80).Opacity(180),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
flashBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Colour = Color4.White.Opacity(100),
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
}
|
||||
},
|
||||
digital = new DigitalClockDisplay
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
analog = new AnalogClockDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
digital = new DigitalClockDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
public ToolbarHomeButton()
|
||||
{
|
||||
Width *= 1.4f;
|
||||
ButtonContent.Width *= 1.4f;
|
||||
Hotkey = GlobalAction.Home;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
public ToolbarMusicButton()
|
||||
{
|
||||
Hotkey = GlobalAction.ToggleNowPlaying;
|
||||
AutoSizeAxes = Axes.X;
|
||||
ButtonContent.AutoSizeAxes = Axes.X;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
public RulesetButton()
|
||||
{
|
||||
Padding = new MarginPadding(3)
|
||||
ButtonContent.Padding = new MarginPadding(PADDING)
|
||||
{
|
||||
Bottom = 5
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
public ToolbarSettingsButton()
|
||||
{
|
||||
Width *= 1.4f;
|
||||
ButtonContent.Width *= 1.4f;
|
||||
Hotkey = GlobalAction.ToggleSettings;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
public ToolbarUserButton()
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
ButtonContent.AutoSizeAxes = Axes.X;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@ -27,9 +26,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
public DrawableEditorRulesetWrapper(DrawableRuleset<TObject> drawableRuleset)
|
||||
{
|
||||
this.drawableRuleset = drawableRuleset;
|
||||
@ -42,6 +38,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
drawableRuleset.FrameStablePlayback = false;
|
||||
Playfield.DisplayJudgements.Value = false;
|
||||
}
|
||||
|
||||
@ -68,22 +65,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
Scheduler.AddOnce(regenerateAutoplay);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Whenever possible, we want to stay in frame stability playback.
|
||||
// Without doing so, we run into bugs with some gameplay elements not behaving as expected.
|
||||
//
|
||||
// Note that this is not using EditorClock.IsSeeking as that would exit frame stability
|
||||
// on all seeks. The intention here is to retain frame stability for small seeks.
|
||||
//
|
||||
// I still think no gameplay elements should require frame stability in the first place, but maybe that ship has sailed already..
|
||||
bool shouldBypassFrameStability = Math.Abs(drawableRuleset.FrameStableClock.CurrentTime - editorClock.CurrentTime) > 1000;
|
||||
|
||||
drawableRuleset.FrameStablePlayback = !shouldBypassFrameStability;
|
||||
}
|
||||
|
||||
private void regenerateAutoplay()
|
||||
{
|
||||
var autoplayMod = drawableRuleset.Mods.OfType<ModAutoplay>().Single();
|
||||
|
@ -57,5 +57,40 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1);
|
||||
}
|
||||
|
||||
public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength)
|
||||
{
|
||||
/*
|
||||
* WARNING: DO NOT TOUCH IF YOU DO NOT KNOW WHAT YOU ARE DOING
|
||||
*
|
||||
* It so happens that in stable, due to .NET Framework internals, float math would be performed
|
||||
* using x87 registers and opcodes.
|
||||
* .NET (Core) however uses SSE instructions on 32- and 64-bit words.
|
||||
* x87 registers are _80 bits_ wide. Which is notably wider than _both_ float and double.
|
||||
* Therefore, on a significant number of beatmaps, the rounding would not produce correct values.
|
||||
*
|
||||
* Thus, to crudely - but, seemingly *mostly* accurately, after checking across all ranked maps - emulate this,
|
||||
* use `decimal`, which is slow, but has bigger precision than `double`.
|
||||
* At the time of writing, there is _one_ ranked exception to this - namely https://osu.ppy.sh/beatmapsets/1156087#osu/2625853 -
|
||||
* but it is considered an "acceptable casualty", since in that case scores aren't inflated by _that_ much compared to others.
|
||||
*/
|
||||
|
||||
decimal objectToDrainRatio = drainLength != 0
|
||||
? Math.Clamp((decimal)objectCount / drainLength * 8, 0, 16)
|
||||
: 16;
|
||||
|
||||
/*
|
||||
* Notably, THE `double` CASTS BELOW ARE IMPORTANT AND MUST REMAIN.
|
||||
* Their goal is to trick the compiler / runtime into NOT promoting from single-precision float, as doing so would prompt it
|
||||
* to attempt to "silently" fix the single-precision values when converting to decimal,
|
||||
* which is NOT what the x87 FPU does.
|
||||
*/
|
||||
|
||||
decimal drainRate = (decimal)(double)difficulty.DrainRate;
|
||||
decimal overallDifficulty = (decimal)(double)difficulty.OverallDifficulty;
|
||||
decimal circleSize = (decimal)(double)difficulty.CircleSize;
|
||||
|
||||
return (int)Math.Round((drainRate + overallDifficulty + circleSize + objectToDrainRatio) / 38 * 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
[Description(@"")]
|
||||
[EnumMember(Value = "none")]
|
||||
[Order(14)]
|
||||
[Order(15)]
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Indicates small tick miss.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "small_tick_miss")]
|
||||
[Order(11)]
|
||||
[Order(12)]
|
||||
SmallTickMiss,
|
||||
|
||||
/// <summary>
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
[EnumMember(Value = "large_tick_miss")]
|
||||
[Description("-")]
|
||||
[Order(10)]
|
||||
[Order(11)]
|
||||
LargeTickMiss,
|
||||
|
||||
/// <summary>
|
||||
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
[Description("S Bonus")]
|
||||
[EnumMember(Value = "small_bonus")]
|
||||
[Order(9)]
|
||||
[Order(10)]
|
||||
SmallBonus,
|
||||
|
||||
/// <summary>
|
||||
@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
[Description("L Bonus")]
|
||||
[EnumMember(Value = "large_bonus")]
|
||||
[Order(8)]
|
||||
[Order(9)]
|
||||
LargeBonus,
|
||||
|
||||
/// <summary>
|
||||
@ -119,14 +119,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
[EnumMember(Value = "ignore_miss")]
|
||||
[Description("-")]
|
||||
[Order(13)]
|
||||
[Order(14)]
|
||||
IgnoreMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a hit that should be ignored for scoring purposes.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "ignore_hit")]
|
||||
[Order(12)]
|
||||
[Order(13)]
|
||||
IgnoreHit,
|
||||
|
||||
/// <summary>
|
||||
@ -136,14 +136,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// May be paired with <see cref="IgnoreHit"/>.
|
||||
/// </remarks>
|
||||
[EnumMember(Value = "combo_break")]
|
||||
[Order(15)]
|
||||
[Order(16)]
|
||||
ComboBreak,
|
||||
|
||||
/// <summary>
|
||||
/// A special judgement similar to <see cref="LargeTickHit"/> that's used to increase the valuation of the final tick of a slider.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "slider_tail_hit")]
|
||||
[Order(16)]
|
||||
[Order(8)]
|
||||
SliderTailHit,
|
||||
|
||||
/// <summary>
|
||||
|
77
osu.Game/Rulesets/UI/JudgementPooler.cs
Normal file
77
osu.Game/Rulesets/UI/JudgementPooler.cs
Normal file
@ -0,0 +1,77 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the task of preparing poolable drawable judgements for gameplay usage.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The drawable judgement type.</typeparam>
|
||||
public partial class JudgementPooler<T> : CompositeComponent
|
||||
where T : DrawableJudgement, new()
|
||||
{
|
||||
private readonly IDictionary<HitResult, DrawablePool<T>> poolDictionary = new Dictionary<HitResult, DrawablePool<T>>();
|
||||
|
||||
private readonly IEnumerable<HitResult> usableHitResults;
|
||||
private readonly Action<T>? onJudgementInitialLoad;
|
||||
|
||||
public JudgementPooler(IEnumerable<HitResult> usableHitResults, Action<T>? onJudgementInitialLoad = null)
|
||||
{
|
||||
this.usableHitResults = usableHitResults;
|
||||
this.onJudgementInitialLoad = onJudgementInitialLoad;
|
||||
}
|
||||
|
||||
public T? Get(HitResult result, Action<T>? setupAction)
|
||||
{
|
||||
if (!poolDictionary.TryGetValue(result, out var pool))
|
||||
return null;
|
||||
|
||||
return pool.Get(setupAction);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
foreach (HitResult result in usableHitResults)
|
||||
{
|
||||
var pool = new DrawableJudgementPool(result, onJudgementInitialLoad);
|
||||
poolDictionary.Add(result, pool);
|
||||
AddInternal(pool);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class DrawableJudgementPool : DrawablePool<T>
|
||||
{
|
||||
private readonly HitResult result;
|
||||
private readonly Action<T>? onLoaded;
|
||||
|
||||
public DrawableJudgementPool(HitResult result, Action<T>? onLoaded)
|
||||
: base(20)
|
||||
{
|
||||
this.result = result;
|
||||
this.onLoaded = onLoaded;
|
||||
}
|
||||
|
||||
protected override T CreateNewDrawable()
|
||||
{
|
||||
var judgement = base.CreateNewDrawable();
|
||||
|
||||
// just a placeholder to initialise the correct drawable hierarchy for this pool.
|
||||
judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null);
|
||||
|
||||
onLoaded?.Invoke(judgement);
|
||||
|
||||
return judgement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ using osu.Game.Extensions;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
@ -38,9 +39,13 @@ namespace osu.Game.Scoring.Legacy
|
||||
/// <item><description>30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores.</description></item>
|
||||
/// <item><description>30000010: Fix mania score V1 conversion using score V1 accuracy rather than V2 accuracy. Reconvert all scores.</description></item>
|
||||
/// <item><description>30000011: Re-do catch scoring to mirror stable Score V2 as closely as feasible. Reconvert all scores.</description></item>
|
||||
/// <item><description>
|
||||
/// 30000012: Fix incorrect total score conversion on selected beatmaps after implementing the more correct
|
||||
/// <see cref="LegacyRulesetExtensions.CalculateDifficultyPeppyStars"/> method. Reconvert all scores.
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public const int LATEST_VERSION = 30000011;
|
||||
public const int LATEST_VERSION = 30000012;
|
||||
|
||||
/// <summary>
|
||||
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Backgrounds
|
||||
/// </summary>
|
||||
public readonly Bindable<float> DimWhenUserSettingsIgnored = new Bindable<float>();
|
||||
|
||||
internal readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
|
||||
internal readonly Bindable<bool> IsBreakTime = new Bindable<bool>();
|
||||
|
||||
private readonly DimmableBackground dimmable;
|
||||
|
||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
protected RulesetStore Rulesets { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
@ -422,7 +422,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
if (selected == null)
|
||||
return;
|
||||
|
||||
var rulesetInstance = rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
Debug.Assert(rulesetInstance != null);
|
||||
var allowedMods = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance));
|
||||
|
||||
@ -463,7 +463,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
if (SelectedItem.Value == null || !this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
var rulesetInstance = rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
Debug.Assert(rulesetInstance != null);
|
||||
Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
|
||||
}
|
||||
@ -473,7 +473,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
if (SelectedItem.Value == null || !this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
Ruleset.Value = rulesets.GetRuleset(SelectedItem.Value.RulesetID);
|
||||
Ruleset.Value = Rulesets.GetRuleset(SelectedItem.Value.RulesetID);
|
||||
}
|
||||
|
||||
private void beginHandlingTrack()
|
||||
|
@ -241,8 +241,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
// update local mods based on room's reported status for the local user (omitting the base call implementation).
|
||||
// this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed).
|
||||
var ruleset = Ruleset.Value.CreateInstance();
|
||||
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(ruleset))).ToList();
|
||||
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
Debug.Assert(rulesetInstance != null);
|
||||
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(rulesetInstance)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
|
@ -137,6 +137,12 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Spacing = new Vector2(-2f, 0f);
|
||||
Font = new FontUsage(font_name, 1);
|
||||
glyphStore = new GlyphStore(font_name, textures, getLookup);
|
||||
|
||||
// cache common lookups ahead of time.
|
||||
foreach (char c in new[] { '.', '%', 'x' })
|
||||
glyphStore.Get(font_name, c);
|
||||
for (int i = 0; i < 10; i++)
|
||||
glyphStore.Get(font_name, (char)('0' + i));
|
||||
}
|
||||
|
||||
protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -21,18 +22,30 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
[Resolved]
|
||||
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
||||
|
||||
public List<JudgementCount> Results = new List<JudgementCount>();
|
||||
private readonly Dictionary<HitResult, JudgementCount> results = new Dictionary<HitResult, JudgementCount>();
|
||||
|
||||
public IEnumerable<JudgementCount> Counters => counters;
|
||||
|
||||
private readonly List<JudgementCount> counters = new List<JudgementCount>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<RulesetInfo> ruleset)
|
||||
{
|
||||
foreach (var result in ruleset.Value.CreateInstance().GetHitResults())
|
||||
// Due to weirdness in judgements, some results have the same name and should be aggregated for display purposes.
|
||||
// There's only one case of this right now ("slider end").
|
||||
foreach (var group in ruleset.Value.CreateInstance().GetHitResults().GroupBy(r => r.displayName))
|
||||
{
|
||||
Results.Add(new JudgementCount
|
||||
var judgementCount = new JudgementCount
|
||||
{
|
||||
Type = result.result,
|
||||
DisplayName = group.Key,
|
||||
Types = group.Select(r => r.result).ToArray(),
|
||||
ResultCount = new BindableInt()
|
||||
});
|
||||
};
|
||||
|
||||
counters.Add(judgementCount);
|
||||
|
||||
foreach (var r in group)
|
||||
results[r.result] = judgementCount;
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,13 +59,20 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
|
||||
private void updateCount(JudgementResult judgement, bool revert)
|
||||
{
|
||||
foreach (JudgementCount result in Results.Where(result => result.Type == judgement.Type))
|
||||
result.ResultCount.Value = revert ? result.ResultCount.Value - 1 : result.ResultCount.Value + 1;
|
||||
if (!results.TryGetValue(judgement.Type, out var count))
|
||||
return;
|
||||
|
||||
if (revert)
|
||||
count.ResultCount.Value--;
|
||||
else
|
||||
count.ResultCount.Value++;
|
||||
}
|
||||
|
||||
public struct JudgementCount
|
||||
{
|
||||
public HitResult Type { get; set; }
|
||||
public LocalisableString DisplayName { get; set; }
|
||||
|
||||
public HitResult[] Types { get; set; }
|
||||
|
||||
public BindableInt ResultCount { get; set; }
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -44,14 +45,14 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
{
|
||||
Alpha = 0,
|
||||
Font = OsuFont.Numeric.With(size: 8),
|
||||
Text = ruleset.Value.CreateInstance().GetDisplayNameForHitResult(Result.Type)
|
||||
Text = Result.DisplayName,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = Result.Type;
|
||||
var result = Result.Types.First();
|
||||
|
||||
Colour = result.IsBasic() ? colours.ForHitResult(Result.Type) : !result.IsBonus() ? colours.PurpleLight : colours.PurpleLighter;
|
||||
Colour = result.IsBasic() ? colours.ForHitResult(result) : !result.IsBonus() ? colours.PurpleLight : colours.PurpleLighter;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -49,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
AutoSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
foreach (var result in judgementCountController.Results)
|
||||
foreach (var result in judgementCountController.Counters)
|
||||
CounterFlow.Add(createCounter(result));
|
||||
}
|
||||
|
||||
@ -88,7 +89,9 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
if (index == 0 && !ShowMaxJudgement.Value)
|
||||
return false;
|
||||
|
||||
if (counter.Result.Type.IsBasic())
|
||||
var hitResult = counter.Result.Types.First();
|
||||
|
||||
if (hitResult.IsBasic())
|
||||
return true;
|
||||
|
||||
switch (Mode.Value)
|
||||
@ -97,7 +100,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
return false;
|
||||
|
||||
case DisplayMode.Normal:
|
||||
return !counter.Result.Type.IsBonus();
|
||||
return !hitResult.IsBonus();
|
||||
|
||||
case DisplayMode.All:
|
||||
return true;
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play
|
||||
/// Whether the audio playback rate should be validated.
|
||||
/// Mostly disabled for tests.
|
||||
/// </summary>
|
||||
internal bool ShouldValidatePlaybackRate { get; init; } = true;
|
||||
internal bool ShouldValidatePlaybackRate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the audio playback is within acceptable ranges.
|
||||
|
@ -1078,7 +1078,7 @@ namespace osu.Game.Screens.Play
|
||||
b.FadeColour(Color4.White, 250);
|
||||
|
||||
// bind component bindables.
|
||||
b.IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||
((IBindable<bool>)b.IsBreakTime).BindTo(breakTracker.IsBreakTime);
|
||||
|
||||
b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
|
||||
|
||||
@ -1238,7 +1238,13 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
if (this.IsCurrentScreen())
|
||||
{
|
||||
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
|
||||
ApplyToBackground(b =>
|
||||
{
|
||||
b.IgnoreUserSettings.Value = true;
|
||||
|
||||
b.IsBreakTime.UnbindFrom(breakTracker.IsBreakTime);
|
||||
b.IsBreakTime.Value = false;
|
||||
});
|
||||
storyboardReplacesBackground.Value = false;
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,11 @@ namespace osu.Game.Screens.Play
|
||||
AddInternal(new PlayerTouchInputDetector());
|
||||
}
|
||||
|
||||
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart)
|
||||
{
|
||||
ShouldValidatePlaybackRate = true,
|
||||
};
|
||||
|
||||
protected override void LoadAsyncComplete()
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
|
@ -146,14 +146,6 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
// Scores will be refreshed on arriving at this screen.
|
||||
// Clear them to avoid animation overload on returning to song select.
|
||||
playBeatmapDetailArea.Leaderboard.ClearScores();
|
||||
base.OnSuspending(e);
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnResuming(e);
|
||||
|
@ -155,7 +155,15 @@ namespace osu.Game.Skinning
|
||||
if (i >= output.Length)
|
||||
break;
|
||||
|
||||
output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * (applyScaleFactor ? LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR : 1);
|
||||
if (!float.TryParse(values[i], NumberStyles.Float, CultureInfo.InvariantCulture, out float parsedValue))
|
||||
// some skins may provide incorrect entries in array values. to match stable behaviour, read such entries as zero.
|
||||
// see: https://github.com/ppy/osu/issues/26464, stable code: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Skinning/Components/Section.cs#L134-L137
|
||||
parsedValue = 0;
|
||||
|
||||
if (applyScaleFactor)
|
||||
parsedValue *= LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
|
||||
|
||||
output[i] = parsedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,12 @@ namespace osu.Game.Skinning
|
||||
Spacing = new Vector2(-skin.GetFontOverlap(font), 0);
|
||||
|
||||
glyphStore = new LegacyGlyphStore(fontPrefix, skin, MaxSizePerGlyph);
|
||||
|
||||
// cache common lookups ahead of time.
|
||||
foreach (char c in FixedWidthExcludeCharacters)
|
||||
glyphStore.Get(fontPrefix, c);
|
||||
for (int i = 0; i < 10; i++)
|
||||
glyphStore.Get(fontPrefix, (char)('0' + i));
|
||||
}
|
||||
|
||||
protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.110.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.114.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1228.0" />
|
||||
<PackageReference Include="Sentry" Version="3.40.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -23,6 +23,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.110.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.114.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user