mirror of
https://github.com/ppy/osu.git
synced 2025-02-16 01:03:21 +08:00
Merge branch 'master' into carousel-maintain-selection-over-update
This commit is contained in:
commit
c8fdfd298c
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.810.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.805.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.810.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
52
osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
Normal file
52
osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
|
{
|
||||||
|
public class TestSceneBarLine : ManiaSkinnableTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestMinor()
|
||||||
|
{
|
||||||
|
AddStep("Create barlines", () => recreate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recreate(Func<IEnumerable<BarLine>>? createBarLines = null)
|
||||||
|
{
|
||||||
|
var stageDefinitions = new List<StageDefinition>
|
||||||
|
{
|
||||||
|
new StageDefinition { Columns = 4 },
|
||||||
|
};
|
||||||
|
|
||||||
|
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
|
||||||
|
{
|
||||||
|
if (createBarLines != null)
|
||||||
|
{
|
||||||
|
var barLines = createBarLines();
|
||||||
|
|
||||||
|
foreach (var b in barLines)
|
||||||
|
s.Add(b);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
s.Add(new BarLine
|
||||||
|
{
|
||||||
|
StartTime = Time.Current + i * 500,
|
||||||
|
Major = i % 4 == 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override string Acronym => "CS";
|
public override string Acronym => "CS";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.9;
|
||||||
|
|
||||||
public override string Description => "No more tricky speed changes!";
|
public override string Description => "No more tricky speed changes!";
|
||||||
|
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -16,21 +13,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Height of major bar line triangles.
|
|
||||||
/// </summary>
|
|
||||||
private const float triangle_height = 12;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Offset of the major bar line triangles from the sides of the bar line.
|
|
||||||
/// </summary>
|
|
||||||
private const float triangle_offset = 9;
|
|
||||||
|
|
||||||
public DrawableBarLine(BarLine barLine)
|
public DrawableBarLine(BarLine barLine)
|
||||||
: base(barLine)
|
: base(barLine)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = 2f;
|
Height = barLine.Major ? 1.7f : 1.2f;
|
||||||
|
|
||||||
AddInternal(new Box
|
AddInternal(new Box
|
||||||
{
|
{
|
||||||
@ -38,34 +25,33 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = new Color4(255, 204, 33, 255),
|
Alpha = barLine.Major ? 0.5f : 0.2f
|
||||||
});
|
});
|
||||||
|
|
||||||
if (barLine.Major)
|
if (barLine.Major)
|
||||||
{
|
{
|
||||||
AddInternal(new EquilateralTriangle
|
Vector2 size = new Vector2(22, 6);
|
||||||
|
const float line_offset = 4;
|
||||||
|
|
||||||
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Name = "Left triangle",
|
Name = "Left line",
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.CentreRight,
|
||||||
Size = new Vector2(triangle_height),
|
|
||||||
X = -triangle_offset,
|
Size = size,
|
||||||
Rotation = 90
|
X = -line_offset,
|
||||||
});
|
});
|
||||||
|
|
||||||
AddInternal(new EquilateralTriangle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Name = "Right triangle",
|
Name = "Right line",
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.CentreLeft,
|
||||||
Size = new Vector2(triangle_height),
|
Size = size,
|
||||||
X = triangle_offset,
|
X = line_offset,
|
||||||
Rotation = -90
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!barLine.Major)
|
|
||||||
Alpha = 0.2f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
{
|
{
|
||||||
Mod = mod,
|
Mod = mod,
|
||||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
|
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
|
||||||
Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value)
|
Precision.AlmostEquals(Player.GameplayClockContainer.Rate, mod.SpeedChange.Value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.6369583000323935d, 206, "diffcalc-test")]
|
[TestCase(6.7115569159190587d, 206, "diffcalc-test")]
|
||||||
[TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
|
[TestCase(1.4391311903612753d, 45, "zero-length-sliders")]
|
||||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||||
|
|
||||||
[TestCase(8.8816128335486386d, 206, "diffcalc-test")]
|
[TestCase(8.9757300665532966d, 206, "diffcalc-test")]
|
||||||
[TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
|
[TestCase(1.7437232654020756d, 45, "zero-length-sliders")]
|
||||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
||||||
|
|
||||||
[TestCase(6.6369583000323935d, 239, "diffcalc-test")]
|
[TestCase(6.7115569159190587d, 239, "diffcalc-test")]
|
||||||
[TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
|
[TestCase(1.4391311903612753d, 54, "zero-length-sliders")]
|
||||||
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -12,7 +10,6 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -36,16 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private const double spinner_duration = 6000;
|
private const double spinner_duration = 6000;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audioManager { get; set; }
|
private AudioManager audioManager { get; set; } = null!;
|
||||||
|
|
||||||
protected override bool Autoplay => true;
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
private DrawableSpinner drawableSpinner;
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
@ -67,12 +64,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||||
});
|
});
|
||||||
AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
|
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
||||||
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.Not.EqualTo(0).Within(100));
|
||||||
|
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
|
AddAssert("is disc rotation almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(0).Within(trackerRotationTolerance));
|
||||||
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
AddAssert("is disc rotation absolute almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(0).Within(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -100,20 +97,20 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
||||||
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
||||||
// (5% relative to the final rotation value, but we're half-way through the spin).
|
// (5% relative to the final rotation value, but we're half-way through the spin).
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
||||||
AddAssert("symbol rotation rewound",
|
AddAssert("symbol rotation rewound",
|
||||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
||||||
AddAssert("is cumulative rotation rewound",
|
AddAssert("is cumulative rotation rewound",
|
||||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
||||||
|
|
||||||
addSeekStep(spinner_start_time + 5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddAssert("is disc rotation almost same",
|
AddAssert("is disc rotation almost same",
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
||||||
AddAssert("is symbol rotation almost same",
|
AddAssert("is symbol rotation almost same",
|
||||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
||||||
AddAssert("is cumulative rotation almost same",
|
AddAssert("is cumulative rotation almost same",
|
||||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -177,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
||||||
|
|
||||||
addSeekStep(2000);
|
addSeekStep(2000);
|
||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
|
||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(0.5)]
|
[TestCase(0.5)]
|
||||||
@ -202,14 +199,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
|
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
|
||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
AddAssert("progress almost same", () => expectedProgress, () => Is.EqualTo(drawableSpinner.Progress).Within(0.05));
|
||||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
AddAssert("spm almost same", () => expectedSpm, () => Is.EqualTo(drawableSpinner.SpinsPerMinute.Value).Within(2.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSeekStep(double time)
|
private void addSeekStep(double time)
|
||||||
{
|
{
|
||||||
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
||||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
AddUntilStep("wait for seek to finish", () => time, () => Is.EqualTo(Player.DrawableRuleset.FrameStableClock.CurrentTime).Within(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
||||||
|
@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
public static class AimEvaluator
|
public static class AimEvaluator
|
||||||
{
|
{
|
||||||
private const double wide_angle_multiplier = 1.5;
|
private const double wide_angle_multiplier = 1.5;
|
||||||
private const double acute_angle_multiplier = 2.0;
|
private const double acute_angle_multiplier = 1.95;
|
||||||
private const double slider_multiplier = 1.5;
|
private const double slider_multiplier = 1.35;
|
||||||
private const double velocity_change_multiplier = 0.75;
|
private const double velocity_change_multiplier = 0.75;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (osuLastObj.TravelTime != 0)
|
if (osuLastObj.BaseObject is Slider)
|
||||||
{
|
{
|
||||||
// Reward sliders based on velocity.
|
// Reward sliders based on velocity.
|
||||||
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||||
|
@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
private const double max_opacity_bonus = 0.4;
|
private const double max_opacity_bonus = 0.4;
|
||||||
private const double hidden_bonus = 0.2;
|
private const double hidden_bonus = 0.2;
|
||||||
|
|
||||||
|
private const double min_velocity = 0.5;
|
||||||
|
private const double slider_multiplier = 1.3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><description>distance between the previous and current object,</description></item>
|
/// <item><description>distance between a number of previous objects and the current object,</description></item>
|
||||||
/// <item><description>the visual opacity of the current object,</description></item>
|
/// <item><description>the visual opacity of the current object,</description></item>
|
||||||
|
/// <item><description>length and speed of the current object (for sliders),</description></item>
|
||||||
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -73,6 +77,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
if (hidden)
|
if (hidden)
|
||||||
result *= 1.0 + hidden_bonus;
|
result *= 1.0 + hidden_bonus;
|
||||||
|
|
||||||
|
double sliderBonus = 0.0;
|
||||||
|
|
||||||
|
if (osuCurrent.BaseObject is Slider osuSlider)
|
||||||
|
{
|
||||||
|
// Invert the scaling factor to determine the true travel distance independent of circle size.
|
||||||
|
double pixelTravelDistance = osuSlider.LazyTravelDistance / scalingFactor;
|
||||||
|
|
||||||
|
// Reward sliders based on velocity.
|
||||||
|
sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5);
|
||||||
|
|
||||||
|
// Longer sliders require more memorisation.
|
||||||
|
sliderBonus *= pixelTravelDistance;
|
||||||
|
|
||||||
|
// Nerf sliders with repeats, as less memorisation is required.
|
||||||
|
if (osuSlider.RepeatCount > 0)
|
||||||
|
sliderBonus /= (osuSlider.RepeatCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
result += sliderBonus * slider_multiplier;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModRelax))
|
if (mods.Any(h => h is OsuModRelax))
|
||||||
|
{
|
||||||
|
aimRating *= 0.9;
|
||||||
speedRating = 0.0;
|
speedRating = 0.0;
|
||||||
|
flashlightRating *= 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||||
@ -62,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
|
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
|
||||||
);
|
);
|
||||||
|
|
||||||
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
||||||
|
|
||||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||||
double drainRate = beatmap.Difficulty.DrainRate;
|
double drainRate = beatmap.Difficulty.DrainRate;
|
||||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||||
{
|
{
|
||||||
|
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||||
|
|
||||||
private double accuracy;
|
private double accuracy;
|
||||||
private int scoreMaxCombo;
|
private int scoreMaxCombo;
|
||||||
private int countGreat;
|
private int countGreat;
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
||||||
|
|
||||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModNoFail))
|
if (score.Mods.Any(m => m is OsuModNoFail))
|
||||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
||||||
@ -51,10 +53,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
if (score.Mods.Any(h => h is OsuModRelax))
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
{
|
{
|
||||||
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
// https://www.desmos.com/calculator/bc9eybdthb
|
||||||
effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
|
// we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0
|
||||||
|
// this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11)
|
||||||
|
double okMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 1.8) : 1.0);
|
||||||
|
double mehMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 5) : 1.0);
|
||||||
|
|
||||||
multiplier *= 0.6;
|
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
||||||
|
effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits);
|
||||||
}
|
}
|
||||||
|
|
||||||
double aimValue = computeAimValue(score, osuAttributes);
|
double aimValue = computeAimValue(score, osuAttributes);
|
||||||
@ -103,7 +109,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (attributes.ApproachRate > 10.33)
|
if (attributes.ApproachRate > 10.33)
|
||||||
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
|
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
|
||||||
else if (attributes.ApproachRate < 8.0)
|
else if (attributes.ApproachRate < 8.0)
|
||||||
approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate);
|
approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate);
|
||||||
|
|
||||||
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
|
approachRateFactor = 0.0;
|
||||||
|
|
||||||
aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
|
aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
|
||||||
|
|
||||||
@ -134,6 +143,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||||
|
|
||||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
@ -174,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
||||||
|
|
||||||
// Scale the speed value with # of 50s to punish doubletapping.
|
// Scale the speed value with # of 50s to punish doubletapping.
|
||||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||||
|
|
||||||
return speedValue;
|
return speedValue;
|
||||||
}
|
}
|
||||||
@ -266,6 +278,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
||||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||||
{
|
{
|
||||||
private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
/// <summary>
|
||||||
|
/// A distance by which all distances should be scaled in order to assume a uniform circle size.
|
||||||
|
/// </summary>
|
||||||
|
public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||||
|
|
||||||
private const int min_delta_time = 25;
|
private const int min_delta_time = 25;
|
||||||
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f;
|
||||||
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
private const float assumed_slider_radius = NORMALISED_RADIUS * 1.8f;
|
||||||
|
|
||||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
public double TravelDistance { get; private set; }
|
public double TravelDistance { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for a non-zero distance.
|
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for <see cref="Slider"/> objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TravelTime { get; private set; }
|
public double TravelTime { get; private set; }
|
||||||
|
|
||||||
@ -123,7 +127,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
if (BaseObject is Slider currentSlider)
|
if (BaseObject is Slider currentSlider)
|
||||||
{
|
{
|
||||||
computeSliderCursorPosition(currentSlider);
|
computeSliderCursorPosition(currentSlider);
|
||||||
TravelDistance = currentSlider.LazyTravelDistance;
|
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
||||||
|
TravelDistance = currentSlider.LazyTravelDistance * (float)Math.Pow(1 + currentSlider.RepeatCount / 2.5, 1.0 / 2.5);
|
||||||
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
float scalingFactor = normalised_radius / (float)BaseObject.Radius;
|
float scalingFactor = NORMALISED_RADIUS / (float)BaseObject.Radius;
|
||||||
|
|
||||||
if (BaseObject.Radius < 30)
|
if (BaseObject.Radius < 30)
|
||||||
{
|
{
|
||||||
@ -206,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
|
|
||||||
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
||||||
var currCursorPosition = slider.StackedPosition;
|
var currCursorPosition = slider.StackedPosition;
|
||||||
double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
double scalingFactor = NORMALISED_RADIUS / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||||
|
|
||||||
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
@ -234,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
else if (currMovementObj is SliderRepeat)
|
else if (currMovementObj is SliderRepeat)
|
||||||
{
|
{
|
||||||
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
||||||
requiredMovement = normalised_radius;
|
requiredMovement = NORMALISED_RADIUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currMovementLength > requiredMovement)
|
if (currMovementLength > requiredMovement)
|
||||||
@ -248,8 +253,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
if (i == slider.NestedHitObjects.Count - 1)
|
if (i == slider.NestedHitObjects.Count - 1)
|
||||||
slider.LazyEndPosition = currCursorPosition;
|
slider.LazyEndPosition = currCursorPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
slider.LazyTravelDistance *= (float)Math.Pow(1 + slider.RepeatCount / 2.5, 1.0 / 2.5); // Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
|
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double currentStrain;
|
private double currentStrain;
|
||||||
|
|
||||||
private double skillMultiplier => 23.25;
|
private double skillMultiplier => 23.55;
|
||||||
private double strainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModAutopilot;
|
public override IconUsage? Icon => OsuIcon.ModAutopilot;
|
||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
public bool PerformFail() => false;
|
public bool PerformFail() => false;
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
|
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.5;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock = null!;
|
private IFrameStableClock gameplayClock = null!;
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
private bool rotationTransferred;
|
private bool rotationTransferred;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private GameplayClock gameplayClock { get; set; }
|
private IGameplayClock gameplayClock { get; set; }
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
|
@ -206,7 +206,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
|
private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
|
||||||
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()) == expectedDistance);
|
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()), () => Is.EqualTo(expectedDistance));
|
||||||
|
|
||||||
private void assertDurationToDistance(double duration, float expectedDistance)
|
private void assertDurationToDistance(double duration, float expectedDistance)
|
||||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
|
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start clock", () => gameplayClockContainer.Start());
|
AddStep("start clock", () => gameplayClockContainer.Start());
|
||||||
AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0);
|
AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.ElapsedFrameTime > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -60,16 +60,16 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start clock", () => gameplayClockContainer.Start());
|
AddStep("start clock", () => gameplayClockContainer.Start());
|
||||||
AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000);
|
AddUntilStep("current time greater 2000", () => gameplayClockContainer.CurrentTime > 2000);
|
||||||
|
|
||||||
double timeAtReset = 0;
|
double timeAtReset = 0;
|
||||||
AddStep("reset clock", () =>
|
AddStep("reset clock", () =>
|
||||||
{
|
{
|
||||||
timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime;
|
timeAtReset = gameplayClockContainer.CurrentTime;
|
||||||
gameplayClockContainer.Reset();
|
gameplayClockContainer.Reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset);
|
AddAssert("current time < time at reset", () => gameplayClockContainer.CurrentTime < timeAtReset);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -77,7 +77,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)
|
Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)
|
||||||
{
|
{
|
||||||
IsPaused = { Value = true },
|
|
||||||
Child = new FrameStabilityContainer
|
Child = new FrameStabilityContainer
|
||||||
{
|
{
|
||||||
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
||||||
@ -106,7 +105,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Add(gameplayContainer = new MasterGameplayClockContainer(working, start_time)
|
Add(gameplayContainer = new MasterGameplayClockContainer(working, start_time)
|
||||||
{
|
{
|
||||||
StartTime = start_time,
|
StartTime = start_time,
|
||||||
IsPaused = { Value = true },
|
|
||||||
Child = new FrameStabilityContainer
|
Child = new FrameStabilityContainer
|
||||||
{
|
{
|
||||||
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
||||||
@ -141,7 +139,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
||||||
{
|
{
|
||||||
Clock = gameplayContainer.GameplayClock
|
Clock = gameplayContainer
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,6 +36,23 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualityCaseSensitivity()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
// empty by default so let's set it..
|
||||||
|
beatmapSetA.Beatmaps.First().Metadata.AudioFile = "audio.mp3";
|
||||||
|
beatmapSetB.Beatmaps.First().Metadata.AudioFile = "audio.mp3";
|
||||||
|
|
||||||
|
addAudioFile(beatmapSetA, "abc", "AuDiO.mP3");
|
||||||
|
addAudioFile(beatmapSetB, "abc", "audio.mp3");
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAudioEqualitySameHash()
|
public void TestAudioEqualitySameHash()
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -30,7 +31,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
public List<Bindable<double>> MutableNonGameplayAdjustments { get; } = new List<Bindable<double>>();
|
public List<Bindable<double>> MutableNonGameplayAdjustments { get; } = new List<Bindable<double>>();
|
||||||
|
|
||||||
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => MutableNonGameplayAdjustments;
|
public override IEnumerable<double> NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value);
|
||||||
|
|
||||||
public TestGameplayClock(IFrameBasedClock underlyingClock)
|
public TestGameplayClock(IFrameBasedClock underlyingClock)
|
||||||
: base(underlyingClock)
|
: base(underlyingClock)
|
||||||
|
@ -128,6 +128,8 @@ namespace osu.Game.Tests.Resources
|
|||||||
|
|
||||||
var rulesetInfo = getRuleset();
|
var rulesetInfo = getRuleset();
|
||||||
|
|
||||||
|
string hash = Guid.NewGuid().ToString().ComputeMD5Hash();
|
||||||
|
|
||||||
yield return new BeatmapInfo
|
yield return new BeatmapInfo
|
||||||
{
|
{
|
||||||
OnlineID = beatmapId,
|
OnlineID = beatmapId,
|
||||||
@ -136,7 +138,8 @@ namespace osu.Game.Tests.Resources
|
|||||||
Length = length,
|
Length = length,
|
||||||
BeatmapSet = beatmapSet,
|
BeatmapSet = beatmapSet,
|
||||||
BPM = bpm,
|
BPM = bpm,
|
||||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
Hash = hash,
|
||||||
|
MD5Hash = hash,
|
||||||
Ruleset = rulesetInfo,
|
Ruleset = rulesetInfo,
|
||||||
Metadata = metadata.DeepClone(),
|
Metadata = metadata.DeepClone(),
|
||||||
Difficulty = new BeatmapDifficulty
|
Difficulty = new BeatmapDifficulty
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -59,15 +57,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
AddStep("reset clock", () => Clock.Seek(0));
|
AddStep("reset clock", () => Clock.Seek(0));
|
||||||
|
|
||||||
AddStep("start clock", Clock.Start);
|
AddStep("start clock", () => Clock.Start());
|
||||||
AddAssert("clock running", () => Clock.IsRunning);
|
AddAssert("clock running", () => Clock.IsRunning);
|
||||||
|
|
||||||
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
|
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
|
||||||
AddUntilStep("clock stops", () => !Clock.IsRunning);
|
AddUntilStep("clock stops", () => !Clock.IsRunning);
|
||||||
|
|
||||||
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
|
AddUntilStep("clock stopped at end", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
|
||||||
|
|
||||||
AddStep("start clock again", Clock.Start);
|
AddStep("start clock again", () => Clock.Start());
|
||||||
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,32 +74,32 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
AddStep("reset clock", () => Clock.Seek(0));
|
AddStep("reset clock", () => Clock.Seek(0));
|
||||||
|
|
||||||
AddStep("stop clock", Clock.Stop);
|
AddStep("stop clock", () => Clock.Stop());
|
||||||
AddAssert("clock stopped", () => !Clock.IsRunning);
|
AddAssert("clock stopped", () => !Clock.IsRunning);
|
||||||
|
|
||||||
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
|
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
|
||||||
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
|
AddAssert("clock stopped at end", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
|
||||||
|
|
||||||
AddStep("start clock again", Clock.Start);
|
AddStep("start clock again", () => Clock.Start());
|
||||||
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestClampWhenSeekOutsideBeatmapBounds()
|
public void TestClampWhenSeekOutsideBeatmapBounds()
|
||||||
{
|
{
|
||||||
AddStep("stop clock", Clock.Stop);
|
AddStep("stop clock", () => Clock.Stop());
|
||||||
|
|
||||||
AddStep("seek before start time", () => Clock.Seek(-1000));
|
AddStep("seek before start time", () => Clock.Seek(-1000));
|
||||||
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
|
AddAssert("time is clamped to 0", () => Clock.CurrentTime, () => Is.EqualTo(0));
|
||||||
|
|
||||||
AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
|
AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
|
||||||
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
|
AddAssert("time is clamped to track length", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
|
||||||
|
|
||||||
AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
|
AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
|
||||||
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
|
AddUntilStep("time is clamped to 0", () => Clock.CurrentTime, () => Is.EqualTo(0));
|
||||||
|
|
||||||
AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
|
AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
|
||||||
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
|
AddUntilStep("time is clamped to track length", () => Clock.CurrentTime, () => Is.EqualTo(Clock.TrackLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -60,17 +60,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
// Forwards
|
// Forwards
|
||||||
AddStep("Seek(0)", () => Clock.Seek(0));
|
AddStep("Seek(0)", () => Clock.Seek(0));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
AddStep("Seek(33)", () => Clock.Seek(33));
|
AddStep("Seek(33)", () => Clock.Seek(33));
|
||||||
AddAssert("Time = 33", () => Clock.CurrentTime == 33);
|
checkTime(33);
|
||||||
AddStep("Seek(89)", () => Clock.Seek(89));
|
AddStep("Seek(89)", () => Clock.Seek(89));
|
||||||
AddAssert("Time = 89", () => Clock.CurrentTime == 89);
|
checkTime(89);
|
||||||
|
|
||||||
// Backwards
|
// Backwards
|
||||||
AddStep("Seek(25)", () => Clock.Seek(25));
|
AddStep("Seek(25)", () => Clock.Seek(25));
|
||||||
AddAssert("Time = 25", () => Clock.CurrentTime == 25);
|
checkTime(25);
|
||||||
AddStep("Seek(0)", () => Clock.Seek(0));
|
AddStep("Seek(0)", () => Clock.Seek(0));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -83,19 +83,19 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
|
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
|
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
|
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
|
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
|
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
|
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
|
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -108,17 +108,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
|
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
|
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
|
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
|
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
|
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
|
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -130,15 +130,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 200", () => Clock.CurrentTime == 200);
|
checkTime(200);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -150,17 +150,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -174,28 +174,28 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(49)", () => Clock.Seek(49));
|
AddStep("Seek(49)", () => Clock.Seek(49));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
|
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(99)", () => Clock.Seek(99));
|
AddStep("Seek(99)", () => Clock.Seek(99));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
|
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 150);
|
checkTime(150);
|
||||||
AddStep("Seek(174)", () => Clock.Seek(174));
|
AddStep("Seek(174)", () => Clock.Seek(174));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(349)", () => Clock.Seek(349));
|
AddStep("Seek(349)", () => Clock.Seek(349));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("Seek(399)", () => Clock.Seek(399));
|
AddStep("Seek(399)", () => Clock.Seek(399));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("Seek(449)", () => Clock.Seek(449));
|
AddStep("Seek(449)", () => Clock.Seek(449));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -208,15 +208,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(450)", () => Clock.Seek(450));
|
AddStep("Seek(450)", () => Clock.Seek(450));
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 150", () => Clock.CurrentTime == 150);
|
checkTime(150);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -229,17 +229,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(450)", () => Clock.Seek(450));
|
AddStep("Seek(450)", () => Clock.Seek(450));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -253,16 +253,16 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(451)", () => Clock.Seek(451));
|
AddStep("Seek(451)", () => Clock.Seek(451));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
|
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
AddStep("Seek(401)", () => Clock.Seek(401));
|
AddStep("Seek(401)", () => Clock.Seek(401));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
|
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -297,9 +297,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime);
|
AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkTime(double expectedTime) => AddAssert($"Current time is {expectedTime}", () => Clock.CurrentTime, () => Is.EqualTo(expectedTime));
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
{
|
{
|
||||||
AddStep("Reset", () => Clock.Seek(0));
|
AddStep("Reset", () => Clock.Seek(0));
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private void pressAndCheckTime(Key key, double expectedTime)
|
private void pressAndCheckTime(Key key, double expectedTime)
|
||||||
{
|
{
|
||||||
AddStep($"press {key}", () => InputManager.Key(key));
|
AddStep($"press {key}", () => InputManager.Key(key));
|
||||||
AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1));
|
AddUntilStep($"time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime).Within(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
|
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
|
||||||
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
|
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
|
||||||
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
|
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
|
||||||
(typeof(GameplayClock), actualComponentsContainer.Dependencies.Get<GameplayClock>())
|
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>())
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
||||||
|
|
||||||
private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time);
|
private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime, () => Is.EqualTo(time));
|
||||||
|
|
||||||
private void checkFrameCount(int frames) =>
|
private void checkFrameCount(int frames) =>
|
||||||
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
|
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames, () => Is.EqualTo(frames));
|
||||||
|
|
||||||
private void checkRate(double rate) =>
|
private void checkRate(double rate) =>
|
||||||
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
|
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate, () => Is.EqualTo(rate));
|
||||||
|
|
||||||
public class ClockConsumingChild : CompositeDrawable
|
public class ClockConsumingChild : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -21,22 +19,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAllSamplesStopDuringSeek()
|
public void TestAllSamplesStopDuringSeek()
|
||||||
{
|
{
|
||||||
DrawableSlider slider = null;
|
DrawableSlider? slider = null;
|
||||||
PoolableSkinnableSample[] samples = null;
|
PoolableSkinnableSample[] samples = null!;
|
||||||
ISamplePlaybackDisabler sampleDisabler = null;
|
ISamplePlaybackDisabler sampleDisabler = null!;
|
||||||
|
|
||||||
AddUntilStep("get variables", () =>
|
AddUntilStep("get variables", () =>
|
||||||
{
|
{
|
||||||
sampleDisabler = Player;
|
sampleDisabler = Player;
|
||||||
slider = Player.ChildrenOfType<DrawableSlider>().MinBy(s => s.HitObject.StartTime);
|
slider = Player.ChildrenOfType<DrawableSlider>().MinBy(s => s.HitObject.StartTime);
|
||||||
samples = slider?.ChildrenOfType<PoolableSkinnableSample>().ToArray();
|
samples = slider.ChildrenOfType<PoolableSkinnableSample>().ToArray();
|
||||||
|
|
||||||
return slider != null;
|
return slider != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for slider sliding then seek", () =>
|
AddUntilStep("wait for slider sliding then seek", () =>
|
||||||
{
|
{
|
||||||
if (!slider.Tracking.Value)
|
if (slider?.Tracking.Value != true)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!samples.Any(s => s.Playing))
|
if (!samples.Any(s => s.Playing))
|
||||||
|
@ -38,8 +38,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||||
|
|
||||||
[Cached]
|
[Cached(typeof(IGameplayClock))]
|
||||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
|
@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
|
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
|
||||||
|
|
||||||
public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
|
public double GameplayClockTime => GameplayClockContainer.CurrentTime;
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
if (!FirstFrameClockTime.HasValue)
|
if (!FirstFrameClockTime.HasValue)
|
||||||
{
|
{
|
||||||
FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
|
FirstFrameClockTime = GameplayClockContainer.CurrentTime;
|
||||||
AddInternal(new OsuSpriteText
|
AddInternal(new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
|
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
AddUntilStep("gameplay has started",
|
AddUntilStep("gameplay has started",
|
||||||
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
|
() => Player.GameplayClockContainer.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -313,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("pause again", () =>
|
AddUntilStep("pause again", () =>
|
||||||
{
|
{
|
||||||
Player.Pause();
|
Player.Pause();
|
||||||
return !Player.GameplayClockContainer.GameplayClock.IsRunning;
|
return !Player.GameplayClockContainer.IsRunning;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("loop is playing", () => getLoop().IsPlaying);
|
AddAssert("loop is playing", () => getLoop().IsPlaying);
|
||||||
@ -378,7 +378,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
|
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
|
||||||
|
|
||||||
private void confirmClockRunning(bool isRunning) =>
|
private void confirmClockRunning(bool isRunning) =>
|
||||||
AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
|
AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.IsRunning == isRunning);
|
||||||
|
|
||||||
protected override bool AllowFail => true;
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
|
@ -56,6 +56,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private readonly ChangelogOverlay changelogOverlay;
|
private readonly ChangelogOverlay changelogOverlay;
|
||||||
|
|
||||||
|
private double savedTrackVolume;
|
||||||
|
private double savedMasterVolume;
|
||||||
|
private bool savedMutedState;
|
||||||
|
|
||||||
public TestScenePlayerLoader()
|
public TestScenePlayerLoader()
|
||||||
{
|
{
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
@ -75,11 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() => player = null);
|
||||||
{
|
|
||||||
player = null;
|
|
||||||
audioManager.Volume.SetDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the input manager child to a new test player loader container instance.
|
/// Sets the input manager child to a new test player loader container instance.
|
||||||
@ -147,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
moveMouse();
|
moveMouse();
|
||||||
return player?.LoadState == LoadState.Ready;
|
return player?.LoadState == LoadState.Ready;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddRepeatStep("move mouse", moveMouse, 20);
|
AddRepeatStep("move mouse", moveMouse, 20);
|
||||||
|
|
||||||
AddAssert("loader still active", () => loader.IsCurrentScreen());
|
AddAssert("loader still active", () => loader.IsCurrentScreen());
|
||||||
@ -154,6 +155,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
void moveMouse()
|
void moveMouse()
|
||||||
{
|
{
|
||||||
|
notificationOverlay.State.Value = Visibility.Hidden;
|
||||||
|
|
||||||
InputManager.MoveMouseTo(
|
InputManager.MoveMouseTo(
|
||||||
loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft
|
loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft
|
||||||
+ (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft)
|
+ (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft)
|
||||||
@ -274,6 +277,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("load player", () => resetPlayer(false, beforeLoad));
|
AddStep("load player", () => resetPlayer(false, beforeLoad));
|
||||||
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
|
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
|
||||||
|
|
||||||
|
saveVolumes();
|
||||||
|
|
||||||
AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == 1);
|
AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == 1);
|
||||||
AddStep("click notification", () =>
|
AddStep("click notification", () =>
|
||||||
{
|
{
|
||||||
@ -287,6 +292,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddAssert("check " + volumeName, assert);
|
AddAssert("check " + volumeName, assert);
|
||||||
|
|
||||||
|
restoreVolumes();
|
||||||
|
|
||||||
AddUntilStep("wait for player load", () => player.IsLoaded);
|
AddUntilStep("wait for player load", () => player.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +301,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public void TestEpilepsyWarning(bool warning)
|
public void TestEpilepsyWarning(bool warning)
|
||||||
{
|
{
|
||||||
|
saveVolumes();
|
||||||
|
setFullVolume();
|
||||||
|
|
||||||
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
||||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
@ -306,6 +316,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25);
|
AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25);
|
||||||
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreVolumes();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEpilepsyWarningEarlyExit()
|
||||||
|
{
|
||||||
|
saveVolumes();
|
||||||
|
setFullVolume();
|
||||||
|
|
||||||
|
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
||||||
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddUntilStep("wait for epilepsy warning", () => getWarning().Alpha > 0);
|
||||||
|
AddUntilStep("warning is shown", () => getWarning().State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("exit early", () => loader.Exit());
|
||||||
|
|
||||||
|
AddUntilStep("warning is hidden", () => getWarning().State.Value == Visibility.Hidden);
|
||||||
|
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
||||||
|
|
||||||
|
restoreVolumes();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning
|
[TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning
|
||||||
@ -336,21 +370,34 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for player load", () => player.IsLoaded);
|
AddUntilStep("wait for player load", () => player.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
private void restoreVolumes()
|
||||||
public void TestEpilepsyWarningEarlyExit()
|
|
||||||
{
|
{
|
||||||
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
AddStep("restore previous volumes", () =>
|
||||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
{
|
||||||
|
audioManager.VolumeTrack.Value = savedTrackVolume;
|
||||||
|
audioManager.Volume.Value = savedMasterVolume;
|
||||||
|
volumeOverlay.IsMuted.Value = savedMutedState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
private void setFullVolume()
|
||||||
|
{
|
||||||
|
AddStep("set volumes to 100%", () =>
|
||||||
|
{
|
||||||
|
audioManager.VolumeTrack.Value = 1;
|
||||||
|
audioManager.Volume.Value = 1;
|
||||||
|
volumeOverlay.IsMuted.Value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
AddUntilStep("wait for epilepsy warning", () => getWarning().Alpha > 0);
|
private void saveVolumes()
|
||||||
AddUntilStep("warning is shown", () => getWarning().State.Value == Visibility.Visible);
|
{
|
||||||
|
AddStep("save previous volumes", () =>
|
||||||
AddStep("exit early", () => loader.Exit());
|
{
|
||||||
|
savedTrackVolume = audioManager.VolumeTrack.Value;
|
||||||
AddUntilStep("warning is hidden", () => getWarning().State.Value == Visibility.Hidden);
|
savedMasterVolume = audioManager.Volume.Value;
|
||||||
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
savedMutedState = volumeOverlay.IsMuted.Value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault();
|
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault();
|
||||||
|
@ -29,8 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||||
|
|
||||||
[Cached]
|
[Cached(typeof(IGameplayClock))]
|
||||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
|
@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||||
|
|
||||||
[Cached]
|
[Cached(typeof(IGameplayClock))]
|
||||||
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
|
||||||
|
|
||||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private double increment;
|
private double increment;
|
||||||
|
|
||||||
private GameplayClockContainer gameplayClockContainer;
|
private GameplayClockContainer gameplayClockContainer;
|
||||||
private GameplayClock gameplayClock;
|
private IFrameBasedClock gameplayClock;
|
||||||
|
|
||||||
private const double skip_time = 6000;
|
private const double skip_time = 6000;
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
|
|
||||||
gameplayClockContainer.Start();
|
gameplayClockContainer.Start();
|
||||||
gameplayClock = gameplayClockContainer.GameplayClock;
|
gameplayClock = gameplayClockContainer;
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
|
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
|
||||||
|
|
||||||
Dependencies.CacheAs(gameplayClockContainer.GameplayClock);
|
Dependencies.CacheAs<IGameplayClock>(gameplayClockContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
|
@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private Player player => Stack.CurrentScreen as Player;
|
private Player player => Stack.CurrentScreen as Player;
|
||||||
|
|
||||||
private double currentFrameStableTime
|
private double currentFrameStableTime
|
||||||
=> player.ChildrenOfType<FrameStabilityContainer>().First().FrameStableClock.CurrentTime;
|
=> player.ChildrenOfType<FrameStabilityContainer>().First().CurrentTime;
|
||||||
|
|
||||||
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestStoryboardNoSkipOutro()
|
public void TestStoryboardNoSkipOutro()
|
||||||
{
|
{
|
||||||
CreateTest();
|
CreateTest();
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddStep("set ShowResults = false", () => showResults = false);
|
AddStep("set ShowResults = false", () => showResults = false);
|
||||||
});
|
});
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
AddWaitStep("wait", 10);
|
AddWaitStep("wait", 10);
|
||||||
AddAssert("no score shown", () => !Player.IsScoreShown);
|
AddAssert("no score shown", () => !Player.IsScoreShown);
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestStoryboardEndsBeforeCompletion()
|
public void TestStoryboardEndsBeforeCompletion()
|
||||||
{
|
{
|
||||||
CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100));
|
CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100));
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden);
|
AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden);
|
||||||
|
|
||||||
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
|
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
87
osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
Normal file
87
osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Overlays.Toolbar;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneToolbarUserButton : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
public TestSceneToolbarUserButton()
|
||||||
|
{
|
||||||
|
Container mainContainer;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
mainContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = Toolbar.HEIGHT,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.DarkRed,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
},
|
||||||
|
new ToolbarUserButton(),
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.DarkRed,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
AddSliderStep("scale", 0.5, 4, 1, scale => mainContainer.Scale = new Vector2((float)scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLoginLogout()
|
||||||
|
{
|
||||||
|
AddStep("Log out", () => ((DummyAPIAccess)API).Logout());
|
||||||
|
AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStates()
|
||||||
|
{
|
||||||
|
AddStep("Log in", () => ((DummyAPIAccess)API).Login("wang", "jang"));
|
||||||
|
|
||||||
|
foreach (var state in Enum.GetValues<APIState>())
|
||||||
|
{
|
||||||
|
AddStep($"Change state to {state}", () => ((DummyAPIAccess)API).SetState(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -432,8 +432,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
var user = playingUsers.Single(u => u.UserID == userId);
|
var user = playingUsers.Single(u => u.UserID == userId);
|
||||||
|
|
||||||
OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull());
|
|
||||||
SpectatorClient.SendEndPlay(userId);
|
SpectatorClient.SendEndPlay(userId);
|
||||||
|
OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull());
|
||||||
|
|
||||||
playingUsers.Remove(user);
|
playingUsers.Remove(user);
|
||||||
});
|
});
|
||||||
@ -451,7 +451,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkPaused(int userId, bool state)
|
private void checkPaused(int userId, bool state)
|
||||||
=> AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
|
=> AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().IsRunning != state);
|
||||||
|
|
||||||
private void checkPausedInstant(int userId, bool state)
|
private void checkPausedInstant(int userId, bool state)
|
||||||
{
|
{
|
||||||
|
@ -671,7 +671,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
|
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
|
||||||
{
|
{
|
||||||
double time = i;
|
double time = i;
|
||||||
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.GameplayClock.CurrentTime > time);
|
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.CurrentTime > time);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);
|
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);
|
||||||
|
@ -134,6 +134,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public void TestSoftDeleteSupport()
|
public void TestSoftDeleteSupport()
|
||||||
{
|
{
|
||||||
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
|
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
|
||||||
|
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
AddStep("create content", () => Child = new ModPresetColumn
|
AddStep("create content", () => Child = new ModPresetColumn
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -153,9 +154,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
foreach (var preset in r.All<ModPreset>())
|
foreach (var preset in r.All<ModPreset>())
|
||||||
preset.DeletePending = true;
|
preset.DeletePending = true;
|
||||||
}));
|
}));
|
||||||
AddUntilStep("no panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 0);
|
AddUntilStep("no panels visible", () => !this.ChildrenOfType<ModPresetPanel>().Any());
|
||||||
|
|
||||||
AddStep("undelete preset", () => Realm.Write(r =>
|
AddStep("select mods from first preset", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() });
|
||||||
|
|
||||||
|
AddStep("undelete presets", () => Realm.Write(r =>
|
||||||
{
|
{
|
||||||
foreach (var preset in r.All<ModPreset>())
|
foreach (var preset in r.All<ModPreset>())
|
||||||
preset.DeletePending = false;
|
preset.DeletePending = false;
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Overlays.Music;
|
using osu.Game.Overlays.Music;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -21,13 +23,25 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene
|
public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly BindableList<Live<BeatmapSetInfo>> beatmapSets = new BindableList<Live<BeatmapSetInfo>>();
|
protected override bool UseFreshStoragePerRun => true;
|
||||||
|
|
||||||
private PlaylistOverlay playlistOverlay;
|
private PlaylistOverlay playlistOverlay = null!;
|
||||||
|
|
||||||
private Live<BeatmapSetInfo> first;
|
private BeatmapManager beatmapManager = null!;
|
||||||
|
|
||||||
private const int item_count = 100;
|
private const int item_count = 20;
|
||||||
|
|
||||||
|
private List<BeatmapSetInfo> beatmapSets => beatmapManager.GetAllUsableBeatmapSets();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||||
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
@ -46,16 +60,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
beatmapSets.Clear();
|
|
||||||
|
|
||||||
for (int i = 0; i < item_count; i++)
|
for (int i = 0; i < item_count; i++)
|
||||||
{
|
{
|
||||||
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged());
|
beatmapManager.Import(TestResources.CreateTestBeatmapSetInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
first = beatmapSets.First();
|
beatmapSets.First().ToLive(Realm);
|
||||||
|
|
||||||
playlistOverlay.BeatmapSets.BindTo(beatmapSets);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -70,9 +80,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any());
|
AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any());
|
||||||
|
|
||||||
|
PlaylistItem firstItem = null!;
|
||||||
|
|
||||||
AddStep("hold 1st item handle", () =>
|
AddStep("hold 1st item handle", () =>
|
||||||
{
|
{
|
||||||
var handle = this.ChildrenOfType<OsuRearrangeableListItem<Live<BeatmapSetInfo>>.PlaylistItemHandle>().First();
|
firstItem = this.ChildrenOfType<PlaylistItem>().First();
|
||||||
|
var handle = firstItem.ChildrenOfType<PlaylistItem.PlaylistItemHandle>().First();
|
||||||
|
|
||||||
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
|
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
|
||||||
InputManager.PressButton(MouseButton.Left);
|
InputManager.PressButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
@ -83,7 +97,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft);
|
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first));
|
AddAssert("first is moved", () => playlistOverlay.ChildrenOfType<Playlist>().Single().Items.ElementAt(4).Value.Equals(firstItem.Model.Value));
|
||||||
|
|
||||||
AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left));
|
AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
}
|
}
|
||||||
@ -101,6 +115,68 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
|
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
|
||||||
.Where(item => item.MatchingFilter)
|
.Where(item => item.MatchingFilter)
|
||||||
.All(item => item.FilterTerms.Any(term => term.ToString().Contains("10"))));
|
.All(item => item.FilterTerms.Any(term => term.ToString().Contains("10"))));
|
||||||
|
|
||||||
|
AddStep("Import new non-matching beatmap", () =>
|
||||||
|
{
|
||||||
|
var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
testBeatmapSetInfo.Beatmaps.Single().Metadata.Title = "no guid";
|
||||||
|
beatmapManager.Import(testBeatmapSetInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh()));
|
||||||
|
|
||||||
|
AddAssert("results filtered correctly",
|
||||||
|
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
|
||||||
|
.Where(item => item.MatchingFilter)
|
||||||
|
.All(item => item.FilterTerms.Any(term => term.ToString().Contains("10"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionFiltering()
|
||||||
|
{
|
||||||
|
NowPlayingCollectionDropdown collectionDropdown() => playlistOverlay.ChildrenOfType<NowPlayingCollectionDropdown>().Single();
|
||||||
|
|
||||||
|
AddStep("Add collection", () =>
|
||||||
|
{
|
||||||
|
Dependencies.Get<RealmAccess>().Write(r =>
|
||||||
|
{
|
||||||
|
r.RemoveAll<BeatmapCollection>();
|
||||||
|
r.Add(new BeatmapCollection("wang"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for dropdown to have new collection", () => collectionDropdown().Items.Count() == 2);
|
||||||
|
|
||||||
|
AddStep("Filter to collection", () =>
|
||||||
|
{
|
||||||
|
collectionDropdown().Current.Value = collectionDropdown().Items.Last();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("No items present", () => !playlistOverlay.ChildrenOfType<PlaylistItem>().Any(i => i.MatchingFilter));
|
||||||
|
|
||||||
|
AddStep("Import new non-matching beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmapManager.Import(TestResources.CreateTestBeatmapSetInfo(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh()));
|
||||||
|
|
||||||
|
AddUntilStep("No items matching", () => !playlistOverlay.ChildrenOfType<PlaylistItem>().Any(i => i.MatchingFilter));
|
||||||
|
|
||||||
|
BeatmapSetInfo collectionAddedBeatmapSet = null!;
|
||||||
|
|
||||||
|
AddStep("Import new matching beatmap", () =>
|
||||||
|
{
|
||||||
|
collectionAddedBeatmapSet = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
beatmapManager.Import(collectionAddedBeatmapSet);
|
||||||
|
Realm.Write(r => r.All<BeatmapCollection>().First().BeatmapMD5Hashes.Add(collectionAddedBeatmapSet.Beatmaps.First().MD5Hash));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh()));
|
||||||
|
|
||||||
|
AddUntilStep("Only matching item",
|
||||||
|
() => playlistOverlay.ChildrenOfType<PlaylistItem>().Where(i => i.MatchingFilter).Select(i => i.Model.ID), () => Is.EquivalentTo(new[] { collectionAddedBeatmapSet.ID }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,8 +199,8 @@ namespace osu.Game.Beatmaps
|
|||||||
Debug.Assert(x.BeatmapSet != null);
|
Debug.Assert(x.BeatmapSet != null);
|
||||||
Debug.Assert(y.BeatmapSet != null);
|
Debug.Assert(y.BeatmapSet != null);
|
||||||
|
|
||||||
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.Metadata))?.File.Hash;
|
string? fileHashX = x.BeatmapSet.GetFile(getFilename(x.Metadata))?.File.Hash;
|
||||||
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.Metadata))?.File.Hash;
|
string? fileHashY = y.BeatmapSet.GetFile(getFilename(y.Metadata))?.File.Hash;
|
||||||
|
|
||||||
return fileHashX == fileHashY;
|
return fileHashX == fileHashY;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps
|
|||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
// AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
|
// AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
|
||||||
var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase));
|
var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null;
|
||||||
string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);
|
string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);
|
||||||
|
|
||||||
// ensure that two difficulties from the set don't point at the same beatmap file.
|
// ensure that two difficulties from the set don't point at the same beatmap file.
|
||||||
|
@ -84,13 +84,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
|
||||||
/// The path returned is relative to the user file storage.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filename">The name of the file to get the storage path of.</param>
|
|
||||||
public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
|
|
||||||
|
|
||||||
public bool Equals(BeatmapSetInfo? other)
|
public bool Equals(BeatmapSetInfo? other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(this, other)) return true;
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
33
osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs
Normal file
33
osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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.Game.Database;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Models;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
public static class BeatmapSetInfoExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
||||||
|
/// The path returned is relative to the user file storage.
|
||||||
|
/// The lookup is case insensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model to operate on.</param>
|
||||||
|
/// <param name="filename">The name of the file to get the storage path of.</param>
|
||||||
|
public static string? GetPathForFile(this IHasRealmFiles model, string filename) => model.GetFile(filename)?.File.GetStoragePath();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the file usage for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
||||||
|
/// The path returned is relative to the user file storage.
|
||||||
|
/// The lookup is case insensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model to operate on.</param>
|
||||||
|
/// <param name="filename">The name of the file to get the storage path of.</param>
|
||||||
|
public static RealmNamedFileUsage? GetFile(this IHasRealmFiles model, string filename) =>
|
||||||
|
model.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
@ -3,16 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum BackgroundSource
|
public enum BackgroundSource
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(SkinSettingsStrings), nameof(SkinSettingsStrings.SkinSectionHeader))]
|
||||||
Skin,
|
Skin,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.BeatmapHeader))]
|
||||||
Beatmap,
|
Beatmap,
|
||||||
|
|
||||||
[Description("Beatmap (with storyboard / video)")]
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.BeatmapWithStoryboard))]
|
||||||
BeatmapWithStoryboard,
|
BeatmapWithStoryboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum DiscordRichPresenceMode
|
public enum DiscordRichPresenceMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.DiscordPresenceOff))]
|
||||||
Off,
|
Off,
|
||||||
|
|
||||||
[Description("Hide identifiable information")]
|
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.HideIdentifiableInformation))]
|
||||||
Limited,
|
Limited,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.DiscordPresenceFull))]
|
||||||
Full
|
Full
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum HUDVisibilityMode
|
public enum HUDVisibilityMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.NeverShowHUD))]
|
||||||
Never,
|
Never,
|
||||||
|
|
||||||
[Description("Hide during gameplay")]
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.HideDuringGameplay))]
|
||||||
HideDuringGameplay,
|
HideDuringGameplay,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.AlwaysShowHUD))]
|
||||||
Always
|
Always
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum RandomSelectAlgorithm
|
public enum RandomSelectAlgorithm
|
||||||
{
|
{
|
||||||
[Description("Never repeat")]
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.NeverRepeat))]
|
||||||
RandomPermutation,
|
RandomPermutation,
|
||||||
|
|
||||||
[Description("True Random")]
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.TrueRandom))]
|
||||||
Random
|
Random
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,23 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum ScalingMode
|
public enum ScalingMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScalingOff))]
|
||||||
Off,
|
Off,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleEverything))]
|
||||||
Everything,
|
Everything,
|
||||||
|
|
||||||
[Description("Excluding overlays")]
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleEverythingExcludingOverlays))]
|
||||||
ExcludeOverlays,
|
ExcludeOverlays,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleGameplay))]
|
||||||
Gameplay,
|
Gameplay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum ScreenshotFormat
|
public enum ScreenshotFormat
|
||||||
{
|
{
|
||||||
[Description("JPG (web-friendly)")]
|
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.Jpg))]
|
||||||
Jpg = 1,
|
Jpg = 1,
|
||||||
|
|
||||||
[Description("PNG (lossless)")]
|
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.Png))]
|
||||||
Png = 2
|
Png = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum SeasonalBackgroundMode
|
public enum SeasonalBackgroundMode
|
||||||
@ -10,16 +13,19 @@ namespace osu.Game.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seasonal backgrounds are shown regardless of season, if at all available.
|
/// Seasonal backgrounds are shown regardless of season, if at all available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.AlwaysSeasonalBackground))]
|
||||||
Always,
|
Always,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seasonal backgrounds are shown only during their corresponding season.
|
/// Seasonal backgrounds are shown only during their corresponding season.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SometimesSeasonalBackground))]
|
||||||
Sometimes,
|
Sometimes,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seasonal backgrounds are never shown.
|
/// Seasonal backgrounds are never shown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.NeverSeasonalBackground))]
|
||||||
Never
|
Never
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
@ -11,8 +12,16 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasRealmFiles
|
public interface IHasRealmFiles
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Available files in this model, with locally filenames.
|
||||||
|
/// When performing lookups, consider using <see cref="BeatmapSetInfoExtensions.GetFile"/> or <see cref="BeatmapSetInfoExtensions.GetPathForFile"/> to do case-insensitive lookups.
|
||||||
|
/// </summary>
|
||||||
IList<RealmNamedFileUsage> Files { get; }
|
IList<RealmNamedFileUsage> Files { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A combined hash representing the model, based on the files it contains.
|
||||||
|
/// Implementation specific.
|
||||||
|
/// </summary>
|
||||||
string Hash { get; set; }
|
string Hash { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -27,27 +25,30 @@ namespace osu.Game.Database
|
|||||||
public class LegacyImportManager : Component
|
public class LegacyImportManager : Component
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SkinManager skins { get; set; }
|
private SkinManager skins { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreManager scores { get; set; }
|
private ScoreManager scores { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
|
||||||
private OsuGame game { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IDialogOverlay dialogOverlay { get; set; }
|
private OsuGame? game { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RealmAccess realmAccess { get; set; }
|
private IDialogOverlay dialogOverlay { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
private DesktopGameHost desktopGameHost { get; set; }
|
private RealmAccess realmAccess { get; set; } = null!;
|
||||||
|
|
||||||
private StableStorage cachedStorage;
|
[Resolved(canBeNull: true)] // canBeNull required while we remain on mono for mobile platforms.
|
||||||
|
private DesktopGameHost? desktopGameHost { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private INotificationOverlay? notifications { get; set; }
|
||||||
|
|
||||||
|
private StableStorage? cachedStorage;
|
||||||
|
|
||||||
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||||
|
|
||||||
@ -98,6 +99,9 @@ namespace osu.Game.Database
|
|||||||
stableStorage = GetCurrentStableStorage();
|
stableStorage = GetCurrentStableStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stableStorage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var importTasks = new List<Task>();
|
var importTasks = new List<Task>();
|
||||||
|
|
||||||
Task beatmapImportTask = Task.CompletedTask;
|
Task beatmapImportTask = Task.CompletedTask;
|
||||||
@ -108,7 +112,14 @@ namespace osu.Game.Database
|
|||||||
importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage));
|
importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
if (content.HasFlagFast(StableContent.Collections))
|
if (content.HasFlagFast(StableContent.Collections))
|
||||||
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStorage(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
{
|
||||||
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess)
|
||||||
|
{
|
||||||
|
// Other legacy importers import via model managers which handle the posting of notifications.
|
||||||
|
// Collections are an exception.
|
||||||
|
PostNotification = n => notifications?.Post(n)
|
||||||
|
}.ImportFromStorage(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
}
|
||||||
|
|
||||||
if (content.HasFlagFast(StableContent.Scores))
|
if (content.HasFlagFast(StableContent.Scores))
|
||||||
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
@ -116,7 +127,7 @@ namespace osu.Game.Database
|
|||||||
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StableStorage GetCurrentStableStorage()
|
public StableStorage? GetCurrentStableStorage()
|
||||||
{
|
{
|
||||||
if (cachedStorage != null)
|
if (cachedStorage != null)
|
||||||
return cachedStorage;
|
return cachedStorage;
|
||||||
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
@ -79,7 +80,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddFile(TModel item, Stream contents, string filename, Realm realm)
|
public void AddFile(TModel item, Stream contents, string filename, Realm realm)
|
||||||
{
|
{
|
||||||
var existing = item.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase));
|
var existing = item.GetFile(filename);
|
||||||
|
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
|
@ -173,6 +173,11 @@ namespace osu.Game.Database
|
|||||||
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
||||||
Filename += realm_extension;
|
Filename += realm_extension;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (!DebugUtils.IsNUnitRunning)
|
||||||
|
applyFilenameSchemaSuffix(ref Filename);
|
||||||
|
#endif
|
||||||
|
|
||||||
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
|
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
|
||||||
|
|
||||||
// Attempt to recover a newer database version if available.
|
// Attempt to recover a newer database version if available.
|
||||||
@ -212,6 +217,51 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Some developers may be annoyed if a newer version migration (ie. caused by testing a pull request)
|
||||||
|
/// cause their test database to be unusable with previous versions.
|
||||||
|
/// To get around this, store development databases against their realm version.
|
||||||
|
/// Note that this means changes made on newer realm versions will disappear.
|
||||||
|
/// </summary>
|
||||||
|
private void applyFilenameSchemaSuffix(ref string filename)
|
||||||
|
{
|
||||||
|
string originalFilename = filename;
|
||||||
|
|
||||||
|
filename = getVersionedFilename(schema_version);
|
||||||
|
|
||||||
|
// First check if the current realm version already exists...
|
||||||
|
if (storage.Exists(filename))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check for a previous version we can use as a base database to migrate from...
|
||||||
|
for (int i = schema_version - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
string previousFilename = getVersionedFilename(i);
|
||||||
|
|
||||||
|
if (storage.Exists(previousFilename))
|
||||||
|
{
|
||||||
|
copyPreviousVersion(previousFilename, filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, check for a non-versioned file exists (aka before this method was added)...
|
||||||
|
if (storage.Exists(originalFilename))
|
||||||
|
copyPreviousVersion(originalFilename, filename);
|
||||||
|
|
||||||
|
void copyPreviousVersion(string previousFilename, string newFilename)
|
||||||
|
{
|
||||||
|
using (var previous = storage.GetStream(previousFilename))
|
||||||
|
using (var current = storage.CreateFileSafely(newFilename))
|
||||||
|
{
|
||||||
|
Logger.Log(@$"Copying previous realm database {previousFilename} to {newFilename} for migration to schema version {schema_version}");
|
||||||
|
previous.CopyTo(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string getVersionedFilename(int version) => originalFilename.Replace(realm_extension, $"_{version}{realm_extension}");
|
||||||
|
}
|
||||||
|
|
||||||
private void attemptRecoverFromFile(string recoveryFilename)
|
private void attemptRecoverFromFile(string recoveryFilename)
|
||||||
{
|
{
|
||||||
Logger.Log($@"Performing recovery from {recoveryFilename}", LoggingTarget.Database);
|
Logger.Log($@"Performing recovery from {recoveryFilename}", LoggingTarget.Database);
|
||||||
|
@ -167,6 +167,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
// If the game goes into a suspended state (ie. debugger attached or backgrounded on a mobile device)
|
||||||
|
// we want to ignore really long periods of no processing.
|
||||||
|
if (updateClock.ElapsedFrameTime > 10000)
|
||||||
|
return;
|
||||||
|
|
||||||
mainContent.Width = Math.Max(mainContent.Width, counters.DrawWidth);
|
mainContent.Width = Math.Max(mainContent.Width, counters.DrawWidth);
|
||||||
|
|
||||||
// Handle the case where the window has become inactive or the user changed the
|
// Handle the case where the window has become inactive or the user changed the
|
||||||
@ -177,15 +182,15 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
// use elapsed frame time rather then FramesPerSecond to better catch stutter frames.
|
// use elapsed frame time rather then FramesPerSecond to better catch stutter frames.
|
||||||
bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms;
|
bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms;
|
||||||
|
|
||||||
// note that we use an elapsed time here of 1 intentionally.
|
const float damp_time = 100;
|
||||||
// this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower.
|
|
||||||
displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 100, 1);
|
displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : damp_time, updateClock.ElapsedFrameTime);
|
||||||
|
|
||||||
if (hasDrawSpike)
|
if (hasDrawSpike)
|
||||||
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
|
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
|
||||||
displayedFpsCount = 1000 / drawClock.ElapsedFrameTime;
|
displayedFpsCount = 1000 / drawClock.ElapsedFrameTime;
|
||||||
else
|
else
|
||||||
displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 100, Time.Elapsed);
|
displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, damp_time, Time.Elapsed);
|
||||||
|
|
||||||
if (Time.Current - lastUpdate > min_time_between_updates)
|
if (Time.Current - lastUpdate > min_time_between_updates)
|
||||||
{
|
{
|
||||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LoadingLayer : LoadingSpinner
|
public class LoadingLayer : LoadingSpinner
|
||||||
{
|
{
|
||||||
|
private readonly bool blockInput;
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
protected Box BackgroundDimLayer { get; }
|
protected Box BackgroundDimLayer { get; }
|
||||||
|
|
||||||
@ -28,9 +30,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dimBackground">Whether the full background area should be dimmed while loading.</param>
|
/// <param name="dimBackground">Whether the full background area should be dimmed while loading.</param>
|
||||||
/// <param name="withBox">Whether the spinner should have a surrounding black box for visibility.</param>
|
/// <param name="withBox">Whether the spinner should have a surrounding black box for visibility.</param>
|
||||||
public LoadingLayer(bool dimBackground = false, bool withBox = true)
|
/// <param name="blockInput">Whether to block input of components behind the loading layer.</param>
|
||||||
|
public LoadingLayer(bool dimBackground = false, bool withBox = true, bool blockInput = true)
|
||||||
: base(withBox)
|
: base(withBox)
|
||||||
{
|
{
|
||||||
|
this.blockInput = blockInput;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Size = new Vector2(1);
|
Size = new Vector2(1);
|
||||||
|
|
||||||
@ -52,6 +56,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override bool Handle(UIEvent e)
|
protected override bool Handle(UIEvent e)
|
||||||
{
|
{
|
||||||
|
if (!blockInput)
|
||||||
|
return false;
|
||||||
|
|
||||||
switch (e)
|
switch (e)
|
||||||
{
|
{
|
||||||
// blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer.
|
// blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer.
|
||||||
@ -83,7 +90,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
MainContents.Size = new Vector2(Math.Clamp(Math.Min(DrawWidth, DrawHeight) * 0.25f, 30, 100));
|
MainContents.Size = new Vector2(Math.Clamp(Math.Min(DrawWidth, DrawHeight) * 0.25f, 20, 100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -15,10 +13,11 @@ namespace osu.Game.Input.Bindings
|
|||||||
{
|
{
|
||||||
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
||||||
{
|
{
|
||||||
private readonly Drawable handler;
|
private readonly Drawable? handler;
|
||||||
private InputManager parentInputManager;
|
|
||||||
|
|
||||||
public GlobalActionContainer(OsuGameBase game)
|
private InputManager? parentInputManager;
|
||||||
|
|
||||||
|
public GlobalActionContainer(OsuGameBase? game)
|
||||||
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
||||||
{
|
{
|
||||||
if (game is IKeyBindingHandler<GlobalAction>)
|
if (game is IKeyBindingHandler<GlobalAction>)
|
||||||
@ -32,7 +31,10 @@ namespace osu.Game.Input.Bindings
|
|||||||
parentInputManager = GetContainingInputManager();
|
parentInputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: Do not change the order of key bindings in this list.
|
||||||
|
// It is used to decide the order of precedence (see note in DatabasedKeyBindingContainer).
|
||||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||||
|
.Concat(OverlayKeyBindings)
|
||||||
.Concat(EditorKeyBindings)
|
.Concat(EditorKeyBindings)
|
||||||
.Concat(InGameKeyBindings)
|
.Concat(InGameKeyBindings)
|
||||||
.Concat(SongSelectKeyBindings)
|
.Concat(SongSelectKeyBindings)
|
||||||
@ -40,25 +42,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(InputKey.F6, GlobalAction.ToggleNowPlaying),
|
|
||||||
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
|
|
||||||
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
|
|
||||||
new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
|
|
||||||
new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
|
||||||
|
|
||||||
new KeyBinding(InputKey.Escape, GlobalAction.Back),
|
|
||||||
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
|
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home),
|
|
||||||
|
|
||||||
new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious),
|
new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious),
|
||||||
new KeyBinding(InputKey.Down, GlobalAction.SelectNext),
|
new KeyBinding(InputKey.Down, GlobalAction.SelectNext),
|
||||||
|
|
||||||
@ -69,7 +52,32 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(InputKey.Enter, GlobalAction.Select),
|
new KeyBinding(InputKey.Enter, GlobalAction.Select),
|
||||||
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
|
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
|
||||||
|
|
||||||
|
new KeyBinding(InputKey.Escape, GlobalAction.Back),
|
||||||
|
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.ToggleProfile),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.R }, GlobalAction.RandomSkin),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.R }, GlobalAction.RandomSkin),
|
||||||
|
|
||||||
|
new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
|
||||||
|
new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> OverlayKeyBindings => new[]
|
||||||
|
{
|
||||||
|
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
|
||||||
|
new KeyBinding(InputKey.F6, GlobalAction.ToggleNowPlaying),
|
||||||
|
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> EditorKeyBindings => new[]
|
public IEnumerable<KeyBinding> EditorKeyBindings => new[]
|
||||||
@ -332,5 +340,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleFPSCounter))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleFPSCounter))]
|
||||||
ToggleFPSDisplay,
|
ToggleFPSDisplay,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleProfile))]
|
||||||
|
ToggleProfile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Input
|
namespace osu.Game.Input
|
||||||
{
|
{
|
||||||
@ -17,18 +18,20 @@ namespace osu.Game.Input
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mouse cursor will be free to move outside the game window.
|
/// The mouse cursor will be free to move outside the game window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(MouseSettingsStrings), nameof(MouseSettingsStrings.NeverConfine))]
|
||||||
Never,
|
Never,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mouse cursor will be locked to the window bounds during gameplay,
|
/// The mouse cursor will be locked to the window bounds during gameplay,
|
||||||
/// but may otherwise move freely.
|
/// but may otherwise move freely.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("During Gameplay")]
|
[LocalisableDescription(typeof(MouseSettingsStrings), nameof(MouseSettingsStrings.ConfineDuringGameplay))]
|
||||||
DuringGameplay,
|
DuringGameplay,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mouse cursor will always be locked to the window bounds while the game has focus.
|
/// The mouse cursor will always be locked to the window bounds while the game has focus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(MouseSettingsStrings), nameof(MouseSettingsStrings.AlwaysConfine))]
|
||||||
Always
|
Always
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,4 +101,4 @@ namespace osu.Game.Localisation
|
|||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,31 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
|
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Hide during gameplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HideDuringGameplay => new TranslatableString(getKey(@"hide_during_gameplay"), @"Hide during gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Always"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AlwaysShowHUD => new TranslatableString(getKey(@"always_show_hud"), @"Always");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverShowHUD => new TranslatableString(getKey(@"never_show_hud"), @"Never");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Standardised"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StandardisedScoreDisplay => new TranslatableString(getKey(@"standardised_score_display"), @"Standardised");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Classic"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ClassicScoreDisplay => new TranslatableString(getKey(@"classic_score_display"), @"Classic");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ToggleNotifications => new TranslatableString(getKey(@"toggle_notifications"), @"Toggle notifications");
|
public static LocalisableString ToggleNotifications => new TranslatableString(getKey(@"toggle_notifications"), @"Toggle notifications");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Toggle profile"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ToggleProfile => new TranslatableString(getKey(@"toggle_profile"), @"Toggle profile");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Pause gameplay"
|
/// "Pause gameplay"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -129,6 +129,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString UseHardwareAcceleration => new TranslatableString(getKey(@"use_hardware_acceleration"), @"Use hardware acceleration");
|
public static LocalisableString UseHardwareAcceleration => new TranslatableString(getKey(@"use_hardware_acceleration"), @"Use hardware acceleration");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "JPG (web-friendly)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Jpg => new TranslatableString(getKey(@"jpg_web_friendly"), @"JPG (web-friendly)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "PNG (lossless)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Png => new TranslatableString(getKey(@"png_lossless"), @"PNG (lossless)");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
|
public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Overlays"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString OverlaysSection => new TranslatableString(getKey(@"overlays_section"), @"Overlays");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Song Select"
|
/// "Song Select"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -29,6 +29,26 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Excluding overlays"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScaleEverythingExcludingOverlays => new TranslatableString(getKey(@"scale_everything_excluding_overlays"), @"Excluding overlays");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Everything"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScaleEverything => new TranslatableString(getKey(@"scale_everything"), @"Everything");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Gameplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScaleGameplay => new TranslatableString(getKey(@"scale_gameplay"), @"Gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Off"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScalingOff => new TranslatableString(getKey(@"scaling_off"), @"Off");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HighPrecisionPlatformWarning => new TranslatableString(getKey(@"high_precision_platform_warning"), @"This setting has known issues on your platform. If you encounter problems, it is recommended to adjust sensitivity externally and keep this disabled for now.");
|
public static LocalisableString HighPrecisionPlatformWarning => new TranslatableString(getKey(@"high_precision_platform_warning"), @"This setting has known issues on your platform. If you encounter problems, it is recommended to adjust sensitivity externally and keep this disabled for now.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Always"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AlwaysConfine => new TranslatableString(getKey(@"always_confine"), @"Always");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "During Gameplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ConfineDuringGameplay => new TranslatableString(getKey(@"confine_during_gameplay"), @"During Gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverConfine => new TranslatableString(getKey(@"never_confine"), @"Never");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
|
public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Hide identifiable information"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HideIdentifiableInformation => new TranslatableString(getKey(@"hide_identifiable_information"), @"Hide identifiable information");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Full"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DiscordPresenceFull => new TranslatableString(getKey(@"discord_presence_full"), @"Full");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Off"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DiscordPresenceOff => new TranslatableString(getKey(@"discord_presence_off"), @"Off");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
|
public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "None"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BorderNone => new TranslatableString(getKey(@"no_borders"), @"None");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Corners"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BorderCorners => new TranslatableString(getKey(@"corner_borders"), @"Corners");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Full"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BorderFull => new TranslatableString(getKey(@"full_borders"), @"Full");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
osu.Game/Localisation/ToolbarStrings.cs
Normal file
24
osu.Game/Localisation/ToolbarStrings.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class ToolbarStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.Toolbar";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Connection interrupted, will try to reconnect..."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AttemptingToReconnect => new TranslatableString(getKey(@"attempting_to_reconnect"), @"Connection interrupted, will try to reconnect...");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Connecting..."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Connecting => new TranslatableString(getKey(@"connecting"), @"Connecting...");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -114,6 +114,46 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
|
public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Beatmap (with storyboard / video)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BeatmapWithStoryboard => new TranslatableString(getKey(@"beatmap_with_storyboard"), @"Beatmap (with storyboard / video)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Always"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AlwaysSeasonalBackground => new TranslatableString(getKey(@"always_seasonal_backgrounds"), @"Always");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverSeasonalBackground => new TranslatableString(getKey(@"never_seasonal_backgrounds"), @"Never");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Sometimes"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SometimesSeasonalBackground => new TranslatableString(getKey(@"sometimes_seasonal_backgrounds"), @"Sometimes");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Sequential"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SequentialHotkeyStyle => new TranslatableString(getKey(@"mods_sequential_hotkeys"), @"Sequential");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Classic"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ClassicHotkeyStyle => new TranslatableString(getKey(@"mods_classic_hotkeys"), @"Classic");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never repeat"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverRepeat => new TranslatableString(getKey(@"never_repeat_random"), @"Never repeat");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "True Random"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TrueRandom => new TranslatableString(getKey(@"true_random"), @"True Random");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,120 +104,39 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int failureCount;
|
private int failureCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main API thread loop, which will continue to run until the game is shut down.
|
||||||
|
/// </summary>
|
||||||
private void run()
|
private void run()
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
switch (State.Value)
|
if (state.Value == APIState.Failing)
|
||||||
{
|
{
|
||||||
case APIState.Failing:
|
// To recover from a failing state, falling through and running the full reconnection process seems safest for now.
|
||||||
//todo: replace this with a ping request.
|
// This could probably be replaced with a ping-style request if we want to avoid the reconnection overheads.
|
||||||
log.Add(@"In a failing state, waiting a bit before we try again...");
|
log.Add($@"{nameof(APIAccess)} is in a failing state, waiting a bit before we try again...");
|
||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsLoggedIn) goto case APIState.Connecting;
|
// Ensure that we have valid credentials.
|
||||||
|
// If not, setting the offline state will allow the game to prompt the user to provide new credentials.
|
||||||
|
if (!HasLogin)
|
||||||
|
{
|
||||||
|
state.Value = APIState.Offline;
|
||||||
|
Thread.Sleep(50);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (queue.Count == 0)
|
Debug.Assert(HasLogin);
|
||||||
{
|
|
||||||
log.Add(@"Queueing a ping request");
|
|
||||||
Queue(new GetUserRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
// Ensure that we are in an online state. If not, attempt a connect.
|
||||||
|
if (state.Value != APIState.Online)
|
||||||
|
{
|
||||||
|
attemptConnect();
|
||||||
|
|
||||||
case APIState.Offline:
|
if (state.Value != APIState.Online)
|
||||||
case APIState.Connecting:
|
continue;
|
||||||
// work to restore a connection...
|
|
||||||
if (!HasLogin)
|
|
||||||
{
|
|
||||||
state.Value = APIState.Offline;
|
|
||||||
Thread.Sleep(50);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Value = APIState.Connecting;
|
|
||||||
|
|
||||||
// save the username at this point, if the user requested for it to be.
|
|
||||||
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
|
||||||
|
|
||||||
if (!authentication.HasValidAccessToken)
|
|
||||||
{
|
|
||||||
LastLoginError = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
authentication.AuthenticateWithLogin(ProvidedUsername, password);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
//todo: this fails even on network-related issues. we should probably handle those differently.
|
|
||||||
LastLoginError = e;
|
|
||||||
log.Add(@"Login failed!");
|
|
||||||
password = null;
|
|
||||||
authentication.Clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var userReq = new GetUserRequest();
|
|
||||||
|
|
||||||
userReq.Failure += ex =>
|
|
||||||
{
|
|
||||||
if (ex is APIException)
|
|
||||||
{
|
|
||||||
LastLoginError = ex;
|
|
||||||
log.Add("Login failed on local user retrieval!");
|
|
||||||
Logout();
|
|
||||||
}
|
|
||||||
else if (ex is WebException webException && webException.Message == @"Unauthorized")
|
|
||||||
{
|
|
||||||
log.Add(@"Login no longer valid");
|
|
||||||
Logout();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
failConnectionProcess();
|
|
||||||
};
|
|
||||||
userReq.Success += u =>
|
|
||||||
{
|
|
||||||
localUser.Value = u;
|
|
||||||
|
|
||||||
// todo: save/pull from settings
|
|
||||||
localUser.Value.Status.Value = new UserStatusOnline();
|
|
||||||
|
|
||||||
failureCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!handleRequest(userReq))
|
|
||||||
{
|
|
||||||
failConnectionProcess();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// getting user's friends is considered part of the connection process.
|
|
||||||
var friendsReq = new GetFriendsRequest();
|
|
||||||
|
|
||||||
friendsReq.Failure += _ => failConnectionProcess();
|
|
||||||
friendsReq.Success += res =>
|
|
||||||
{
|
|
||||||
friends.AddRange(res);
|
|
||||||
|
|
||||||
//we're connected!
|
|
||||||
state.Value = APIState.Online;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!handleRequest(friendsReq))
|
|
||||||
{
|
|
||||||
failConnectionProcess();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
|
||||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
|
||||||
// before actually going online.
|
|
||||||
while (State.Value > APIState.Offline && State.Value < APIState.Online)
|
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hard bail if we can't get a valid access token.
|
// hard bail if we can't get a valid access token.
|
||||||
@ -227,31 +146,132 @@ namespace osu.Game.Online.API
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
processQueuedRequests();
|
||||||
{
|
|
||||||
APIRequest req;
|
|
||||||
|
|
||||||
lock (queue)
|
|
||||||
{
|
|
||||||
if (queue.Count == 0) break;
|
|
||||||
|
|
||||||
req = queue.Dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void failConnectionProcess()
|
/// <summary>
|
||||||
|
/// Dequeue from the queue and run each request synchronously until the queue is empty.
|
||||||
|
/// </summary>
|
||||||
|
private void processQueuedRequests()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
// if something went wrong during the connection process, we want to reset the state (but only if still connecting).
|
APIRequest req;
|
||||||
if (State.Value == APIState.Connecting)
|
|
||||||
state.Value = APIState.Failing;
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count == 0) return;
|
||||||
|
|
||||||
|
req = queue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequest(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// From a non-connected state, perform a full connection flow, obtaining OAuth tokens and populating the local user and friends.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method takes control of <see cref="state"/> and transitions from <see cref="APIState.Connecting"/> to either
|
||||||
|
/// - <see cref="APIState.Online"/> (successful connection)
|
||||||
|
/// - <see cref="APIState.Failing"/> (failed connection but retrying)
|
||||||
|
/// - <see cref="APIState.Offline"/> (failed and can't retry, clear credentials and require user interaction)
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>Whether the connection attempt was successful.</returns>
|
||||||
|
private void attemptConnect()
|
||||||
|
{
|
||||||
|
state.Value = APIState.Connecting;
|
||||||
|
|
||||||
|
if (localUser.IsDefault)
|
||||||
|
{
|
||||||
|
// Show a placeholder user if saved credentials are available.
|
||||||
|
// This is useful for storing local scores and showing a placeholder username after starting the game,
|
||||||
|
// until a valid connection has been established.
|
||||||
|
setLocalUser(new APIUser
|
||||||
|
{
|
||||||
|
Username = ProvidedUsername,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the username at this point, if the user requested for it to be.
|
||||||
|
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||||
|
|
||||||
|
if (!authentication.HasValidAccessToken)
|
||||||
|
{
|
||||||
|
LastLoginError = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authentication.AuthenticateWithLogin(ProvidedUsername, password);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//todo: this fails even on network-related issues. we should probably handle those differently.
|
||||||
|
LastLoginError = e;
|
||||||
|
log.Add($@"Login failed for username {ProvidedUsername} ({LastLoginError.Message})!");
|
||||||
|
|
||||||
|
Logout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userReq = new GetUserRequest();
|
||||||
|
userReq.Failure += ex =>
|
||||||
|
{
|
||||||
|
if (ex is APIException)
|
||||||
|
{
|
||||||
|
LastLoginError = ex;
|
||||||
|
log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!");
|
||||||
|
Logout();
|
||||||
|
}
|
||||||
|
else if (ex is WebException webException && webException.Message == @"Unauthorized")
|
||||||
|
{
|
||||||
|
log.Add(@"Login no longer valid");
|
||||||
|
Logout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.Value = APIState.Failing;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
userReq.Success += user =>
|
||||||
|
{
|
||||||
|
// todo: save/pull from settings
|
||||||
|
user.Status.Value = new UserStatusOnline();
|
||||||
|
|
||||||
|
setLocalUser(user);
|
||||||
|
|
||||||
|
// we're connected!
|
||||||
|
state.Value = APIState.Online;
|
||||||
|
failureCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!handleRequest(userReq))
|
||||||
|
{
|
||||||
|
state.Value = APIState.Failing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var friendsReq = new GetFriendsRequest();
|
||||||
|
friendsReq.Failure += _ => state.Value = APIState.Failing;
|
||||||
|
friendsReq.Success += res => friends.AddRange(res);
|
||||||
|
|
||||||
|
if (!handleRequest(friendsReq))
|
||||||
|
{
|
||||||
|
state.Value = APIState.Failing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||||
|
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||||
|
// before actually going online.
|
||||||
|
while (State.Value == APIState.Connecting && !cancellationToken.IsCancellationRequested)
|
||||||
|
Thread.Sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
public void Perform(APIRequest request)
|
public void Perform(APIRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -327,8 +347,7 @@ namespace osu.Game.Online.API
|
|||||||
if (req.CompletionState != APIRequestCompletionState.Completed)
|
if (req.CompletionState != APIRequestCompletionState.Completed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// we could still be in initialisation, at which point we don't want to say we're Online yet.
|
// Reset failure count if this request succeeded.
|
||||||
if (IsLoggedIn) state.Value = APIState.Online;
|
|
||||||
failureCount = 0;
|
failureCount = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -402,7 +421,7 @@ namespace osu.Game.Online.API
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoggedIn => localUser.Value.Id > 1; // TODO: should this also be true if attempting to connect?
|
public bool IsLoggedIn => State.Value > APIState.Offline;
|
||||||
|
|
||||||
public void Queue(APIRequest request)
|
public void Queue(APIRequest request)
|
||||||
{
|
{
|
||||||
@ -442,7 +461,7 @@ namespace osu.Game.Online.API
|
|||||||
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
localUser.Value = createGuestUser();
|
setLocalUser(createGuestUser());
|
||||||
friends.Clear();
|
friends.Clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -452,6 +471,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private static APIUser createGuestUser() => new GuestUser();
|
private static APIUser createGuestUser() => new GuestUser();
|
||||||
|
|
||||||
|
private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -13,19 +13,16 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The local user.
|
/// The local user.
|
||||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindable<APIUser> LocalUser { get; }
|
IBindable<APIUser> LocalUser { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user's friends.
|
/// The user's friends.
|
||||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindableList<APIUser> Friends { get; }
|
IBindableList<APIUser> Friends { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current user's activity.
|
/// The current user's activity.
|
||||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindable<UserActivity> Activity { get; }
|
IBindable<UserActivity> Activity { get; }
|
||||||
|
|
||||||
|
@ -196,6 +196,9 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||||
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
||||||
|
|
||||||
|
// The server will null out the end date upon the host joining the room, but the null value is never communicated to the client.
|
||||||
|
APIRoom.EndDate.Value = null;
|
||||||
|
|
||||||
Debug.Assert(LocalUser != null);
|
Debug.Assert(LocalUser != null);
|
||||||
addUserToAPIRoom(LocalUser);
|
addUserToAPIRoom(LocalUser);
|
||||||
|
|
||||||
|
@ -1138,6 +1138,13 @@ namespace osu.Game
|
|||||||
mouseDisableButtons.Value = !mouseDisableButtons.Value;
|
mouseDisableButtons.Value = !mouseDisableButtons.Value;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.ToggleProfile:
|
||||||
|
if (userProfile.State.Value == Visibility.Visible)
|
||||||
|
userProfile.Hide();
|
||||||
|
else
|
||||||
|
ShowUser(API.LocalUser.Value);
|
||||||
|
return true;
|
||||||
|
|
||||||
case GlobalAction.RandomSkin:
|
case GlobalAction.RandomSkin:
|
||||||
// Don't allow random skin selection while in the skin editor.
|
// Don't allow random skin selection while in the skin editor.
|
||||||
// This is mainly to stop many "osu! default (modified)" skins being created via the SkinManager.EnsureMutableSkin() path.
|
// This is mainly to stop many "osu! default (modified)" skins being created via the SkinManager.EnsureMutableSkin() path.
|
||||||
|
@ -104,11 +104,11 @@ namespace osu.Game.Overlays
|
|||||||
filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged());
|
filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged());
|
||||||
|
|
||||||
apiUser = api.LocalUser.GetBoundCopy();
|
apiUser = api.LocalUser.GetBoundCopy();
|
||||||
apiUser.BindValueChanged(_ =>
|
apiUser.BindValueChanged(_ => Schedule(() =>
|
||||||
{
|
{
|
||||||
if (api.IsLoggedIn)
|
if (api.IsLoggedIn)
|
||||||
addContentToResultsArea(Drawable.Empty());
|
addContentToResultsArea(Drawable.Empty());
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowWithSearch(string query)
|
public void ShowWithSearch(string query)
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using Markdig.Syntax;
|
using Markdig.Syntax;
|
||||||
using Markdig.Syntax.Inlines;
|
|
||||||
using osu.Framework.Graphics.Containers.Markdown;
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
using osu.Game.Graphics.Containers.Markdown;
|
using osu.Game.Graphics.Containers.Markdown;
|
||||||
|
|
||||||
@ -12,16 +11,8 @@ namespace osu.Game.Overlays.Comments
|
|||||||
{
|
{
|
||||||
public class CommentMarkdownContainer : OsuMarkdownContainer
|
public class CommentMarkdownContainer : OsuMarkdownContainer
|
||||||
{
|
{
|
||||||
public override MarkdownTextFlowContainer CreateTextFlow() => new CommentMarkdownTextFlowContainer();
|
|
||||||
|
|
||||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock);
|
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock);
|
||||||
|
|
||||||
private class CommentMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
|
||||||
{
|
|
||||||
// Don't render image in comment for now
|
|
||||||
protected override void AddImage(LinkInline linkInline) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CommentMarkdownHeading : OsuMarkdownHeading
|
private class CommentMarkdownHeading : OsuMarkdownHeading
|
||||||
{
|
{
|
||||||
public CommentMarkdownHeading(HeadingBlock headingBlock)
|
public CommentMarkdownHeading(HeadingBlock headingBlock)
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -109,7 +110,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
TextAnchor = Anchor.TopCentre,
|
TextAnchor = Anchor.TopCentre,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
|
Text = state.NewValue == APIState.Failing ? ToolbarStrings.AttemptingToReconnect : ToolbarStrings.Connecting,
|
||||||
Margin = new MarginPadding { Top = 10, Bottom = 10 },
|
Margin = new MarginPadding { Top = 10, Bottom = 10 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods.Input
|
namespace osu.Game.Overlays.Mods.Input
|
||||||
{
|
{
|
||||||
@ -15,6 +17,7 @@ namespace osu.Game.Overlays.Mods.Input
|
|||||||
/// Individual letters in a row trigger the mods in a sequential fashion.
|
/// Individual letters in a row trigger the mods in a sequential fashion.
|
||||||
/// Uses <see cref="SequentialModHotkeyHandler"/>.
|
/// Uses <see cref="SequentialModHotkeyHandler"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SequentialHotkeyStyle))]
|
||||||
Sequential,
|
Sequential,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -22,6 +25,7 @@ namespace osu.Game.Overlays.Mods.Input
|
|||||||
/// One keybinding can toggle between what used to be <see cref="MultiMod"/>s on stable,
|
/// One keybinding can toggle between what used to be <see cref="MultiMod"/>s on stable,
|
||||||
/// and some mods in a column may not have any hotkeys at all.
|
/// and some mods in a column may not have any hotkeys at all.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.ClassicHotkeyStyle))]
|
||||||
Classic
|
Classic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private IModHotkeyHandler hotkeyHandler = null!;
|
private IModHotkeyHandler hotkeyHandler = null!;
|
||||||
|
|
||||||
private Task? latestLoadTask;
|
private Task? latestLoadTask;
|
||||||
internal bool ItemsLoaded => latestLoadTask == null;
|
internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true;
|
||||||
|
|
||||||
public ModColumn(ModType modType, bool allowIncompatibleSelection)
|
public ModColumn(ModType modType, bool allowIncompatibleSelection)
|
||||||
{
|
{
|
||||||
@ -132,18 +132,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
var panels = availableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = Vector2.Zero));
|
var panels = availableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = Vector2.Zero));
|
||||||
|
|
||||||
Task? loadTask;
|
latestLoadTask = LoadComponentsAsync(panels, loaded =>
|
||||||
|
|
||||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
|
||||||
{
|
{
|
||||||
ItemsFlow.ChildrenEnumerable = loaded;
|
ItemsFlow.ChildrenEnumerable = loaded;
|
||||||
updateState();
|
updateState();
|
||||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||||
loadTask.ContinueWith(_ =>
|
|
||||||
{
|
|
||||||
if (loadTask == latestLoadTask)
|
|
||||||
latestLoadTask = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -50,45 +50,41 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
presetSubscription?.Dispose();
|
presetSubscription?.Dispose();
|
||||||
presetSubscription = realm.RegisterForNotifications(r =>
|
presetSubscription = realm.RegisterForNotifications(r =>
|
||||||
r.All<ModPreset>()
|
r.All<ModPreset>()
|
||||||
.Filter($"{nameof(ModPreset.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $0"
|
.Filter($"{nameof(ModPreset.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $0"
|
||||||
+ $" && {nameof(ModPreset.DeletePending)} == false", ruleset.Value.ShortName)
|
+ $" && {nameof(ModPreset.DeletePending)} == false", ruleset.Value.ShortName)
|
||||||
.OrderBy(preset => preset.Name),
|
.OrderBy(preset => preset.Name), asyncLoadPanels);
|
||||||
(presets, _, _) => asyncLoadPanels(presets));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource? cancellationTokenSource;
|
private CancellationTokenSource? cancellationTokenSource;
|
||||||
|
|
||||||
private Task? latestLoadTask;
|
private Task? latestLoadTask;
|
||||||
internal bool ItemsLoaded => latestLoadTask == null;
|
internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true;
|
||||||
|
|
||||||
private void asyncLoadPanels(IReadOnlyList<ModPreset> presets)
|
private void asyncLoadPanels(IRealmCollection<ModPreset> presets, ChangeSet changes, Exception error)
|
||||||
{
|
{
|
||||||
cancellationTokenSource?.Cancel();
|
cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
if (!presets.Any())
|
if (!presets.Any())
|
||||||
{
|
{
|
||||||
ItemsFlow.RemoveAll(panel => panel is ModPresetPanel);
|
removeAndDisposePresetPanels();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var panels = presets.Select(preset => new ModPresetPanel(preset.ToLive(realm))
|
latestLoadTask = LoadComponentsAsync(presets.Select(p => new ModPresetPanel(p.ToLive(realm))
|
||||||
{
|
{
|
||||||
Shear = Vector2.Zero
|
Shear = Vector2.Zero
|
||||||
});
|
}), loaded =>
|
||||||
|
|
||||||
Task? loadTask;
|
|
||||||
|
|
||||||
latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded =>
|
|
||||||
{
|
{
|
||||||
ItemsFlow.RemoveAll(panel => panel is ModPresetPanel);
|
removeAndDisposePresetPanels();
|
||||||
ItemsFlow.AddRange(loaded);
|
ItemsFlow.AddRange(loaded);
|
||||||
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
}, (cancellationTokenSource = new CancellationTokenSource()).Token);
|
||||||
loadTask.ContinueWith(_ =>
|
|
||||||
|
void removeAndDisposePresetPanels()
|
||||||
{
|
{
|
||||||
if (loadTask == latestLoadTask)
|
foreach (var panel in ItemsFlow.OfType<ModPresetPanel>().ToArray())
|
||||||
latestLoadTask = null;
|
panel.RemoveAndDisposeImmediately();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -17,10 +15,12 @@ namespace osu.Game.Overlays.Music
|
|||||||
{
|
{
|
||||||
public class Playlist : OsuRearrangeableListContainer<Live<BeatmapSetInfo>>
|
public class Playlist : OsuRearrangeableListContainer<Live<BeatmapSetInfo>>
|
||||||
{
|
{
|
||||||
public Action<Live<BeatmapSetInfo>> RequestSelection;
|
public Action<Live<BeatmapSetInfo>>? RequestSelection;
|
||||||
|
|
||||||
public readonly Bindable<Live<BeatmapSetInfo>> SelectedSet = new Bindable<Live<BeatmapSetInfo>>();
|
public readonly Bindable<Live<BeatmapSetInfo>> SelectedSet = new Bindable<Live<BeatmapSetInfo>>();
|
||||||
|
|
||||||
|
private FilterCriteria currentCriteria = new FilterCriteria();
|
||||||
|
|
||||||
public new MarginPadding Padding
|
public new MarginPadding Padding
|
||||||
{
|
{
|
||||||
get => base.Padding;
|
get => base.Padding;
|
||||||
@ -31,26 +31,22 @@ namespace osu.Game.Overlays.Music
|
|||||||
{
|
{
|
||||||
var items = (SearchContainer<RearrangeableListItem<Live<BeatmapSetInfo>>>)ListContainer;
|
var items = (SearchContainer<RearrangeableListItem<Live<BeatmapSetInfo>>>)ListContainer;
|
||||||
|
|
||||||
string[] currentCollectionHashes = criteria.Collection?.PerformRead(c => c.BeatmapMD5Hashes.ToArray());
|
string[]? currentCollectionHashes = criteria.Collection?.PerformRead(c => c.BeatmapMD5Hashes.ToArray());
|
||||||
|
|
||||||
foreach (var item in items.OfType<PlaylistItem>())
|
foreach (var item in items.OfType<PlaylistItem>())
|
||||||
{
|
{
|
||||||
if (currentCollectionHashes == null)
|
item.InSelectedCollection = currentCollectionHashes == null || item.Model.Value.Beatmaps.Select(b => b.MD5Hash).Any(currentCollectionHashes.Contains);
|
||||||
item.InSelectedCollection = true;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.InSelectedCollection = item.Model.Value.Beatmaps.Select(b => b.MD5Hash)
|
|
||||||
.Any(currentCollectionHashes.Contains);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items.SearchTerm = criteria.SearchText;
|
items.SearchTerm = criteria.SearchText;
|
||||||
|
currentCriteria = criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Live<BeatmapSetInfo> FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter);
|
public Live<BeatmapSetInfo>? FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter);
|
||||||
|
|
||||||
protected override OsuRearrangeableListItem<Live<BeatmapSetInfo>> CreateOsuDrawable(Live<BeatmapSetInfo> item) => new PlaylistItem(item)
|
protected override OsuRearrangeableListItem<Live<BeatmapSetInfo>> CreateOsuDrawable(Live<BeatmapSetInfo> item) => new PlaylistItem(item)
|
||||||
{
|
{
|
||||||
|
InSelectedCollection = currentCriteria.Collection?.PerformRead(c => item.Value.Beatmaps.Select(b => b.MD5Hash).Any(c.BeatmapMD5Hashes.Contains)) != false,
|
||||||
SelectedSet = { BindTarget = SelectedSet },
|
SelectedSet = { BindTarget = SelectedSet },
|
||||||
RequestSelection = set => RequestSelection?.Invoke(set)
|
RequestSelection = set => RequestSelection?.Invoke(set)
|
||||||
};
|
};
|
||||||
|
@ -26,8 +26,6 @@ namespace osu.Game.Overlays.Music
|
|||||||
private const float transition_duration = 600;
|
private const float transition_duration = 600;
|
||||||
private const float playlist_height = 510;
|
private const float playlist_height = 510;
|
||||||
|
|
||||||
public IBindableList<Live<BeatmapSetInfo>> BeatmapSets => beatmapSets;
|
|
||||||
|
|
||||||
private readonly BindableList<Live<BeatmapSetInfo>> beatmapSets = new BindableList<Live<BeatmapSetInfo>>();
|
private readonly BindableList<Live<BeatmapSetInfo>> beatmapSets = new BindableList<Live<BeatmapSetInfo>>();
|
||||||
|
|
||||||
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||||
@ -104,9 +102,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
// tests might bind externally, in which case we don't want to involve realm.
|
beatmapSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
|
||||||
if (beatmapSets.Count == 0)
|
|
||||||
beatmapSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
|
|
||||||
|
|
||||||
list.Items.BindTo(beatmapSets);
|
list.Items.BindTo(beatmapSets);
|
||||||
beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true);
|
beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -23,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
public GlobalKeyBindingsSection(GlobalActionContainer manager)
|
public GlobalKeyBindingsSection(GlobalActionContainer manager)
|
||||||
{
|
{
|
||||||
Add(new DefaultBindingsSubsection(manager));
|
Add(new DefaultBindingsSubsection(manager));
|
||||||
|
Add(new OverlayBindingsSubsection(manager));
|
||||||
Add(new AudioControlKeyBindingsSubsection(manager));
|
Add(new AudioControlKeyBindingsSubsection(manager));
|
||||||
Add(new SongSelectKeyBindingSubsection(manager));
|
Add(new SongSelectKeyBindingSubsection(manager));
|
||||||
Add(new InGameKeyBindingsSubsection(manager));
|
Add(new InGameKeyBindingsSubsection(manager));
|
||||||
@ -40,6 +39,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class OverlayBindingsSubsection : KeyBindingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => InputSettingsStrings.OverlaysSection;
|
||||||
|
|
||||||
|
public OverlayBindingsSubsection(GlobalActionContainer manager)
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
Defaults = manager.OverlayKeyBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
|
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
|
protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
|
||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -20,59 +22,103 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
{
|
{
|
||||||
public class ToolbarUserButton : ToolbarOverlayToggleButton
|
public class ToolbarUserButton : ToolbarOverlayToggleButton
|
||||||
{
|
{
|
||||||
private readonly UpdateableAvatar avatar;
|
private UpdateableAvatar avatar = null!;
|
||||||
|
|
||||||
[Resolved]
|
private IBindable<APIUser> localUser = null!;
|
||||||
private IAPIProvider api { get; set; }
|
|
||||||
|
|
||||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
private LoadingSpinner spinner = null!;
|
||||||
|
|
||||||
|
private SpriteIcon failingIcon = null!;
|
||||||
|
|
||||||
|
private IBindable<APIState> apiState = null!;
|
||||||
|
|
||||||
public ToolbarUserButton()
|
public ToolbarUserButton()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.X;
|
AutoSizeAxes = Axes.X;
|
||||||
|
}
|
||||||
|
|
||||||
DrawableText.Font = OsuFont.GetFont(italics: true);
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours, IAPIProvider api, LoginOverlay? login)
|
||||||
|
{
|
||||||
Add(new OpaqueBackground { Depth = 1 });
|
Add(new OpaqueBackground { Depth = 1 });
|
||||||
|
|
||||||
Flow.Add(avatar = new UpdateableAvatar(isInteractive: false)
|
Flow.Add(new Container
|
||||||
{
|
{
|
||||||
Masking = true,
|
Masking = true,
|
||||||
|
CornerRadius = 4,
|
||||||
Size = new Vector2(32),
|
Size = new Vector2(32),
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
CornerRadius = 4,
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Shadow,
|
Type = EdgeEffectType.Shadow,
|
||||||
Radius = 4,
|
Radius = 4,
|
||||||
Colour = Color4.Black.Opacity(0.1f),
|
Colour = Color4.Black.Opacity(0.1f),
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
avatar = new UpdateableAvatar(isInteractive: false)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
spinner = new LoadingLayer(dimBackground: true, withBox: false, blockInput: false)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
failingIcon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0,
|
||||||
|
Size = new Vector2(0.3f),
|
||||||
|
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.YellowLight,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
apiState = api.State.GetBoundCopy();
|
||||||
private void load(LoginOverlay login)
|
|
||||||
{
|
|
||||||
apiState.BindTo(api.State);
|
|
||||||
apiState.BindValueChanged(onlineStateChanged, true);
|
apiState.BindValueChanged(onlineStateChanged, true);
|
||||||
|
|
||||||
|
localUser = api.LocalUser.GetBoundCopy();
|
||||||
|
localUser.BindValueChanged(userChanged, true);
|
||||||
|
|
||||||
StateContainer = login;
|
StateContainer = login;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void userChanged(ValueChangedEvent<APIUser> user) => Schedule(() =>
|
||||||
|
{
|
||||||
|
Text = user.NewValue.Username;
|
||||||
|
avatar.User = user.NewValue;
|
||||||
|
});
|
||||||
|
|
||||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
failingIcon.FadeTo(state.NewValue == APIState.Failing ? 1 : 0, 200, Easing.OutQuint);
|
||||||
|
|
||||||
switch (state.NewValue)
|
switch (state.NewValue)
|
||||||
{
|
{
|
||||||
default:
|
case APIState.Connecting:
|
||||||
Text = UsersStrings.AnonymousUsername;
|
TooltipText = ToolbarStrings.Connecting;
|
||||||
avatar.User = new APIUser();
|
spinner.Show();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case APIState.Online:
|
case APIState.Failing:
|
||||||
Text = api.LocalUser.Value.Username;
|
TooltipText = ToolbarStrings.AttemptingToReconnect;
|
||||||
avatar.User = api.LocalUser.Value;
|
spinner.Show();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case APIState.Offline:
|
||||||
|
case APIState.Online:
|
||||||
|
TooltipText = string.Empty;
|
||||||
|
spinner.Hide();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.IO.FileAbstraction;
|
using osu.Game.IO.FileAbstraction;
|
||||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Checks
|
namespace osu.Game.Rulesets.Edit.Checks
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.5;
|
||||||
|
|
||||||
public override bool ValidForMultiplayer => false;
|
public override bool ValidForMultiplayer => false;
|
||||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Hammer;
|
public override IconUsage? Icon => FontAwesome.Solid.Hammer;
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1.0;
|
public override double ScoreMultiplier => 0.5;
|
||||||
|
|
||||||
public override bool RequiresConfiguration => true;
|
public override bool RequiresConfiguration => true;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override string Acronym => "RX";
|
public override string Acronym => "RX";
|
||||||
public override IconUsage? Icon => OsuIcon.ModRelax;
|
public override IconUsage? Icon => OsuIcon.ModRelax;
|
||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Scoring
|
namespace osu.Game.Rulesets.Scoring
|
||||||
{
|
{
|
||||||
@ -636,7 +638,10 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
public enum ScoringMode
|
public enum ScoringMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.StandardisedScoreDisplay))]
|
||||||
Standardised,
|
Standardised,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.ClassicScoreDisplay))]
|
||||||
Classic
|
Classic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
public override IFrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock;
|
public override IFrameStableClock FrameStableClock => frameStabilityContainer;
|
||||||
|
|
||||||
private bool frameStablePlayback = true;
|
private bool frameStablePlayback = true;
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
@ -20,9 +20,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// A container which consumes a parent gameplay clock and standardises frame counts for children.
|
/// A container which consumes a parent gameplay clock and standardises frame counts for children.
|
||||||
/// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
|
/// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FrameStabilityContainer : Container, IHasReplayHandler
|
[Cached(typeof(IGameplayClock))]
|
||||||
|
[Cached(typeof(IFrameStableClock))]
|
||||||
|
public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock
|
||||||
{
|
{
|
||||||
private readonly double gameplayStartTime;
|
public ReplayInputHandler? ReplayInputHandler { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time.
|
/// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time.
|
||||||
@ -32,28 +34,35 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to enable frame-stable playback.
|
/// Whether to enable frame-stable playback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool FrameStablePlayback = true;
|
internal bool FrameStablePlayback { get; set; } = true;
|
||||||
|
|
||||||
public IFrameStableClock FrameStableClock => frameStableClock;
|
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid;
|
||||||
|
|
||||||
[Cached(typeof(GameplayClock))]
|
private readonly Bindable<bool> isCatchingUp = new Bindable<bool>();
|
||||||
private readonly FrameStabilityClock frameStableClock;
|
|
||||||
|
|
||||||
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
private readonly Bindable<bool> waitingOnFrames = new Bindable<bool>();
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
frameStableClock = new FrameStabilityClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
private readonly double gameplayStartTime;
|
||||||
|
|
||||||
this.gameplayStartTime = gameplayStartTime;
|
private IGameplayClock? parentGameplayClock;
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A clock which is used as reference for time, rate and running state.
|
||||||
|
/// </summary>
|
||||||
|
private IClock referenceClock = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A local manual clock which tracks the reference clock.
|
||||||
|
/// Values are transferred from <see cref="referenceClock"/> each update call.
|
||||||
|
/// </summary>
|
||||||
private readonly ManualClock manualClock;
|
private readonly ManualClock manualClock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main framed clock which has stability applied to it.
|
||||||
|
/// This gets exposed to children as an <see cref="IGameplayClock"/>.
|
||||||
|
/// </summary>
|
||||||
private readonly FramedClock framedClock;
|
private readonly FramedClock framedClock;
|
||||||
|
|
||||||
private IFrameBasedClock parentGameplayClock;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current direction of playback to be exposed to frame stable children.
|
/// The current direction of playback to be exposed to frame stable children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -62,32 +71,34 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private int direction = 1;
|
private int direction = 1;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
|
||||||
private void load(GameplayClock clock)
|
|
||||||
{
|
|
||||||
if (clock != null)
|
|
||||||
{
|
|
||||||
parentGameplayClock = frameStableClock.ParentGameplayClock = clock;
|
|
||||||
frameStableClock.IsPaused.BindTo(clock.IsPaused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
setClock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlaybackState state;
|
private PlaybackState state;
|
||||||
|
|
||||||
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid;
|
|
||||||
|
|
||||||
private bool hasReplayAttached => ReplayInputHandler != null;
|
private bool hasReplayAttached => ReplayInputHandler != null;
|
||||||
|
|
||||||
private const double sixty_frame_time = 1000.0 / 60;
|
|
||||||
|
|
||||||
private bool firstConsumption = true;
|
private bool firstConsumption = true;
|
||||||
|
|
||||||
|
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
framedClock = new FramedClock(manualClock = new ManualClock());
|
||||||
|
|
||||||
|
this.gameplayStartTime = gameplayStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IGameplayClock? gameplayClock)
|
||||||
|
{
|
||||||
|
if (gameplayClock != null)
|
||||||
|
{
|
||||||
|
parentGameplayClock = gameplayClock;
|
||||||
|
IsPaused.BindTo(parentGameplayClock.IsPaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceClock = gameplayClock ?? Clock;
|
||||||
|
Clock = this;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool UpdateSubTree()
|
public override bool UpdateSubTree()
|
||||||
{
|
{
|
||||||
int loops = MaxCatchUpFrames;
|
int loops = MaxCatchUpFrames;
|
||||||
@ -110,12 +121,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private void updateClock()
|
private void updateClock()
|
||||||
{
|
{
|
||||||
if (frameStableClock.WaitingOnFrames.Value)
|
if (waitingOnFrames.Value)
|
||||||
{
|
{
|
||||||
// if waiting on frames, run one update loop to determine if frames have arrived.
|
// if waiting on frames, run one update loop to determine if frames have arrived.
|
||||||
state = PlaybackState.Valid;
|
state = PlaybackState.Valid;
|
||||||
}
|
}
|
||||||
else if (frameStableClock.IsPaused.Value)
|
else if (IsPaused.Value)
|
||||||
{
|
{
|
||||||
// time should not advance while paused, nor should anything run.
|
// time should not advance while paused, nor should anything run.
|
||||||
state = PlaybackState.NotValid;
|
state = PlaybackState.NotValid;
|
||||||
@ -126,10 +137,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
state = PlaybackState.Valid;
|
state = PlaybackState.Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentGameplayClock == null)
|
double proposedTime = referenceClock.CurrentTime;
|
||||||
setClock(); // LoadComplete may not be run yet, but we still want the clock.
|
|
||||||
|
|
||||||
double proposedTime = parentGameplayClock.CurrentTime;
|
|
||||||
|
|
||||||
if (FrameStablePlayback)
|
if (FrameStablePlayback)
|
||||||
// if we require frame stability, the proposed time will be adjusted to move at most one known
|
// if we require frame stability, the proposed time will be adjusted to move at most one known
|
||||||
@ -149,14 +157,14 @@ namespace osu.Game.Rulesets.UI
|
|||||||
if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime)
|
if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime)
|
||||||
direction = proposedTime >= manualClock.CurrentTime ? 1 : -1;
|
direction = proposedTime >= manualClock.CurrentTime ? 1 : -1;
|
||||||
|
|
||||||
double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime);
|
double timeBehind = Math.Abs(proposedTime - referenceClock.CurrentTime);
|
||||||
|
|
||||||
frameStableClock.IsCatchingUp.Value = timeBehind > 200;
|
isCatchingUp.Value = timeBehind > 200;
|
||||||
frameStableClock.WaitingOnFrames.Value = state == PlaybackState.NotValid;
|
waitingOnFrames.Value = state == PlaybackState.NotValid;
|
||||||
|
|
||||||
manualClock.CurrentTime = proposedTime;
|
manualClock.CurrentTime = proposedTime;
|
||||||
manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction;
|
manualClock.Rate = Math.Abs(referenceClock.Rate) * direction;
|
||||||
manualClock.IsRunning = parentGameplayClock.IsRunning;
|
manualClock.IsRunning = referenceClock.IsRunning;
|
||||||
|
|
||||||
// determine whether catch-up is required.
|
// determine whether catch-up is required.
|
||||||
if (state == PlaybackState.Valid && timeBehind > 0)
|
if (state == PlaybackState.Valid && timeBehind > 0)
|
||||||
@ -174,6 +182,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <returns>Whether playback is still valid.</returns>
|
/// <returns>Whether playback is still valid.</returns>
|
||||||
private bool updateReplay(ref double proposedTime)
|
private bool updateReplay(ref double proposedTime)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(ReplayInputHandler != null);
|
||||||
|
|
||||||
double? newTime;
|
double? newTime;
|
||||||
|
|
||||||
if (FrameStablePlayback)
|
if (FrameStablePlayback)
|
||||||
@ -210,6 +220,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <param name="proposedTime">The time which is to be displayed.</param>
|
/// <param name="proposedTime">The time which is to be displayed.</param>
|
||||||
private void applyFrameStability(ref double proposedTime)
|
private void applyFrameStability(ref double proposedTime)
|
||||||
{
|
{
|
||||||
|
const double sixty_frame_time = 1000.0 / 60;
|
||||||
|
|
||||||
if (firstConsumption)
|
if (firstConsumption)
|
||||||
{
|
{
|
||||||
// On the first update, frame-stability seeking would result in unexpected/unwanted behaviour.
|
// On the first update, frame-stability seeking would result in unexpected/unwanted behaviour.
|
||||||
@ -233,20 +245,54 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setClock()
|
#region Delegation of IGameplayClock
|
||||||
|
|
||||||
|
public IBindable<bool> IsPaused { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public double CurrentTime => framedClock.CurrentTime;
|
||||||
|
|
||||||
|
public double Rate => framedClock.Rate;
|
||||||
|
|
||||||
|
public bool IsRunning => framedClock.IsRunning;
|
||||||
|
|
||||||
|
public void ProcessFrame() { }
|
||||||
|
|
||||||
|
public double ElapsedFrameTime => framedClock.ElapsedFrameTime;
|
||||||
|
|
||||||
|
public double FramesPerSecond => framedClock.FramesPerSecond;
|
||||||
|
|
||||||
|
public FrameTimeInfo TimeInfo => framedClock.TimeInfo;
|
||||||
|
|
||||||
|
public double TrueGameplayRate
|
||||||
{
|
{
|
||||||
if (parentGameplayClock == null)
|
get
|
||||||
{
|
{
|
||||||
// in case a parent gameplay clock isn't available, just use the parent clock.
|
double baseRate = Rate;
|
||||||
parentGameplayClock ??= Clock;
|
|
||||||
}
|
foreach (double adjustment in NonGameplayAdjustments)
|
||||||
else
|
{
|
||||||
{
|
if (Precision.AlmostEquals(adjustment, 0))
|
||||||
Clock = frameStableClock;
|
return 0;
|
||||||
|
|
||||||
|
baseRate /= adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseRate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplayInputHandler ReplayInputHandler { get; set; }
|
public double? StartTime => parentGameplayClock?.StartTime;
|
||||||
|
|
||||||
|
public IEnumerable<double> NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<double>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delegation of IFrameStableClock
|
||||||
|
|
||||||
|
IBindable<bool> IFrameStableClock.IsCatchingUp => isCatchingUp;
|
||||||
|
IBindable<bool> IFrameStableClock.WaitingOnFrames => waitingOnFrames;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private enum PlaybackState
|
private enum PlaybackState
|
||||||
{
|
{
|
||||||
@ -266,25 +312,5 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Valid
|
Valid
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FrameStabilityClock : GameplayClock, IFrameStableClock
|
|
||||||
{
|
|
||||||
public GameplayClock ParentGameplayClock;
|
|
||||||
|
|
||||||
public readonly Bindable<bool> IsCatchingUp = new Bindable<bool>();
|
|
||||||
|
|
||||||
public readonly Bindable<bool> WaitingOnFrames = new Bindable<bool>();
|
|
||||||
|
|
||||||
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<Bindable<double>>();
|
|
||||||
|
|
||||||
public FrameStabilityClock(FramedClock underlyingClock)
|
|
||||||
: base(underlyingClock)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
IBindable<bool> IFrameStableClock.IsCatchingUp => IsCatchingUp;
|
|
||||||
|
|
||||||
IBindable<bool> IFrameStableClock.WaitingOnFrames => WaitingOnFrames;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
|
||||||
|
@ -3,12 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public enum PlayfieldBorderStyle
|
public enum PlayfieldBorderStyle
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.BorderNone))]
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.BorderCorners))]
|
||||||
Corners,
|
Corners,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.BorderFull))]
|
||||||
Full
|
Full
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -78,7 +77,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
// remove the previous background for now.
|
// remove the previous background for now.
|
||||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile);
|
var oldFile = set.GetFile(working.Value.Metadata.BackgroundFile);
|
||||||
|
|
||||||
using (var stream = source.OpenRead())
|
using (var stream = source.OpenRead())
|
||||||
{
|
{
|
||||||
@ -107,7 +106,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
// remove the previous audio track for now.
|
// remove the previous audio track for now.
|
||||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.AudioFile);
|
var oldFile = set.GetFile(working.Value.Metadata.AudioFile);
|
||||||
|
|
||||||
using (var stream = source.OpenRead())
|
using (var stream = source.OpenRead())
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user