1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 04:02:57 +08:00

Merge branch 'master' into improve_retry_behaviour

This commit is contained in:
Dean Herbert 2022-08-16 13:06:04 +09:00
commit c9baadcf88
196 changed files with 2405 additions and 1059 deletions

View File

@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
@ -50,9 +48,6 @@ namespace osu.Game.Rulesets.Pippidon
new KeyBinding(InputKey.X, PippidonAction.Button2),
};
public override Drawable CreateIcon() => new Sprite
{
Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
};
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
}
}

View File

@ -0,0 +1,26 @@
// 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.Allocation;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Rulesets.Pippidon
{
public class PippidonRulesetIcon : Sprite
{
private readonly Ruleset ruleset;
public PippidonRulesetIcon(Ruleset ruleset)
{
this.ruleset = ruleset;
}
[BackgroundDependencyLoader]
private void load(IRenderer renderer)
{
Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/coin");
}
}
}

View File

@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
@ -47,10 +45,6 @@ namespace osu.Game.Rulesets.Pippidon
new KeyBinding(InputKey.S, PippidonAction.MoveDown),
};
public override Drawable CreateIcon() => new Sprite
{
Margin = new MarginPadding { Top = 3 },
Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
};
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
}
}

View File

@ -0,0 +1,29 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Rulesets.Pippidon
{
public class PippidonRulesetIcon : Sprite
{
private readonly Ruleset ruleset;
public PippidonRulesetIcon(Ruleset ruleset)
{
this.ruleset = ruleset;
Margin = new MarginPadding { Top = 3 };
}
[BackgroundDependencyLoader]
private void load(IRenderer renderer)
{
Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/coin");
}
}
}

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.730.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.810.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.810.2" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View 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,
});
}
}));
}
}
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
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!";

View File

@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@ -16,21 +13,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
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)
: base(barLine)
{
RelativeSizeAxes = Axes.X;
Height = 2f;
Height = barLine.Major ? 1.7f : 1.2f;
AddInternal(new Box
{
@ -38,34 +25,33 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
Colour = new Color4(255, 204, 33, 255),
Alpha = barLine.Major ? 0.5f : 0.2f
});
if (barLine.Major)
{
AddInternal(new EquilateralTriangle
Vector2 size = new Vector2(22, 6);
const float line_offset = 4;
AddInternal(new Circle
{
Name = "Left triangle",
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopCentre,
Size = new Vector2(triangle_height),
X = -triangle_offset,
Rotation = 90
Name = "Left line",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Size = size,
X = -line_offset,
});
AddInternal(new EquilateralTriangle
AddInternal(new Circle
{
Name = "Right triangle",
Anchor = Anchor.BottomRight,
Origin = Anchor.TopCentre,
Size = new Vector2(triangle_height),
X = triangle_offset,
Rotation = -90
Name = "Right line",
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
Size = size,
X = line_offset,
});
}
if (!barLine.Major)
Alpha = 0.2f;
}
protected override void UpdateInitialTransforms()

View File

@ -9,7 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;

View File

@ -9,7 +9,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.UI.Scrolling;

View File

@ -6,7 +6,8 @@ using System.Diagnostics;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Tests
[HeadlessTest]
public class LegacyMainCirclePieceTest : OsuTestScene
{
[Resolved]
private IRenderer renderer { get; set; } = null!;
private static readonly object?[][] texture_priority_cases =
{
// default priority lookup
@ -76,7 +80,12 @@ namespace osu.Game.Rulesets.Osu.Tests
skin.Setup(s => s.GetTexture(It.IsAny<string>())).CallBase();
skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny<WrapMode>(), It.IsAny<WrapMode>()))
.Returns((string componentName, WrapMode _, WrapMode _) => new Texture(1, 1) { AssetName = componentName });
.Returns((string componentName, WrapMode _, WrapMode _) =>
{
var tex = renderer.CreateTexture(1, 1);
tex.AssetName = componentName;
return tex;
});
Child = new DependencyProvidingContainer
{
@ -84,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Child = piece = new TestLegacyMainCirclePiece(priorityLookup),
};
var sprites = this.ChildrenOfType<Sprite>().Where(s => s.Texture.AssetName != null).DistinctBy(s => s.Texture.AssetName).ToArray();
var sprites = this.ChildrenOfType<Sprite>().Where(s => !string.IsNullOrEmpty(s.Texture.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray();
Debug.Assert(sprites.Length <= 2);
});

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
Mod = mod,
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value)
Precision.AlmostEquals(Player.GameplayClockContainer.Rate, mod.SpeedChange.Value)
});
}
}

View File

@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.6369583000323935d, 206, "diffcalc-test")]
[TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
[TestCase(6.7115569159190587d, 206, "diffcalc-test")]
[TestCase(1.4391311903612753d, 45, "zero-length-sliders")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);
[TestCase(8.8816128335486386d, 206, "diffcalc-test")]
[TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
[TestCase(8.9757300665532966d, 206, "diffcalc-test")]
[TestCase(1.7437232654020756d, 45, "zero-length-sliders")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
[TestCase(6.6369583000323935d, 239, "diffcalc-test")]
[TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
[TestCase(6.7115569159190587d, 239, "diffcalc-test")]
[TestCase(1.4391311903612753d, 54, "zero-length-sliders")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());

View File

@ -11,7 +11,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing.Input;
using osu.Game.Audio;
@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneCursorTrail : OsuTestScene
{
[Resolved]
private IRenderer renderer { get; set; }
[Test]
public void TestSmoothCursorTrail()
{
@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
createTest(() =>
{
var skinContainer = new LegacySkinContainer(false);
var skinContainer = new LegacySkinContainer(renderer, false);
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
skinContainer.Child = legacyCursorTrail;
@ -58,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
createTest(() =>
{
var skinContainer = new LegacySkinContainer(true);
var skinContainer = new LegacySkinContainer(renderer, true);
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
skinContainer.Child = legacyCursorTrail;
@ -82,10 +85,12 @@ namespace osu.Game.Rulesets.Osu.Tests
[Cached(typeof(ISkinSource))]
private class LegacySkinContainer : Container, ISkinSource
{
private readonly IRenderer renderer;
private readonly bool disjoint;
public LegacySkinContainer(bool disjoint)
public LegacySkinContainer(IRenderer renderer, bool disjoint)
{
this.renderer = renderer;
this.disjoint = disjoint;
RelativeSizeAxes = Axes.Both;
@ -98,14 +103,14 @@ namespace osu.Game.Rulesets.Osu.Tests
switch (componentName)
{
case "cursortrail":
var tex = new Texture(Texture.WhitePixel.TextureGL);
var tex = new Texture(renderer.WhitePixel);
if (disjoint)
tex.ScaleAdjust = 1 / 25f;
return tex;
case "cursormiddle":
return disjoint ? null : Texture.WhitePixel;
return disjoint ? null : renderer.WhitePixel;
}
return null;

View File

@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;

View File

@ -12,7 +12,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Timing;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,7 +10,6 @@ using osu.Framework.Audio;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects;
@ -36,16 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double spinner_duration = 6000;
[Resolved]
private AudioManager audioManager { get; set; }
private AudioManager audioManager { get; set; } = null!;
protected override bool Autoplay => true;
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);
private DrawableSpinner drawableSpinner;
private DrawableSpinner drawableSpinner = null!;
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
[SetUpSteps]
@ -67,12 +64,12 @@ namespace osu.Game.Rulesets.Osu.Tests
{
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 absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 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", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.Not.EqualTo(0).Within(100));
addSeekStep(0);
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
AddAssert("is disc rotation almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(0).Within(trackerRotationTolerance));
AddAssert("is disc rotation absolute almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(0).Within(100));
}
[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.
// 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).
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance));
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
AddAssert("symbol rotation rewound",
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
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.
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
addSeekStep(spinner_start_time + 5000);
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",
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
AddAssert("is cumulative rotation almost same",
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
}
[Test]
@ -177,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
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);
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)]
@ -202,14 +199,14 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
addSeekStep(1000);
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
AddAssert("progress almost same", () => expectedProgress, () => Is.EqualTo(drawableSpinner.Progress).Within(0.05));
AddAssert("spm almost same", () => expectedSpm, () => Is.EqualTo(drawableSpinner.SpinsPerMinute.Value).Within(2.0));
}
private void addSeekStep(double 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", () =>

View File

@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public static class AimEvaluator
{
private const double wide_angle_multiplier = 1.5;
private const double acute_angle_multiplier = 2.0;
private const double slider_multiplier = 1.5;
private const double acute_angle_multiplier = 1.95;
private const double slider_multiplier = 1.35;
private const double velocity_change_multiplier = 0.75;
/// <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);
}
if (osuLastObj.TravelTime != 0)
if (osuLastObj.BaseObject is Slider)
{
// Reward sliders based on velocity.
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;

View File

@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const double max_opacity_bonus = 0.4;
private const double hidden_bonus = 0.2;
private const double min_velocity = 0.5;
private const double slider_multiplier = 1.3;
/// <summary>
/// Evaluates the difficulty of memorising and hitting an object, based on:
/// <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>length and speed of the current object (for sliders),</description></item>
/// <item><description>and whether the hidden mod is enabled.</description></item>
/// </list>
/// </summary>
@ -73,6 +77,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (hidden)
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;
}
}

View File

@ -46,7 +46,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
if (mods.Any(h => h is OsuModRelax))
{
aimRating *= 0.9;
speedRating = 0.0;
flashlightRating *= 0.7;
}
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;
@ -62,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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 drainRate = beatmap.Difficulty.DrainRate;

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
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 int scoreMaxCombo;
private int countGreat;
@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
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))
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))
{
// 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 + countMeh, totalHits);
// https://www.desmos.com/calculator/bc9eybdthb
// 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);
@ -103,7 +109,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
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.
@ -134,6 +143,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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 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);
// 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;
}
@ -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 int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
}

View File

@ -15,10 +15,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
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 float maximum_slider_radius = normalised_radius * 2.4f;
private const float assumed_slider_radius = normalised_radius * 1.8f;
private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f;
private const float assumed_slider_radius = NORMALISED_RADIUS * 1.8f;
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
@ -64,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
public double TravelDistance { get; private set; }
/// <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>
public double TravelTime { get; private set; }
@ -123,7 +127,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
if (BaseObject is Slider 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);
}
@ -132,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
return;
// 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)
{
@ -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.
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++)
{
@ -234,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
else if (currMovementObj is SliderRepeat)
{
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
requiredMovement = normalised_radius;
requiredMovement = NORMALISED_RADIUS;
}
if (currMovementLength > requiredMovement)
@ -248,8 +253,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
if (i == slider.NestedHitObjects.Count - 1)
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)

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double currentStrain;
private double skillMultiplier => 23.25;
private double skillMultiplier => 23.55;
private double strainDecayBase => 0.15;
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModAutopilot;
public override ModType Type => ModType.Automation;
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 bool PerformFail() => false;

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
public override ModType Type => ModType.Fun;
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) };
private IFrameStableClock gameplayClock = null!;

View File

@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
private bool rotationTransferred;
[Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
private IGameplayClock gameplayClock { get; set; }
protected override void Update()
{

View File

@ -9,9 +9,9 @@ using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
@ -68,8 +68,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
private void load(IRenderer renderer, ShaderManager shaders)
{
texture ??= renderer.WhitePixel;
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
}
@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
resetTime();
}
private Texture texture = Texture.WhitePixel;
private Texture texture;
public Texture Texture
{
@ -222,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private Vector2 size;
private Vector2 originPosition;
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
private IVertexBatch<TexturedTrailVertex> vertexBatch;
public TrailDrawNode(CursorTrail source)
: base(source)
@ -254,15 +255,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Source.parts.CopyTo(parts, 0);
}
public override void Draw(Action<TexturedVertex2D> vertexAction)
public override void Draw(IRenderer renderer)
{
base.Draw(vertexAction);
base.Draw(renderer);
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
shader.Bind();
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
texture.TextureGL.Bind();
texture.Bind();
RectangleF textureRect = texture.GetTextureRect();
@ -319,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
base.Dispose(isDisposing);
vertexBatch.Dispose();
vertexBatch?.Dispose();
}
}

View File

@ -6,8 +6,8 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Skinning;
using osuTK.Graphics;

View File

@ -9,6 +9,7 @@ using System.Linq;
using JetBrains.Annotations;
using Moq;
using NUnit.Framework;
using osu.Framework.Graphics.Rendering.Dummy;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
@ -55,7 +56,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestAcceptable()
{
var context = getContext(new Texture(1920, 1080));
var context = getContext(new DummyRenderer().CreateTexture(1920, 1080));
Assert.That(check.Run(context), Is.Empty);
}
@ -63,7 +64,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestTooHighResolution()
{
var context = getContext(new Texture(3840, 2160));
var context = getContext(new DummyRenderer().CreateTexture(3840, 2160));
var issues = check.Run(context).ToList();
@ -74,7 +75,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestLowResolution()
{
var context = getContext(new Texture(640, 480));
var context = getContext(new DummyRenderer().CreateTexture(640, 480));
var issues = check.Run(context).ToList();
@ -85,7 +86,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestTooLowResolution()
{
var context = getContext(new Texture(100, 100));
var context = getContext(new DummyRenderer().CreateTexture(100, 100));
var issues = check.Run(context).ToList();
@ -96,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestTooUncompressed()
{
var context = getContext(new Texture(1920, 1080), new MemoryStream(new byte[1024 * 1024 * 3]));
var context = getContext(new DummyRenderer().CreateTexture(1920, 1080), new MemoryStream(new byte[1024 * 1024 * 3]));
var issues = check.Run(context).ToList();
@ -107,7 +108,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestStreamClosed()
{
var background = new Texture(1920, 1080);
var background = new DummyRenderer().CreateTexture(1920, 1080);
var stream = new Mock<MemoryStream>(new byte[1024 * 1024]);
var context = getContext(background, stream.Object);

View File

@ -206,7 +206,7 @@ namespace osu.Game.Tests.Editing
}
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)
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);

View File

@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tests.Gameplay
});
AddStep("start clock", () => gameplayClockContainer.Start());
AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0);
AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.ElapsedFrameTime > 0);
}
[Test]
@ -60,16 +60,16 @@ namespace osu.Game.Tests.Gameplay
});
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;
AddStep("reset clock", () =>
{
timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime;
timeAtReset = gameplayClockContainer.CurrentTime;
gameplayClockContainer.Reset();
});
AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset);
AddAssert("current time < time at reset", () => gameplayClockContainer.CurrentTime < timeAtReset);
}
[Test]

View File

@ -11,8 +11,10 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Configuration;
@ -36,6 +38,9 @@ namespace osu.Game.Tests.Gameplay
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private GameHost host { get; set; }
[Test]
public void TestRetrieveTopLevelSample()
{
@ -72,7 +77,6 @@ namespace osu.Game.Tests.Gameplay
Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)
{
IsPaused = { Value = true },
Child = new FrameStabilityContainer
{
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
@ -101,7 +105,6 @@ namespace osu.Game.Tests.Gameplay
Add(gameplayContainer = new MasterGameplayClockContainer(working, start_time)
{
StartTime = start_time,
IsPaused = { Value = true },
Child = new FrameStabilityContainer
{
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
@ -136,7 +139,7 @@ namespace osu.Game.Tests.Gameplay
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
{
Clock = gameplayContainer.GameplayClock
Clock = gameplayContainer
});
});
@ -202,6 +205,7 @@ namespace osu.Game.Tests.Gameplay
#region IResourceStorageProvider
public IRenderer Renderer => host.Renderer;
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => null;
public new IResourceStore<byte[]> Resources => base.Resources;

View File

@ -36,6 +36,23 @@ namespace osu.Game.Tests.NonVisual
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]
public void TestAudioEqualitySameHash()
{

View File

@ -4,6 +4,7 @@
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Timing;
@ -30,7 +31,7 @@ namespace osu.Game.Tests.NonVisual
{
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)
: base(underlyingClock)

View File

@ -11,7 +11,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Framework.Timing;
@ -27,6 +27,9 @@ namespace osu.Game.Tests.NonVisual.Skinning
private const string animation_name = "animation";
private const int frame_count = 6;
[Resolved]
private IRenderer renderer { get; set; }
[Cached(typeof(IAnimationTimeReference))]
private TestAnimationTimeReference animationTimeReference = new TestAnimationTimeReference();
@ -35,9 +38,12 @@ namespace osu.Game.Tests.NonVisual.Skinning
[Test]
public void TestAnimationTimeReferenceChange()
{
ISkin skin = new TestSkin();
AddStep("get animation", () =>
{
ISkin skin = new TestSkin(renderer);
Add(animation = (TextureAnimation)skin.GetAnimation(animation_name, true, false));
});
AddStep("get animation", () => Add(animation = (TextureAnimation)skin.GetAnimation(animation_name, true, false)));
AddAssert("frame count correct", () => animation.FrameCount == frame_count);
assertPlaybackPosition(0);
@ -55,9 +61,16 @@ namespace osu.Game.Tests.NonVisual.Skinning
{
private static readonly string[] lookup_names = Enumerable.Range(0, frame_count).Select(frame => $"{animation_name}-{frame}").ToArray();
private readonly IRenderer renderer;
public TestSkin(IRenderer renderer)
{
this.renderer = renderer;
}
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
return lookup_names.Contains(componentName) ? Texture.WhitePixel : null;
return lookup_names.Contains(componentName) ? renderer.WhitePixel : null;
}
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();

View File

@ -11,6 +11,8 @@ using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Audio;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Dummy;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Database;
@ -141,6 +143,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
this.textureStore = textureStore;
}
public IRenderer Renderer => new DummyRenderer();
public AudioManager AudioManager => null;
public IResourceStore<byte[]> Files => null;
public IResourceStore<byte[]> Resources => null;

View File

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@ -148,6 +149,16 @@ namespace osu.Game.Tests.Online
Assert.That(apiMod.Settings["speed_change"], Is.EqualTo(1.01d));
}
[Test]
public void TestSerialisedModSettingPresence()
{
var mod = new TestMod();
mod.TestSetting.Value = mod.TestSetting.Default;
JObject serialised = JObject.Parse(JsonConvert.SerializeObject(new APIMod(mod)));
Assert.False(serialised.ContainsKey("settings"));
}
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]

View File

@ -128,6 +128,8 @@ namespace osu.Game.Tests.Resources
var rulesetInfo = getRuleset();
string hash = Guid.NewGuid().ToString().ComputeMD5Hash();
yield return new BeatmapInfo
{
OnlineID = beatmapId,
@ -136,7 +138,8 @@ namespace osu.Game.Tests.Resources
Length = length,
BeatmapSet = beatmapSet,
BPM = bpm,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Hash = hash,
MD5Hash = hash,
Ruleset = rulesetInfo,
Metadata = metadata.DeepClone(),
Difficulty = new BeatmapDifficulty

View File

@ -15,11 +15,12 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
@ -77,9 +78,9 @@ namespace osu.Game.Tests.Rulesets
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore());
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager());
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer));
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
}
@ -95,6 +96,11 @@ namespace osu.Game.Tests.Rulesets
private class TestTextureStore : TextureStore
{
public TestTextureStore(IRenderer renderer)
: base(renderer)
{
}
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public bool IsDisposed { get; private set; }
@ -148,8 +154,8 @@ namespace osu.Game.Tests.Rulesets
private class TestShaderManager : ShaderManager
{
public TestShaderManager()
: base(new ResourceStore<byte[]>())
public TestShaderManager(IRenderer renderer)
: base(renderer, new ResourceStore<byte[]>())
{
}

View File

@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;

View File

@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Audio;

View File

@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;

View File

@ -13,7 +13,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;

View File

@ -6,10 +6,11 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
@ -21,6 +22,9 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinProvidingContainer : OsuTestScene
{
[Resolved]
private IRenderer renderer { get; set; }
/// <summary>
/// Ensures that the first inserted skin after resetting (via source change)
/// is always prioritised over others when providing the same resource.
@ -35,7 +39,7 @@ namespace osu.Game.Tests.Skins
{
var sources = new List<TestSkin>();
for (int i = 0; i < 10; i++)
sources.Add(new TestSkin());
sources.Add(new TestSkin(renderer));
mostPrioritisedSource = sources.First();
@ -76,12 +80,19 @@ namespace osu.Game.Tests.Skins
{
public const string TEXTURE_NAME = "virtual-texture";
private readonly IRenderer renderer;
public TestSkin(IRenderer renderer)
{
this.renderer = renderer;
}
public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException();
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
if (componentName == TEXTURE_NAME)
return Texture.WhitePixel;
return renderer.WhitePixel;
return null;
}

View File

@ -12,7 +12,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Rendering.Dummy;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
@ -55,6 +55,7 @@ namespace osu.Game.Tests.Skins
lookedUpFileNames = new List<string>();
mockResourceProvider = new Mock<IStorageResourceProvider>();
mockResourceProvider.Setup(m => m.AudioManager).Returns(Audio);
mockResourceProvider.Setup(m => m.Renderer).Returns(new DummyRenderer());
mockResourceStore = new Mock<IResourceStore<byte[]>>();
mockResourceStore.Setup(r => r.Get(It.IsAny<string>()))
.Callback<string>(n => lookedUpFileNames.Add(n))

View File

@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Framework.Screens;
using osu.Framework.Testing;
@ -43,6 +44,9 @@ namespace osu.Game.Tests.Visual.Background
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private IRenderer renderer { get; set; }
[SetUpSteps]
public void SetUpSteps()
{
@ -245,7 +249,7 @@ namespace osu.Game.Tests.Visual.Background
Id = API.LocalUser.Value.Id + 1,
});
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(renderer, Audio);
private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio);
private class TestBackgroundScreenDefault : BackgroundScreenDefault
@ -274,12 +278,15 @@ namespace osu.Game.Tests.Visual.Background
private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
{
public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
private readonly IRenderer renderer;
public UniqueBackgroundTestWorkingBeatmap(IRenderer renderer, AudioManager audioManager)
: base(new Beatmap(), null, audioManager)
{
this.renderer = renderer;
}
protected override Texture GetBackground() => new Texture(1, 1);
protected override Texture GetBackground() => renderer.CreateTexture(1, 1);
}
private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap

View File

@ -10,7 +10,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
@ -28,11 +28,9 @@ namespace osu.Game.Tests.Visual.Background
[Resolved]
private SessionStatics statics { get; set; }
[Cached(typeof(LargeTextureStore))]
private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore();
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private LookupLoggingTextureStore textureStore;
private SeasonalBackgroundLoader backgroundLoader;
private Container backgroundContainer;
@ -45,15 +43,32 @@ namespace osu.Game.Tests.Visual.Background
"Backgrounds/bg3"
};
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
textureStore = new LookupLoggingTextureStore(dependencies.Get<IRenderer>());
dependencies.CacheAs(typeof(LargeTextureStore), textureStore);
return dependencies;
}
[BackgroundDependencyLoader]
private void load(LargeTextureStore wrappedStore)
{
textureStore.AddStore(wrappedStore);
Add(backgroundContainer = new Container
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both
});
CachedDependencies = new (Type, object)[]
{
(typeof(LargeTextureStore), textureStore)
},
Child = backgroundContainer = new Container
{
RelativeSizeAxes = Axes.Both
}
};
}
[SetUp]
@ -193,6 +208,11 @@ namespace osu.Game.Tests.Visual.Background
{
public List<string> PerformedLookups { get; } = new List<string>();
public LookupLoggingTextureStore(IRenderer renderer)
: base(renderer)
{
}
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
{
PerformedLookups.Add(name);

View File

@ -200,10 +200,12 @@ namespace osu.Game.Tests.Visual.Collections
AddStep("click confirmation", () =>
{
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
InputManager.Click(MouseButton.Left);
InputManager.PressButton(MouseButton.Left);
});
assertCollectionCount(0);
AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left));
}
[Test]

View File

@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning);
}
private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null);
private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(CreateAPIBeatmapSet());
private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack()
{

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -59,15 +57,15 @@ namespace osu.Game.Tests.Visual.Editing
{
AddStep("reset clock", () => Clock.Seek(0));
AddStep("start clock", Clock.Start);
AddStep("start clock", () => Clock.Start());
AddAssert("clock running", () => Clock.IsRunning);
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
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);
}
@ -76,32 +74,32 @@ namespace osu.Game.Tests.Visual.Editing
{
AddStep("reset clock", () => Clock.Seek(0));
AddStep("stop clock", Clock.Stop);
AddStep("stop clock", () => Clock.Stop());
AddAssert("clock stopped", () => !Clock.IsRunning);
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);
}
[Test]
public void TestClampWhenSeekOutsideBeatmapBounds()
{
AddStep("stop clock", Clock.Stop);
AddStep("stop clock", () => Clock.Stop());
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));
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));
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));
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)

View File

@ -60,17 +60,17 @@ namespace osu.Game.Tests.Visual.Editing
// Forwards
AddStep("Seek(0)", () => Clock.Seek(0));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
AddStep("Seek(33)", () => Clock.Seek(33));
AddAssert("Time = 33", () => Clock.CurrentTime == 33);
checkTime(33);
AddStep("Seek(89)", () => Clock.Seek(89));
AddAssert("Time = 89", () => Clock.CurrentTime == 89);
checkTime(89);
// Backwards
AddStep("Seek(25)", () => Clock.Seek(25));
AddAssert("Time = 25", () => Clock.CurrentTime == 25);
checkTime(25);
AddStep("Seek(0)", () => Clock.Seek(0));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
}
/// <summary>
@ -83,19 +83,19 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
checkTime(175);
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
checkTime(350);
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
checkTime(450);
}
/// <summary>
@ -108,17 +108,17 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
checkTime(175);
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
checkTime(175);
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
checkTime(350);
}
/// <summary>
@ -130,15 +130,15 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 200", () => Clock.CurrentTime == 200);
checkTime(200);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("SeekForward", () => Clock.SeekForward());
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
checkTime(450);
}
/// <summary>
@ -150,17 +150,17 @@ namespace osu.Game.Tests.Visual.Editing
reset();
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
checkTime(175);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
checkTime(350);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
checkTime(450);
}
/// <summary>
@ -174,28 +174,28 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(49)", () => Clock.Seek(49));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("Seek(99)", () => Clock.Seek(99));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 150);
checkTime(150);
AddStep("Seek(174)", () => Clock.Seek(174));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
checkTime(175);
AddStep("Seek(349)", () => Clock.Seek(349));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
checkTime(350);
AddStep("Seek(399)", () => Clock.Seek(399));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("Seek(449)", () => Clock.Seek(449));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
checkTime(450);
}
/// <summary>
@ -208,15 +208,15 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
checkTime(350);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 150", () => Clock.CurrentTime == 150);
checkTime(150);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("SeekBackward", () => Clock.SeekBackward());
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
}
/// <summary>
@ -229,17 +229,17 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(450)", () => Clock.Seek(450));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
checkTime(350);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
checkTime(175);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
checkTime(100);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
checkTime(50);
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
checkTime(0);
}
/// <summary>
@ -253,16 +253,16 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Seek(451)", () => Clock.Seek(451));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
checkTime(450);
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
checkTime(450);
AddStep("Seek(401)", () => Clock.Seek(401));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
checkTime(400);
}
/// <summary>
@ -297,9 +297,11 @@ namespace osu.Game.Tests.Visual.Editing
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()
{
AddStep("Reset", () => Clock.Seek(0));

View File

@ -4,7 +4,6 @@
#nullable disable
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.Editing
private void pressAndCheckTime(Key key, double expectedTime)
{
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));
}
}
}

View File

@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
(typeof(GameplayClock), actualComponentsContainer.Dependencies.Get<GameplayClock>())
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>())
},
};

View File

@ -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 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) =>
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames, () => Is.EqualTo(frames));
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
{

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -21,22 +19,22 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
PoolableSkinnableSample[] samples = null;
ISamplePlaybackDisabler sampleDisabler = null;
DrawableSlider? slider = null;
PoolableSkinnableSample[] samples = null!;
ISamplePlaybackDisabler sampleDisabler = null!;
AddUntilStep("get variables", () =>
{
sampleDisabler = Player;
slider = Player.ChildrenOfType<DrawableSlider>().MinBy(s => s.HitObject.StartTime);
samples = slider?.ChildrenOfType<PoolableSkinnableSample>().ToArray();
samples = slider.ChildrenOfType<PoolableSkinnableSample>().ToArray();
return slider != null;
});
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
if (slider?.Tracking.Value != true)
return false;
if (!samples.Any(s => s.Playing))

View File

@ -38,8 +38,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;

View File

@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime;
public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime;
public double GameplayClockTime => GameplayClockContainer.CurrentTime;
protected override void UpdateAfterChildren()
{
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay
if (!FirstFrameClockTime.HasValue)
{
FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime;
FirstFrameClockTime = GameplayClockContainer.CurrentTime;
AddInternal(new OsuSpriteText
{
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
base.SetUpSteps();
AddUntilStep("gameplay has started",
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
() => Player.GameplayClockContainer.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
}
[Test]

View File

@ -313,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("pause again", () =>
{
Player.Pause();
return !Player.GameplayClockContainer.GameplayClock.IsRunning;
return !Player.GameplayClockContainer.IsRunning;
});
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);
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;

View File

@ -29,8 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
[SetUpSteps]
public void SetUpSteps()

View File

@ -13,7 +13,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;

View File

@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClock(new FramedClock());
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();

View File

@ -13,7 +13,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;

View File

@ -6,6 +6,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK;
@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private double increment;
private GameplayClockContainer gameplayClockContainer;
private GameplayClock gameplayClock;
private IFrameBasedClock gameplayClock;
private const double skip_time = 6000;
@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
};
gameplayClockContainer.Start();
gameplayClock = gameplayClockContainer.GameplayClock;
gameplayClock = gameplayClockContainer;
});
[Test]

View File

@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
Dependencies.CacheAs(gameplayClockContainer.GameplayClock);
Dependencies.CacheAs<IGameplayClock>(gameplayClockContainer);
}
[SetUpSteps]

View File

@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private Player player => Stack.CurrentScreen as Player;
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);

View File

@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestStoryboardNoSkipOutro()
{
CreateTest();
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
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("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);
}
@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("set ShowResults = false", () => showResults = false);
});
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddWaitStep("wait", 10);
AddAssert("no score shown", () => !Player.IsScoreShown);
}
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestStoryboardEndsBeforeCompletion()
{
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("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 becomes visible", () => fadeContainer().State == Visibility.Visible);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
}
[Test]

View 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));
}
}
}
}

View File

@ -432,8 +432,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
var user = playingUsers.Single(u => u.UserID == userId);
OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull());
SpectatorClient.SendEndPlay(userId);
OnlinePlayDependencies.MultiplayerClient.RemoveUser(user.User.AsNonNull());
playingUsers.Remove(user);
});
@ -451,7 +451,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
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)
{

View File

@ -671,7 +671,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
{
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);

View File

@ -83,6 +83,20 @@ namespace osu.Game.Tests.Visual.Online
Beatmap = dummyBeatmap,
},
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetApprove,
Approval = BeatmapApproval.Approved,
Beatmapset = dummyBeatmap,
},
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetApprove,
Approval = BeatmapApproval.Loved,
Beatmapset = dummyBeatmap,
},
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetApprove,
@ -90,6 +104,13 @@ namespace osu.Game.Tests.Visual.Online
Beatmapset = dummyBeatmap,
},
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetApprove,
Approval = BeatmapApproval.Ranked,
Beatmapset = dummyBeatmap,
},
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetDelete,

View File

@ -11,9 +11,11 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania.Mods;
@ -35,16 +37,27 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
[Cached(typeof(IDialogOverlay))]
private readonly DialogOverlay dialogOverlay = new DialogOverlay();
[BackgroundDependencyLoader]
private void load()
{
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(Realm);
base.Content.Add(content = new PopoverContainer
base.Content.AddRange(new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Child = content = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
}
},
dialogOverlay
});
}
@ -121,6 +134,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestSoftDeleteSupport()
{
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
AddStep("create content", () => Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
@ -140,9 +154,11 @@ namespace osu.Game.Tests.Visual.UserInterface
foreach (var preset in r.All<ModPreset>())
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>())
preset.DeletePending = false;
@ -205,6 +221,46 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
}
[Test]
public void TestDeleteFlow()
{
ModPresetColumn modPresetColumn = null!;
AddStep("create content", () => Child = modPresetColumn = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
AddStep("right click first panel", () =>
{
var panel = this.ChildrenOfType<ModPresetPanel>().First();
InputManager.MoveMouseTo(panel);
InputManager.Click(MouseButton.Right);
});
AddUntilStep("wait for context menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
AddStep("click delete", () =>
{
var deleteItem = this.ChildrenOfType<DrawableOsuMenuItem>().Single();
InputManager.MoveMouseTo(deleteItem);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for dialog", () => dialogOverlay.CurrentDialog is DeleteModPresetDialog);
AddStep("hold confirm", () =>
{
var confirmButton = this.ChildrenOfType<PopupDialogDangerousButton>().Single();
InputManager.MoveMouseTo(confirmButton);
InputManager.PressButton(MouseButton.Left);
});
AddUntilStep("wait for dialog to close", () => dialogOverlay.CurrentDialog == null);
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddUntilStep("preset deletion occurred", () => this.ChildrenOfType<ModPresetPanel>().Count() == 2);
AddAssert("preset soft-deleted", () => Realm.Run(r => r.All<ModPreset>().Count(preset => preset.DeletePending) == 1));
}
private ICollection<ModPreset> createTestPresets() => new[]
{
new ModPreset

View File

@ -1,12 +1,15 @@
// 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 System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
@ -23,6 +26,12 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
[SetUpSteps]
public void SetUpSteps()
{
AddStep("reset selected mods", () => SelectedMods.SetDefault());
}
[Test]
public void TestVariousModPresets()
{
@ -37,6 +46,78 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
[Test]
public void TestPresetSelectionStateAfterExternalModChanges()
{
ModPresetPanel? panel = null;
AddStep("create panel", () => Child = panel = new ModPresetPanel(createTestPresets().First().ToLiveUnmanaged())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f
});
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
AddStep("set mods to HR", () => SelectedMods.Value = new[] { new OsuModHardRock() });
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
AddStep("set mods to DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
AddStep("set mods to HR+DT", () => SelectedMods.Value = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() });
AddAssert("panel is active", () => panel.AsNonNull().Active.Value);
AddStep("set mods to HR+customised DT", () => SelectedMods.Value = new Mod[]
{
new OsuModHardRock(),
new OsuModDoubleTime
{
SpeedChange = { Value = 1.25 }
}
});
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
AddStep("set mods to HR+DT", () => SelectedMods.Value = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() });
AddAssert("panel is active", () => panel.AsNonNull().Active.Value);
AddStep("customise mod in place", () => SelectedMods.Value.OfType<OsuModDoubleTime>().Single().SpeedChange.Value = 1.33);
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
AddStep("set mods to HD+HR+DT", () => SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() });
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
}
[Test]
public void TestActivatingPresetTogglesIncludedMods()
{
ModPresetPanel? panel = null;
AddStep("create panel", () => Child = panel = new ModPresetPanel(createTestPresets().First().ToLiveUnmanaged())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f
});
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
assertSelectedModsEquivalentTo(new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() });
AddStep("deactivate panel", () => panel.AsNonNull().TriggerClick());
assertSelectedModsEquivalentTo(Array.Empty<Mod>());
AddStep("set different mod", () => SelectedMods.Value = new[] { new OsuModHidden() });
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
assertSelectedModsEquivalentTo(new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() });
AddStep("set customised mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
assertSelectedModsEquivalentTo(new Mod[] { new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
}
private void assertSelectedModsEquivalentTo(IEnumerable<Mod> mods)
=> AddAssert("selected mods changed correctly", () => new HashSet<Mod>(SelectedMods.Value).SetEquals(mods));
private static IEnumerable<ModPreset> createTestPresets() => new[]
{
new ModPreset

View File

@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("any column dimmed", () => this.ChildrenOfType<ModColumn>().Any(column => !column.Active.Value));
ModColumn lastColumn = null;
ModSelectColumn lastColumn = null;
AddAssert("last column dimmed", () => !this.ChildrenOfType<ModColumn>().Last().Active.Value);
AddStep("request scroll to last column", () =>

View File

@ -1,18 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Music;
using osu.Game.Rulesets;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
@ -21,13 +23,25 @@ namespace osu.Game.Tests.Visual.UserInterface
{
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]
public void Setup() => Schedule(() =>
@ -46,16 +60,12 @@ namespace osu.Game.Tests.Visual.UserInterface
}
};
beatmapSets.Clear();
for (int i = 0; i < item_count; i++)
{
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged());
beatmapManager.Import(TestResources.CreateTestBeatmapSetInfo());
}
first = beatmapSets.First();
playlistOverlay.BeatmapSets.BindTo(beatmapSets);
beatmapSets.First().ToLive(Realm);
});
[Test]
@ -70,9 +80,13 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any());
PlaylistItem firstItem = null!;
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.PressButton(MouseButton.Left);
});
@ -83,7 +97,7 @@ namespace osu.Game.Tests.Visual.UserInterface
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));
}
@ -101,6 +115,68 @@ namespace osu.Game.Tests.Visual.UserInterface
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
.Where(item => item.MatchingFilter)
.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 }));
}
}
}

View File

@ -199,8 +199,8 @@ namespace osu.Game.Beatmaps
Debug.Assert(x.BeatmapSet != null);
Debug.Assert(y.BeatmapSet != null);
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.Metadata))?.File.Hash;
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.Metadata))?.File.Hash;
string? fileHashX = x.BeatmapSet.GetFile(getFilename(x.Metadata))?.File.Hash;
string? fileHashY = y.BeatmapSet.GetFile(getFilename(y.Metadata))?.File.Hash;
return fileHashX == fileHashY;
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using osu.Framework.Localisation;

View File

@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps
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.
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);
// ensure that two difficulties from the set don't point at the same beatmap file.

View 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)
{
if (ReferenceEquals(this, other)) return true;

View 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));
}
}

View File

@ -9,6 +9,8 @@ using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Dummy;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Lists;
@ -56,7 +58,7 @@ namespace osu.Game.Beatmaps
this.resources = resources;
this.host = host;
this.files = files;
largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(files));
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
this.trackStore = trackStore;
}
@ -110,6 +112,7 @@ namespace osu.Game.Beatmaps
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
AudioManager IStorageResourceProvider.AudioManager => audioManager;
RealmAccess IStorageResourceProvider.RealmAccess => null;
IResourceStore<byte[]> IStorageResourceProvider.Files => files;

View File

@ -3,33 +3,17 @@
using System;
using Humanizer;
using osu.Framework.Graphics.Sprites;
using osu.Game.Database;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Collections
{
public class DeleteCollectionDialog : PopupDialog
public class DeleteCollectionDialog : DeleteConfirmationDialog
{
public DeleteCollectionDialog(Live<BeatmapCollection> collection, Action deleteAction)
{
HeaderText = "Confirm deletion of";
BodyText = collection.PerformRead(c => $"{c.Name} ({"beatmap".ToQuantity(c.BeatmapMD5Hashes.Count)})");
Icon = FontAwesome.Regular.TrashAlt;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes. Go for it.",
Action = deleteAction
},
new PopupDialogCancelButton
{
Text = @"No! Abort mission!",
},
};
DeleteAction = deleteAction;
}
}
}

View File

@ -3,16 +3,20 @@
#nullable disable
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum BackgroundSource
{
[LocalisableDescription(typeof(SkinSettingsStrings), nameof(SkinSettingsStrings.SkinSectionHeader))]
Skin,
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.BeatmapHeader))]
Beatmap,
[Description("Beatmap (with storyboard / video)")]
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.BeatmapWithStoryboard))]
BeatmapWithStoryboard,
}
}

View File

@ -3,17 +3,20 @@
#nullable disable
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum DiscordRichPresenceMode
{
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.DiscordPresenceOff))]
Off,
[Description("Hide identifiable information")]
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.HideIdentifiableInformation))]
Limited,
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.DiscordPresenceFull))]
Full
}
}

View File

@ -3,17 +3,20 @@
#nullable disable
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum HUDVisibilityMode
{
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.NeverShowHUD))]
Never,
[Description("Hide during gameplay")]
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.HideDuringGameplay))]
HideDuringGameplay,
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.AlwaysShowHUD))]
Always
}
}

View File

@ -3,16 +3,17 @@
#nullable disable
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum RandomSelectAlgorithm
{
[Description("Never repeat")]
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.NeverRepeat))]
RandomPermutation,
[Description("True Random")]
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.TrueRandom))]
Random
}
}

View File

@ -3,17 +3,23 @@
#nullable disable
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum ScalingMode
{
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScalingOff))]
Off,
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleEverything))]
Everything,
[Description("Excluding overlays")]
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleEverythingExcludingOverlays))]
ExcludeOverlays,
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleGameplay))]
Gameplay,
}
}

View File

@ -3,16 +3,17 @@
#nullable disable
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum ScreenshotFormat
{
[Description("JPG (web-friendly)")]
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.Jpg))]
Jpg = 1,
[Description("PNG (lossless)")]
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.Png))]
Png = 2
}
}

View File

@ -3,6 +3,9 @@
#nullable disable
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Configuration
{
public enum SeasonalBackgroundMode
@ -10,16 +13,19 @@ namespace osu.Game.Configuration
/// <summary>
/// Seasonal backgrounds are shown regardless of season, if at all available.
/// </summary>
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.AlwaysSeasonalBackground))]
Always,
/// <summary>
/// Seasonal backgrounds are shown only during their corresponding season.
/// </summary>
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SometimesSeasonalBackground))]
Sometimes,
/// <summary>
/// Seasonal backgrounds are never shown.
/// </summary>
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.NeverSeasonalBackground))]
Never
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Models;
namespace osu.Game.Database
@ -11,8 +12,16 @@ namespace osu.Game.Database
/// </summary>
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; }
/// <summary>
/// A combined hash representing the model, based on the files it contains.
/// Implementation specific.
/// </summary>
string Hash { get; set; }
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Threading;
@ -27,27 +25,30 @@ namespace osu.Game.Database
public class LegacyImportManager : Component
{
[Resolved]
private SkinManager skins { get; set; }
private SkinManager skins { get; set; } = null!;
[Resolved]
private BeatmapManager beatmaps { get; set; }
private BeatmapManager beatmaps { get; set; } = null!;
[Resolved]
private ScoreManager scores { get; set; }
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
private ScoreManager scores { get; set; } = null!;
[Resolved]
private IDialogOverlay dialogOverlay { get; set; }
private OsuGame? game { get; set; }
[Resolved]
private RealmAccess realmAccess { get; set; }
private IDialogOverlay dialogOverlay { get; set; } = null!;
[Resolved(canBeNull: true)]
private DesktopGameHost desktopGameHost { get; set; }
[Resolved]
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;
@ -98,6 +99,9 @@ namespace osu.Game.Database
stableStorage = GetCurrentStableStorage();
}
if (stableStorage == null)
return;
var importTasks = new List<Task>();
Task beatmapImportTask = Task.CompletedTask;
@ -108,7 +112,14 @@ namespace osu.Game.Database
importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage));
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))
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);
}
public StableStorage GetCurrentStableStorage()
public StableStorage? GetCurrentStableStorage()
{
if (cachedStorage != null)
return cachedStorage;

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
@ -79,7 +80,7 @@ namespace osu.Game.Database
/// </summary>
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)
{

View File

@ -25,6 +25,7 @@ using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Models;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Skinning;
using Realms;
@ -172,6 +173,11 @@ namespace osu.Game.Database
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
Filename += realm_extension;
#if DEBUG
if (!DebugUtils.IsNUnitRunning)
applyFilenameSchemaSuffix(ref Filename);
#endif
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
// Attempt to recover a newer database version if available.
@ -211,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)
{
Logger.Log($@"Performing recovery from {recoveryFilename}", LoggingTarget.Database);
@ -292,6 +343,11 @@ namespace osu.Game.Database
foreach (var s in pendingDeleteSkins)
realm.Remove(s);
var pendingDeletePresets = realm.All<ModPreset>().Where(s => s.DeletePending);
foreach (var s in pendingDeletePresets)
realm.Remove(s);
transaction.Commit();
}

View File

@ -14,9 +14,8 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Allocation;
using System.Collections.Generic;
using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL.Buffers;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Lists;
namespace osu.Game.Graphics.Backgrounds
@ -88,7 +87,7 @@ namespace osu.Game.Graphics.Backgrounds
private Random stableRandom;
private IShader shader;
private readonly Texture texture;
private Texture texture;
/// <summary>
/// Construct a new triangle visualisation.
@ -98,13 +97,12 @@ namespace osu.Game.Graphics.Backgrounds
{
if (seed != null)
stableRandom = new Random(seed.Value);
texture = Texture.WhitePixel;
}
[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
private void load(IRenderer renderer, ShaderManager shaders)
{
texture = renderer.WhitePixel;
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
}
@ -184,8 +182,8 @@ namespace osu.Game.Graphics.Backgrounds
private void addTriangles(bool randomY)
{
// limited by the maximum size of QuadVertexBuffer for safety.
const int max_triangles = QuadVertexBuffer<TexturedVertex2D>.MAX_QUADS;
// Limited by the maximum size of QuadVertexBuffer for safety.
const int max_triangles = ushort.MaxValue / (IRenderer.VERTICES_PER_QUAD + 2);
AimCount = (int)Math.Min(max_triangles, (DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio));
@ -251,7 +249,7 @@ namespace osu.Game.Graphics.Backgrounds
private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
private Vector2 size;
private QuadBatch<TexturedVertex2D> vertexBatch;
private IVertexBatch<TexturedVertex2D> vertexBatch;
public TrianglesDrawNode(Triangles source)
: base(source)
@ -270,14 +268,14 @@ namespace osu.Game.Graphics.Backgrounds
parts.AddRange(Source.parts);
}
public override void Draw(Action<TexturedVertex2D> vertexAction)
public override void Draw(IRenderer renderer)
{
base.Draw(vertexAction);
base.Draw(renderer);
if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount))
{
vertexBatch?.Dispose();
vertexBatch = new QuadBatch<TexturedVertex2D>(Source.AimCount, 1);
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
}
shader.Bind();
@ -297,7 +295,7 @@ namespace osu.Game.Graphics.Backgrounds
ColourInfo colourInfo = DrawColourInfo.Colour;
colourInfo.ApplyChild(particle.Colour);
DrawTriangle(
renderer.DrawTriangle(
texture,
triangle,
colourInfo,

View File

@ -5,7 +5,7 @@
using System;
using System.Runtime.InteropServices;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Rendering.Vertices;
using osuTK;
using osuTK.Graphics;
using osuTK.Graphics.ES30;

View File

@ -6,8 +6,8 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
@ -89,7 +89,7 @@ namespace osu.Game.Graphics
currentTime = source.Time.Current;
}
protected override void Blit(Action<TexturedVertex2D> vertexAction)
protected override void Blit(IRenderer renderer)
{
double time = currentTime - startTime;
@ -112,9 +112,9 @@ namespace osu.Game.Graphics
Vector2Extensions.Transform(rect.BottomRight, DrawInfo.Matrix)
);
DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction,
new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
null, TextureCoords);
renderer.DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha),
inflationPercentage: new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
textureCoords: TextureCoords);
}
}

View File

@ -7,8 +7,8 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
@ -107,7 +107,7 @@ namespace osu.Game.Graphics
sourceSize = Source.DrawSize;
}
protected override void Blit(Action<TexturedVertex2D> vertexAction)
protected override void Blit(IRenderer renderer)
{
foreach (var p in particles)
{
@ -136,9 +136,9 @@ namespace osu.Game.Graphics
transformPosition(rect.BottomRight, rect.Centre, angle)
);
DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction,
new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
null, TextureCoords);
renderer.DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha),
inflationPercentage: new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
textureCoords: TextureCoords);
}
}

View File

@ -3,10 +3,9 @@
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Sprites;
@ -57,11 +56,11 @@ namespace osu.Game.Graphics.Sprites
progress = source.animationProgress;
}
protected override void Blit(Action<TexturedVertex2D> vertexAction)
protected override void Blit(IRenderer renderer)
{
Shader.GetUniform<float>("progress").UpdateValue(ref progress);
GetAppropriateShader(renderer).GetUniform<float>("progress").UpdateValue(ref progress);
base.Blit(vertexAction);
base.Blit(renderer);
}
protected override bool CanDrawOpaqueInterior => false;

View File

@ -167,6 +167,11 @@ namespace osu.Game.Graphics.UserInterface
{
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);
// 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.
bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms;
// note that we use an elapsed time here of 1 intentionally.
// 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);
const float damp_time = 100;
displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : damp_time, updateClock.ElapsedFrameTime);
if (hasDrawSpike)
// show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show.
displayedFpsCount = 1000 / drawClock.ElapsedFrameTime;
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)
{
@ -203,7 +208,7 @@ namespace osu.Game.Graphics.UserInterface
if (hasSignificantChanges)
requestDisplay();
else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000)
else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000 && !IsHovered)
{
mainContent.FadeTo(0, 300, Easing.OutQuint);
isDisplayed = false;

Some files were not shown because too many files have changed in this diff Show More