mirror of
https://github.com/ppy/osu.git
synced 2025-01-19 15:02:54 +08:00
Merge branch 'master' into play-storyboard-outro
This commit is contained in:
commit
45c2b72a25
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.410.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.410.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.415.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -7,6 +7,8 @@ using Android.OS;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
@ -72,5 +74,14 @@ namespace osu.Android
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||||
|
|
||||||
|
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
||||||
|
|
||||||
|
private class AndroidBatteryInfo : BatteryInfo
|
||||||
|
{
|
||||||
|
public override double ChargeLevel => Battery.ChargeLevel;
|
||||||
|
|
||||||
|
public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,6 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
|
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
||||||
</manifest>
|
</manifest>
|
@ -63,5 +63,8 @@
|
|||||||
<Version>5.0.0</Version>
|
<Version>5.0.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
@ -69,7 +69,6 @@ namespace osu.Desktop
|
|||||||
/// Allow a maximum of one unhandled exception, per second of execution.
|
/// Allow a maximum of one unhandled exception, per second of execution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="arg"></param>
|
/// <param name="arg"></param>
|
||||||
/// <returns></returns>
|
|
||||||
private static bool handleException(Exception arg)
|
private static bool handleException(Exception arg)
|
||||||
{
|
{
|
||||||
bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0;
|
bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0;
|
||||||
|
@ -482,7 +482,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// Retrieves the sample info list at a point in time.
|
/// Retrieves the sample info list at a point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||||
/// <returns></returns>
|
|
||||||
private IList<HitSampleInfo> sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
|
private IList<HitSampleInfo> sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Name => "Mirror";
|
public override string Name => "Mirror";
|
||||||
public override string Acronym => "MR";
|
public override string Acronym => "MR";
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
public override string Description => "Notes are flipped horizontally.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
|
|
||||||
|
@ -0,0 +1,260 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckOffscreenObjectsTest
|
||||||
|
{
|
||||||
|
private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE * 0.5f;
|
||||||
|
|
||||||
|
private CheckOffscreenObjects check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckOffscreenObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleInCenter()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre // Playfield is 640 x 480.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleNearEdge()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(5, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleNearEdgeStackedOffscreen()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(5, 5),
|
||||||
|
StackHeight = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenCircle(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleOffscreen()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenCircle(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderInCenter()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(420, 240),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(-100, 0))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderNearEdge()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderNearEdgeStackedOffscreen()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
|
||||||
|
}),
|
||||||
|
StackHeight = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderOffscreenStart()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(0, 0),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(playfield_centre)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderOffscreenEnd()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(-playfield_centre)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderOffscreenPath()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
// Circular arc shoots over the top of the screen.
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(-100, -200)),
|
||||||
|
new PathControlPoint(new Vector2(100, -200))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOffscreenCircle(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOffscreenSlider(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneOsuEditorSelectInvalidPath : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectDoesNotModify()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider { StartTime = 0, Position = new Vector2(320, 40) };
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(-100, 0)),
|
||||||
|
new PathControlPoint(new Vector2(100, 20))
|
||||||
|
};
|
||||||
|
|
||||||
|
int preSelectVersion = -1;
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
slider.Path = new SliderPath(points);
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
preSelectVersion = slider.Path.Version.Value;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
|
||||||
|
AddAssert("slider same path", () => slider.Path.Version.Value == preSelectVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
|
|
||||||
private void runSpmTest(Mod mod)
|
private void runSpmTest(Mod mod)
|
||||||
{
|
{
|
||||||
SpinnerSpmCounter spmCounter = null;
|
SpinnerSpmCalculator spmCalculator = null;
|
||||||
|
|
||||||
CreateModTest(new ModTestData
|
CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
@ -53,13 +53,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 1
|
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 1
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("fetch SPM counter", () =>
|
AddUntilStep("fetch SPM calculator", () =>
|
||||||
{
|
{
|
||||||
spmCounter = this.ChildrenOfType<SpinnerSpmCounter>().SingleOrDefault();
|
spmCalculator = this.ChildrenOfType<SpinnerSpmCalculator>().SingleOrDefault();
|
||||||
return spmCounter != null;
|
return spmCalculator != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCounter.SpinsPerMinute, 477, 5));
|
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
Beatmap = singleSpinnerBeatmap,
|
Beatmap = singleSpinnerBeatmap,
|
||||||
PassCondition = () =>
|
PassCondition = () =>
|
||||||
{
|
{
|
||||||
var counter = Player.ChildrenOfType<SpinnerSpmCounter>().SingleOrDefault();
|
var counter = Player.ChildrenOfType<SpinnerSpmCalculator>().SingleOrDefault();
|
||||||
return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1);
|
return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png
Executable file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursortrail.png
Executable file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursortrail.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-rpm.png
Normal file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-rpm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||||
|
|
||||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
||||||
|
|
||||||
public event Action SourceChanged
|
public event Action SourceChanged
|
||||||
{
|
{
|
||||||
|
@ -4,13 +4,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing.Input;
|
using osu.Framework.Testing.Input;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@ -21,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Cached]
|
[Cached]
|
||||||
private GameplayBeatmap gameplayBeatmap;
|
private GameplayBeatmap gameplayBeatmap;
|
||||||
|
|
||||||
private ClickingCursorContainer lastContainer;
|
private OsuCursorContainer lastContainer;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
@ -48,12 +57,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||||
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
|
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
|
||||||
Scheduler.AddOnce(recreate);
|
Scheduler.AddOnce(() => loadContent(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("test cursor container", recreate);
|
AddStep("test cursor container", () => loadContent(false));
|
||||||
|
|
||||||
void recreate() => SetContents(() => new OsuInputManager(new OsuRuleset().RulesetInfo) { Child = new OsuCursorContainer() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(1, 1)]
|
[TestCase(1, 1)]
|
||||||
@ -68,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
|
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
|
||||||
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
||||||
|
|
||||||
AddStep("load content", loadContent);
|
AddStep("load content", () => loadContent());
|
||||||
|
|
||||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
||||||
|
|
||||||
@ -82,18 +89,46 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
|
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContent()
|
[Test]
|
||||||
|
public void TestTopLeftOrigin()
|
||||||
{
|
{
|
||||||
SetContents(() => new MovingCursorInputManager
|
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
|
||||||
{
|
|
||||||
Child = lastContainer = new ClickingCursorContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null)
|
||||||
|
{
|
||||||
|
SetContents(() =>
|
||||||
|
{
|
||||||
|
var inputManager = automated ? (InputManager)new MovingCursorInputManager() : new OsuInputManager(new OsuRuleset().RulesetInfo);
|
||||||
|
var skinContainer = skinProvider?.Invoke() ?? new SkinProvidingContainer(null);
|
||||||
|
|
||||||
|
lastContainer = automated ? new ClickingCursorContainer() : new OsuCursorContainer();
|
||||||
|
|
||||||
|
return inputManager.WithChild(skinContainer.WithChild(lastContainer));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TopLeftCursorSkin : ISkin
|
||||||
|
{
|
||||||
|
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||||
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
switch (lookup)
|
||||||
|
{
|
||||||
|
case OsuSkinConfiguration osuLookup:
|
||||||
|
if (osuLookup == OsuSkinConfiguration.CursorCentre)
|
||||||
|
return SkinUtils.As<TValue>(new BindableBool(false));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ClickingCursorContainer : OsuCursorContainer
|
private class ClickingCursorContainer : OsuCursorContainer
|
||||||
{
|
{
|
||||||
private bool pressed;
|
private bool pressed;
|
||||||
|
@ -168,13 +168,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
double estimatedSpm = 0;
|
double estimatedSpm = 0;
|
||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
|
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
||||||
|
|
||||||
addSeekStep(2000);
|
addSeekStep(2000);
|
||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(0.5)]
|
[TestCase(0.5)]
|
||||||
@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("retrieve spinner state", () =>
|
AddStep("retrieve spinner state", () =>
|
||||||
{
|
{
|
||||||
expectedProgress = drawableSpinner.Progress;
|
expectedProgress = drawableSpinner.Progress;
|
||||||
expectedSpm = drawableSpinner.SpmCounter.SpinsPerMinute;
|
expectedSpm = drawableSpinner.SpinsPerMinute.Value;
|
||||||
});
|
});
|
||||||
|
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
||||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpmCounter.SpinsPerMinute, 2.0));
|
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay
|
private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay
|
||||||
|
@ -59,11 +59,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
ControlPoint = controlPoint;
|
ControlPoint = controlPoint;
|
||||||
|
|
||||||
|
// we don't want to run the path type update on construction as it may inadvertently change the slider.
|
||||||
|
cachePoints(slider);
|
||||||
|
|
||||||
slider.Path.Version.BindValueChanged(_ =>
|
slider.Path.Version.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
|
cachePoints(slider);
|
||||||
updatePathType();
|
updatePathType();
|
||||||
}, runOnceImmediately: true);
|
});
|
||||||
|
|
||||||
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
@ -205,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
||||||
|
|
||||||
|
private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles correction of invalid path types.
|
/// Handles correction of invalid path types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
115
osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs
Normal file
115
osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckOffscreenObjects : ICheck
|
||||||
|
{
|
||||||
|
// A close approximation for the bounding box of the screen in gameplay on 4:3 aspect ratio.
|
||||||
|
// Uses gameplay space coordinates (512 x 384 playfield / 640 x 480 screen area).
|
||||||
|
// See https://github.com/ppy/osu/pull/12361#discussion_r612199777 for reference.
|
||||||
|
private const int min_x = -67;
|
||||||
|
private const int min_y = -60;
|
||||||
|
private const int max_x = 579;
|
||||||
|
private const int max_y = 428;
|
||||||
|
|
||||||
|
// The amount of milliseconds to step through a slider path at a time
|
||||||
|
// (higher = more performant, but higher false-negative chance).
|
||||||
|
private const int path_step_size = 5;
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Offscreen hitobjects");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateOffscreenCircle(this),
|
||||||
|
new IssueTemplateOffscreenSlider(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
foreach (var hitobject in beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
switch (hitobject)
|
||||||
|
{
|
||||||
|
case Slider slider:
|
||||||
|
{
|
||||||
|
foreach (var issue in sliderIssues(slider))
|
||||||
|
yield return issue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HitCircle circle:
|
||||||
|
{
|
||||||
|
if (isOffscreen(circle.StackedPosition, circle.Radius))
|
||||||
|
yield return new IssueTemplateOffscreenCircle(this).Create(circle);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Steps through points on the slider to ensure the entire path is on-screen.
|
||||||
|
/// Returns at most one issue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slider">The slider whose path to check.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private IEnumerable<Issue> sliderIssues(Slider slider)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < slider.Distance; i += path_step_size)
|
||||||
|
{
|
||||||
|
double progress = i / slider.Distance;
|
||||||
|
Vector2 position = slider.StackedPositionAt(progress);
|
||||||
|
|
||||||
|
if (!isOffscreen(position, slider.Radius))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// `SpanDuration` ensures we don't include reverses.
|
||||||
|
double time = slider.StartTime + progress * slider.SpanDuration;
|
||||||
|
yield return new IssueTemplateOffscreenSlider(this).Create(slider, time);
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Above loop may skip the last position in the slider due to step size.
|
||||||
|
if (!isOffscreen(slider.StackedEndPosition, slider.Radius))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return new IssueTemplateOffscreenSlider(this).Create(slider, slider.EndTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isOffscreen(Vector2 position, double radius)
|
||||||
|
{
|
||||||
|
return position.X - radius < min_x || position.X + radius > max_x ||
|
||||||
|
position.Y - radius < min_y || position.Y + radius > max_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateOffscreenCircle : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateOffscreenCircle(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(HitCircle circle) => new Issue(circle, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateOffscreenSlider : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateOffscreenSlider(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Slider slider, double offscreenTime) => new Issue(slider, this) { Time = offscreenTime };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs
Normal file
22
osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public class OsuBeatmapVerifier : IBeatmapVerifier
|
||||||
|
{
|
||||||
|
private readonly List<ICheck> checks = new List<ICheck>
|
||||||
|
{
|
||||||
|
new CheckOffscreenObjects()
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
|
||||||
|
}
|
||||||
|
}
|
44
osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs
Normal file
44
osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
[SettingSource("Roll speed", "Rotations per minute")]
|
||||||
|
public BindableNumber<double> SpinSpeed { get; } = new BindableDouble(0.5)
|
||||||
|
{
|
||||||
|
MinValue = 0.02,
|
||||||
|
MaxValue = 4,
|
||||||
|
Precision = 0.01,
|
||||||
|
};
|
||||||
|
|
||||||
|
[SettingSource("Direction", "The direction of rotation")]
|
||||||
|
public Bindable<RotationDirection> Direction { get; } = new Bindable<RotationDirection>(RotationDirection.Clockwise);
|
||||||
|
|
||||||
|
public override string Name => "Barrel Roll";
|
||||||
|
public override string Acronym => "BR";
|
||||||
|
public override string Description => "The whole playfield is on a wheel!";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
playfield.Rotation = (Direction.Value == RotationDirection.CounterClockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
// scale the playfield to allow all hitobjects to stay within the visible region.
|
||||||
|
drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Touch Device";
|
public override string Name => "Touch Device";
|
||||||
public override string Acronym => "TD";
|
public override string Acronym => "TD";
|
||||||
|
public override string Description => "Automatically applied to plays on devices with a touchscreen.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override ModType Type => ModType.System;
|
public override ModType Type => ModType.System;
|
||||||
|
@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result;
|
public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result;
|
||||||
|
|
||||||
public SpinnerRotationTracker RotationTracker { get; private set; }
|
public SpinnerRotationTracker RotationTracker { get; private set; }
|
||||||
public SpinnerSpmCounter SpmCounter { get; private set; }
|
|
||||||
|
private SpinnerSpmCalculator spmCalculator;
|
||||||
|
|
||||||
private Container<DrawableSpinnerTick> ticks;
|
private Container<DrawableSpinnerTick> ticks;
|
||||||
private PausableSkinnableSound spinningSample;
|
private PausableSkinnableSound spinningSample;
|
||||||
@ -43,7 +44,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IBindable<double> GainedBonus => gainedBonus;
|
public IBindable<double> GainedBonus => gainedBonus;
|
||||||
|
|
||||||
private readonly Bindable<double> gainedBonus = new Bindable<double>();
|
private readonly Bindable<double> gainedBonus = new BindableDouble();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of spins per minute this spinner is spinning at, for display purposes.
|
||||||
|
/// </summary>
|
||||||
|
public readonly IBindable<double> SpinsPerMinute = new BindableDouble();
|
||||||
|
|
||||||
private const double fade_out_duration = 160;
|
private const double fade_out_duration = 160;
|
||||||
|
|
||||||
@ -63,8 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
|
spmCalculator = new SpinnerSpmCalculator
|
||||||
|
{
|
||||||
|
Result = { BindTarget = SpinsPerMinute },
|
||||||
|
},
|
||||||
ticks = new Container<DrawableSpinnerTick>(),
|
ticks = new Container<DrawableSpinnerTick>(),
|
||||||
new AspectContainer
|
new AspectContainer
|
||||||
{
|
{
|
||||||
@ -77,20 +87,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
RotationTracker = new SpinnerRotationTracker(this)
|
RotationTracker = new SpinnerRotationTracker(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SpmCounter = new SpinnerSpmCounter
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Y = 120,
|
|
||||||
Alpha = 0
|
|
||||||
},
|
|
||||||
spinningSample = new PausableSkinnableSound
|
spinningSample = new PausableSkinnableSound
|
||||||
{
|
{
|
||||||
Volume = { Value = 0 },
|
Volume = { Value = 0 },
|
||||||
Looping = true,
|
Looping = true,
|
||||||
Frequency = { Value = spinning_sample_initial_frequency }
|
Frequency = { Value = spinning_sample_initial_frequency }
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
||||||
}
|
}
|
||||||
@ -161,17 +164,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateStartTimeStateTransforms()
|
|
||||||
{
|
|
||||||
base.UpdateStartTimeStateTransforms();
|
|
||||||
|
|
||||||
if (Result?.TimeStarted is double startTime)
|
|
||||||
{
|
|
||||||
using (BeginAbsoluteSequence(startTime))
|
|
||||||
fadeInCounter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
{
|
{
|
||||||
base.UpdateHitStateTransforms(state);
|
base.UpdateHitStateTransforms(state);
|
||||||
@ -282,22 +274,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
if (Result.TimeStarted == null && RotationTracker.Tracking)
|
||||||
{
|
Result.TimeStarted = Time.Current;
|
||||||
Result.TimeStarted ??= Time.Current;
|
|
||||||
fadeInCounter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't update after end time to avoid the rate display dropping during fade out.
|
// don't update after end time to avoid the rate display dropping during fade out.
|
||||||
// this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period.
|
// this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period.
|
||||||
if (Time.Current <= HitObject.EndTime)
|
if (Time.Current <= HitObject.EndTime)
|
||||||
SpmCounter.SetRotation(Result.RateAdjustedRotation);
|
spmCalculator.SetRotation(Result.RateAdjustedRotation);
|
||||||
|
|
||||||
updateBonusScore();
|
updateBonusScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
|
||||||
|
|
||||||
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
|
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
|
||||||
|
|
||||||
private int wholeSpins;
|
private int wholeSpins;
|
||||||
|
@ -185,6 +185,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
||||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||||
new OsuModTraceable(),
|
new OsuModTraceable(),
|
||||||
|
new OsuModBarrelRoll(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
@ -206,6 +207,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
||||||
|
|
||||||
|
public override IBeatmapVerifier CreateBeatmapVerifier() => new OsuBeatmapVerifier();
|
||||||
|
|
||||||
public override string Description => "osu!";
|
public override string Description => "osu!";
|
||||||
|
|
||||||
public override string ShortName => SHORT_NAME;
|
public override string ShortName => SHORT_NAME;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
private OsuSpriteText bonusCounter;
|
private OsuSpriteText bonusCounter;
|
||||||
|
|
||||||
|
private Container spmContainer;
|
||||||
|
private OsuSpriteText spmCounter;
|
||||||
|
|
||||||
public DefaultSpinner()
|
public DefaultSpinner()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -46,11 +50,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.Numeric.With(size: 24),
|
Font = OsuFont.Numeric.With(size: 24),
|
||||||
Y = -120,
|
Y = -120,
|
||||||
|
},
|
||||||
|
spmContainer = new Container
|
||||||
|
{
|
||||||
|
Alpha = 0f,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 120,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
spmCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"0",
|
||||||
|
Font = OsuFont.Numeric.With(size: 24)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"SPINS PER MINUTE",
|
||||||
|
Font = OsuFont.Numeric.With(size: 12),
|
||||||
|
Y = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<double> gainedBonus;
|
private IBindable<double> gainedBonus;
|
||||||
|
private IBindable<double> spinsPerMinute;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
@ -63,6 +93,40 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
bonusCounter.FadeOutFromOne(1500);
|
bonusCounter.FadeOutFromOne(1500);
|
||||||
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
|
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy();
|
||||||
|
spinsPerMinute.BindValueChanged(spm =>
|
||||||
|
{
|
||||||
|
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
fadeCounterOnTimeStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fadeCounterOnTimeStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fadeCounterOnTimeStart()
|
||||||
|
{
|
||||||
|
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||||
|
{
|
||||||
|
using (BeginAbsoluteSequence(startTime))
|
||||||
|
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,77 +1,37 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
public class SpinnerSpmCounter : Container
|
public class SpinnerSpmCalculator : Component
|
||||||
{
|
{
|
||||||
|
private readonly Queue<RotationRecord> records = new Queue<RotationRecord>();
|
||||||
|
private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resultant spins per minute value, which is updated via <see cref="SetRotation"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<double> Result => result;
|
||||||
|
|
||||||
|
private readonly Bindable<double> result = new BindableDouble();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableSpinner { get; set; }
|
private DrawableHitObject drawableSpinner { get; set; }
|
||||||
|
|
||||||
private readonly OsuSpriteText spmText;
|
|
||||||
|
|
||||||
public SpinnerSpmCounter()
|
|
||||||
{
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
spmText = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = @"0",
|
|
||||||
Font = OsuFont.Numeric.With(size: 24)
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = @"SPINS PER MINUTE",
|
|
||||||
Font = OsuFont.Numeric.With(size: 12),
|
|
||||||
Y = 30
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
drawableSpinner.HitObjectApplied += resetState;
|
drawableSpinner.HitObjectApplied += resetState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double spm;
|
|
||||||
|
|
||||||
public double SpinsPerMinute
|
|
||||||
{
|
|
||||||
get => spm;
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
if (value == spm) return;
|
|
||||||
|
|
||||||
spm = value;
|
|
||||||
spmText.Text = Math.Truncate(value).ToString(@"#0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct RotationRecord
|
|
||||||
{
|
|
||||||
public float Rotation;
|
|
||||||
public double Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Queue<RotationRecord> records = new Queue<RotationRecord>();
|
|
||||||
private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues
|
|
||||||
|
|
||||||
public void SetRotation(float currentRotation)
|
public void SetRotation(float currentRotation)
|
||||||
{
|
{
|
||||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||||
@ -88,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
|
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
|
||||||
record = records.Dequeue();
|
record = records.Dequeue();
|
||||||
|
|
||||||
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||||
@ -96,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
private void resetState(DrawableHitObject hitObject)
|
private void resetState(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
SpinsPerMinute = 0;
|
result.Value = 0;
|
||||||
records.Clear();
|
records.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,5 +67,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
if (drawableSpinner != null)
|
if (drawableSpinner != null)
|
||||||
drawableSpinner.HitObjectApplied -= resetState;
|
drawableSpinner.HitObjectApplied -= resetState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct RotationRecord
|
||||||
|
{
|
||||||
|
public float Rotation;
|
||||||
|
public double Time;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
|
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
|
||||||
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
@ -32,13 +33,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursor"),
|
Texture = skin.GetTexture("cursor"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = centre ? Anchor.Centre : Anchor.TopLeft,
|
||||||
},
|
},
|
||||||
new NonPlayfieldSprite
|
new NonPlayfieldSprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursormiddle"),
|
Texture = skin.GetTexture("cursormiddle"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = centre ? Anchor.Centre : Anchor.TopLeft,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Texture = skin.GetTexture("cursortrail");
|
Texture = skin.GetTexture("cursortrail");
|
||||||
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
||||||
|
|
||||||
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
|
if (disjointTrail)
|
||||||
|
{
|
||||||
|
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
|
||||||
|
|
||||||
|
TrailOrigin = centre ? Anchor.Centre : Anchor.TopLeft;
|
||||||
|
Blending = BlendingParameters.Inherit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
}
|
||||||
|
|
||||||
if (Texture != null)
|
if (Texture != null)
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
protected const float SPRITE_SCALE = 0.625f;
|
protected const float SPRITE_SCALE = 0.625f;
|
||||||
|
|
||||||
|
private const float spm_hide_offset = 50f;
|
||||||
|
|
||||||
protected DrawableSpinner DrawableSpinner { get; private set; }
|
protected DrawableSpinner DrawableSpinner { get; private set; }
|
||||||
|
|
||||||
private Sprite spin;
|
private Sprite spin;
|
||||||
@ -35,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
private LegacySpriteText bonusCounter;
|
private LegacySpriteText bonusCounter;
|
||||||
|
|
||||||
|
private Sprite spmBackground;
|
||||||
|
private LegacySpriteText spmCounter;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableHitObject, ISkinSource source)
|
private void load(DrawableHitObject drawableHitObject, ISkinSource source)
|
||||||
{
|
{
|
||||||
@ -79,11 +84,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Scale = new Vector2(SPRITE_SCALE),
|
Scale = new Vector2(SPRITE_SCALE),
|
||||||
Y = SPINNER_TOP_OFFSET + 299,
|
Y = SPINNER_TOP_OFFSET + 299,
|
||||||
}.With(s => s.Font = s.Font.With(fixedWidth: false)),
|
}.With(s => s.Font = s.Font.With(fixedWidth: false)),
|
||||||
|
spmBackground = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Texture = source.GetTexture("spinner-rpm"),
|
||||||
|
Scale = new Vector2(SPRITE_SCALE),
|
||||||
|
Position = new Vector2(-87, 445 + spm_hide_offset),
|
||||||
|
},
|
||||||
|
spmCounter = new LegacySpriteText(source, LegacyFont.Score)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Scale = new Vector2(SPRITE_SCALE * 0.9f),
|
||||||
|
Position = new Vector2(80, 448 + spm_hide_offset),
|
||||||
|
}.With(s => s.Font = s.Font.With(fixedWidth: false)),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<double> gainedBonus;
|
private IBindable<double> gainedBonus;
|
||||||
|
private IBindable<double> spinsPerMinute;
|
||||||
|
|
||||||
private readonly Bindable<bool> completed = new Bindable<bool>();
|
private readonly Bindable<bool> completed = new Bindable<bool>();
|
||||||
|
|
||||||
@ -99,6 +120,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out);
|
bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
spinsPerMinute = DrawableSpinner.SpinsPerMinute.GetBoundCopy();
|
||||||
|
spinsPerMinute.BindValueChanged(spm =>
|
||||||
|
{
|
||||||
|
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||||
|
}, true);
|
||||||
|
|
||||||
completed.BindValueChanged(onCompletedChanged, true);
|
completed.BindValueChanged(onCompletedChanged, true);
|
||||||
|
|
||||||
DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms;
|
DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms;
|
||||||
@ -142,10 +169,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
switch (drawableHitObject)
|
switch (drawableHitObject)
|
||||||
{
|
{
|
||||||
case DrawableSpinner d:
|
case DrawableSpinner d:
|
||||||
double fadeOutLength = Math.Min(400, d.HitObject.Duration);
|
using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn))
|
||||||
|
{
|
||||||
|
spmBackground.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
||||||
|
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - fadeOutLength, true))
|
double spinFadeOutLength = Math.Min(400, d.HitObject.Duration);
|
||||||
spin.FadeOutFromOne(fadeOutLength);
|
|
||||||
|
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true))
|
||||||
|
spin.FadeOutFromOne(spinFadeOutLength);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSpinnerTick d:
|
case DrawableSpinnerTick d:
|
||||||
|
@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
SliderBorderSize,
|
SliderBorderSize,
|
||||||
SliderPathRadius,
|
SliderPathRadius,
|
||||||
AllowSliderBallTint,
|
AllowSliderBallTint,
|
||||||
|
CursorCentre,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
CursorRotate,
|
CursorRotate,
|
||||||
HitCircleOverlayAboveNumber,
|
HitCircleOverlayAboveNumber,
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Batches;
|
using osu.Framework.Graphics.Batches;
|
||||||
using osu.Framework.Graphics.OpenGL.Vertices;
|
using osu.Framework.Graphics.OpenGL.Vertices;
|
||||||
@ -31,6 +32,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private double timeOffset;
|
private double timeOffset;
|
||||||
private float time;
|
private float time;
|
||||||
|
|
||||||
|
private Anchor trailOrigin = Anchor.Centre;
|
||||||
|
|
||||||
|
protected Anchor TrailOrigin
|
||||||
|
{
|
||||||
|
get => trailOrigin;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
trailOrigin = value;
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public CursorTrail()
|
public CursorTrail()
|
||||||
{
|
{
|
||||||
// as we are currently very dependent on having a running clock, let's make our own clock for the time being.
|
// as we are currently very dependent on having a running clock, let's make our own clock for the time being.
|
||||||
@ -197,6 +210,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
|
|
||||||
|
private Vector2 originPosition;
|
||||||
|
|
||||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
|
|
||||||
public TrailDrawNode(CursorTrail source)
|
public TrailDrawNode(CursorTrail source)
|
||||||
@ -213,6 +228,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
size = Source.partSize;
|
size = Source.partSize;
|
||||||
time = Source.time;
|
time = Source.time;
|
||||||
|
|
||||||
|
originPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
if (Source.TrailOrigin.HasFlagFast(Anchor.x1))
|
||||||
|
originPosition.X = 0.5f;
|
||||||
|
else if (Source.TrailOrigin.HasFlagFast(Anchor.x2))
|
||||||
|
originPosition.X = 1f;
|
||||||
|
|
||||||
|
if (Source.TrailOrigin.HasFlagFast(Anchor.y1))
|
||||||
|
originPosition.Y = 0.5f;
|
||||||
|
else if (Source.TrailOrigin.HasFlagFast(Anchor.y2))
|
||||||
|
originPosition.Y = 1f;
|
||||||
|
|
||||||
Source.parts.CopyTo(parts, 0);
|
Source.parts.CopyTo(parts, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
|
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)),
|
||||||
TexturePosition = textureRect.BottomLeft,
|
TexturePosition = textureRect.BottomLeft,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
||||||
@ -246,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
|
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)),
|
||||||
TexturePosition = textureRect.BottomRight,
|
TexturePosition = textureRect.BottomRight,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
||||||
@ -255,7 +282,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
|
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y),
|
||||||
TexturePosition = textureRect.TopRight,
|
TexturePosition = textureRect.TopRight,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
||||||
@ -264,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
|
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y),
|
||||||
TexturePosition = textureRect.TopLeft,
|
TexturePosition = textureRect.TopLeft,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
||||||
|
@ -42,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public OsuPlayfield()
|
public OsuPlayfield()
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||||
|
@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
Add(cursorScaleContainer = new Container
|
Add(cursorScaleContainer = new Container
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.Both,
|
|
||||||
Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }
|
Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
67
osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs
Normal file
67
osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckBackgroundTest
|
||||||
|
{
|
||||||
|
private CheckBackground check;
|
||||||
|
private IBeatmap beatmap;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckBackground();
|
||||||
|
beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Files = new List<BeatmapSetFileInfo>(new[]
|
||||||
|
{
|
||||||
|
new BeatmapSetFileInfo { Filename = "abc123.jpg" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundSetAndInFiles()
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundSetAndNotInFiles()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||||
|
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundNotSet()
|
||||||
|
{
|
||||||
|
beatmap.Metadata.BackgroundFile = null;
|
||||||
|
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
Normal file
36
osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Mods
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ModSettingsEqualityComparison
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Test()
|
||||||
|
{
|
||||||
|
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
|
||||||
|
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||||
|
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||||
|
var apiMod1 = new APIMod(mod1);
|
||||||
|
var apiMod2 = new APIMod(mod2);
|
||||||
|
var apiMod3 = new APIMod(mod3);
|
||||||
|
|
||||||
|
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||||
|
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||||
|
|
||||||
|
Assert.That(mod2, Is.EqualTo(mod2));
|
||||||
|
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||||
|
|
||||||
|
Assert.That(mod2, Is.EqualTo(mod3));
|
||||||
|
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||||
|
|
||||||
|
Assert.That(mod3, Is.EqualTo(mod2));
|
||||||
|
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -144,6 +144,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
public override string Name => nameof(ModA);
|
public override string Name => nameof(ModA);
|
||||||
public override string Acronym => nameof(ModA);
|
public override string Acronym => nameof(ModA);
|
||||||
|
public override string Description => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
|
||||||
@ -152,6 +153,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
private class ModB : Mod
|
private class ModB : Mod
|
||||||
{
|
{
|
||||||
public override string Name => nameof(ModB);
|
public override string Name => nameof(ModB);
|
||||||
|
public override string Description => string.Empty;
|
||||||
public override string Acronym => nameof(ModB);
|
public override string Acronym => nameof(ModB);
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
@ -162,6 +164,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
public override string Name => nameof(ModC);
|
public override string Name => nameof(ModC);
|
||||||
public override string Acronym => nameof(ModC);
|
public override string Acronym => nameof(ModC);
|
||||||
|
public override string Description => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +172,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
public override string Name => $"Incompatible With {nameof(ModA)}";
|
public override string Name => $"Incompatible With {nameof(ModA)}";
|
||||||
public override string Acronym => $"Incompatible With {nameof(ModA)}";
|
public override string Acronym => $"Incompatible With {nameof(ModA)}";
|
||||||
|
public override string Description => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModA) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModA) };
|
||||||
@ -187,6 +191,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
|
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
|
||||||
public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
|
public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
|
||||||
|
public override string Description => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
|
||||||
|
@ -20,27 +20,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
handler = new TestInputHandler(replay = new Replay
|
handler = new TestInputHandler(replay = new Replay
|
||||||
{
|
{
|
||||||
Frames = new List<ReplayFrame>
|
HasReceivedAllFrames = false
|
||||||
{
|
|
||||||
new TestReplayFrame(0),
|
|
||||||
new TestReplayFrame(1000),
|
|
||||||
new TestReplayFrame(2000),
|
|
||||||
new TestReplayFrame(3000, true),
|
|
||||||
new TestReplayFrame(4000, true),
|
|
||||||
new TestReplayFrame(5000, true),
|
|
||||||
new TestReplayFrame(7000, true),
|
|
||||||
new TestReplayFrame(8000),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNormalPlayback()
|
public void TestNormalPlayback()
|
||||||
{
|
{
|
||||||
Assert.IsNull(handler.CurrentFrame);
|
setReplayFrames();
|
||||||
|
|
||||||
confirmCurrentFrame(null);
|
|
||||||
confirmNextFrame(0);
|
|
||||||
|
|
||||||
setTime(0, 0);
|
setTime(0, 0);
|
||||||
confirmCurrentFrame(0);
|
confirmCurrentFrame(0);
|
||||||
@ -107,6 +94,8 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestIntroTime()
|
public void TestIntroTime()
|
||||||
{
|
{
|
||||||
|
setReplayFrames();
|
||||||
|
|
||||||
setTime(-1000, -1000);
|
setTime(-1000, -1000);
|
||||||
confirmCurrentFrame(null);
|
confirmCurrentFrame(null);
|
||||||
confirmNextFrame(0);
|
confirmNextFrame(0);
|
||||||
@ -123,6 +112,8 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBasicRewind()
|
public void TestBasicRewind()
|
||||||
{
|
{
|
||||||
|
setReplayFrames();
|
||||||
|
|
||||||
setTime(2800, 0);
|
setTime(2800, 0);
|
||||||
setTime(2800, 1000);
|
setTime(2800, 1000);
|
||||||
setTime(2800, 2000);
|
setTime(2800, 2000);
|
||||||
@ -133,34 +124,35 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
// pivot without crossing a frame boundary
|
// pivot without crossing a frame boundary
|
||||||
setTime(2700, 2700);
|
setTime(2700, 2700);
|
||||||
confirmCurrentFrame(2);
|
confirmCurrentFrame(2);
|
||||||
confirmNextFrame(1);
|
confirmNextFrame(3);
|
||||||
|
|
||||||
// cross current frame boundary; should not yet update frame
|
// cross current frame boundary
|
||||||
setTime(1980, 1980);
|
setTime(1980, 2000);
|
||||||
confirmCurrentFrame(2);
|
confirmCurrentFrame(2);
|
||||||
confirmNextFrame(1);
|
confirmNextFrame(3);
|
||||||
|
|
||||||
setTime(1200, 1200);
|
setTime(1200, 1200);
|
||||||
confirmCurrentFrame(2);
|
confirmCurrentFrame(1);
|
||||||
confirmNextFrame(1);
|
confirmNextFrame(2);
|
||||||
|
|
||||||
// ensure each frame plays out until start
|
// ensure each frame plays out until start
|
||||||
setTime(-500, 1000);
|
setTime(-500, 1000);
|
||||||
confirmCurrentFrame(1);
|
confirmCurrentFrame(1);
|
||||||
confirmNextFrame(0);
|
confirmNextFrame(2);
|
||||||
|
|
||||||
setTime(-500, 0);
|
setTime(-500, 0);
|
||||||
confirmCurrentFrame(0);
|
confirmCurrentFrame(0);
|
||||||
confirmNextFrame(null);
|
confirmNextFrame(1);
|
||||||
|
|
||||||
setTime(-500, -500);
|
setTime(-500, -500);
|
||||||
confirmCurrentFrame(0);
|
confirmCurrentFrame(null);
|
||||||
confirmNextFrame(null);
|
confirmNextFrame(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRewindInsideImportantSection()
|
public void TestRewindInsideImportantSection()
|
||||||
{
|
{
|
||||||
|
setReplayFrames();
|
||||||
fastForwardToPoint(3000);
|
fastForwardToPoint(3000);
|
||||||
|
|
||||||
setTime(4000, 4000);
|
setTime(4000, 4000);
|
||||||
@ -168,12 +160,12 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
confirmNextFrame(5);
|
confirmNextFrame(5);
|
||||||
|
|
||||||
setTime(3500, null);
|
setTime(3500, null);
|
||||||
confirmCurrentFrame(4);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(3);
|
confirmNextFrame(4);
|
||||||
|
|
||||||
setTime(3000, 3000);
|
setTime(3000, 3000);
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(2);
|
confirmNextFrame(4);
|
||||||
|
|
||||||
setTime(3500, null);
|
setTime(3500, null);
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(3);
|
||||||
@ -187,46 +179,127 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
confirmCurrentFrame(4);
|
confirmCurrentFrame(4);
|
||||||
confirmNextFrame(5);
|
confirmNextFrame(5);
|
||||||
|
|
||||||
setTime(4000, null);
|
setTime(4000, 4000);
|
||||||
confirmCurrentFrame(4);
|
confirmCurrentFrame(4);
|
||||||
confirmNextFrame(5);
|
confirmNextFrame(5);
|
||||||
|
|
||||||
setTime(3500, null);
|
setTime(3500, null);
|
||||||
confirmCurrentFrame(4);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(3);
|
confirmNextFrame(4);
|
||||||
|
|
||||||
setTime(3000, 3000);
|
setTime(3000, 3000);
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(2);
|
confirmNextFrame(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRewindOutOfImportantSection()
|
public void TestRewindOutOfImportantSection()
|
||||||
{
|
{
|
||||||
|
setReplayFrames();
|
||||||
fastForwardToPoint(3500);
|
fastForwardToPoint(3500);
|
||||||
|
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(4);
|
confirmNextFrame(4);
|
||||||
|
|
||||||
setTime(3200, null);
|
setTime(3200, null);
|
||||||
// next frame doesn't change even though direction reversed, because of important section.
|
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(4);
|
confirmNextFrame(4);
|
||||||
|
|
||||||
setTime(3000, null);
|
setTime(3000, 3000);
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(3);
|
||||||
confirmNextFrame(4);
|
confirmNextFrame(4);
|
||||||
|
|
||||||
setTime(2800, 2800);
|
setTime(2800, 2800);
|
||||||
confirmCurrentFrame(3);
|
confirmCurrentFrame(2);
|
||||||
confirmNextFrame(2);
|
confirmNextFrame(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReplayStreaming()
|
||||||
|
{
|
||||||
|
// no frames are arrived yet
|
||||||
|
setTime(0, null);
|
||||||
|
setTime(1000, null);
|
||||||
|
Assert.IsTrue(handler.WaitingForFrame, "Should be waiting for the first frame");
|
||||||
|
|
||||||
|
replay.Frames.Add(new TestReplayFrame(0));
|
||||||
|
replay.Frames.Add(new TestReplayFrame(1000));
|
||||||
|
|
||||||
|
// should always play from beginning
|
||||||
|
setTime(1000, 0);
|
||||||
|
confirmCurrentFrame(0);
|
||||||
|
Assert.IsFalse(handler.WaitingForFrame, "Should not be waiting yet");
|
||||||
|
setTime(1000, 1000);
|
||||||
|
confirmCurrentFrame(1);
|
||||||
|
confirmNextFrame(null);
|
||||||
|
Assert.IsTrue(handler.WaitingForFrame, "Should be waiting");
|
||||||
|
|
||||||
|
// cannot seek beyond the last frame
|
||||||
|
setTime(1500, null);
|
||||||
|
confirmCurrentFrame(1);
|
||||||
|
|
||||||
|
setTime(-100, 0);
|
||||||
|
confirmCurrentFrame(0);
|
||||||
|
|
||||||
|
// can seek to the point before the first frame, however
|
||||||
|
setTime(-100, -100);
|
||||||
|
confirmCurrentFrame(null);
|
||||||
|
confirmNextFrame(0);
|
||||||
|
|
||||||
|
fastForwardToPoint(1000);
|
||||||
|
setTime(3000, null);
|
||||||
|
replay.Frames.Add(new TestReplayFrame(2000));
|
||||||
|
confirmCurrentFrame(1);
|
||||||
|
setTime(1000, 1000);
|
||||||
|
setTime(3000, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleFramesSameTime()
|
||||||
|
{
|
||||||
|
replay.Frames.Add(new TestReplayFrame(0));
|
||||||
|
replay.Frames.Add(new TestReplayFrame(0));
|
||||||
|
replay.Frames.Add(new TestReplayFrame(1000));
|
||||||
|
replay.Frames.Add(new TestReplayFrame(1000));
|
||||||
|
replay.Frames.Add(new TestReplayFrame(2000));
|
||||||
|
|
||||||
|
// forward direction is prioritized when multiple frames have the same time.
|
||||||
|
setTime(0, 0);
|
||||||
|
setTime(0, 0);
|
||||||
|
|
||||||
|
setTime(2000, 1000);
|
||||||
|
setTime(2000, 1000);
|
||||||
|
|
||||||
|
setTime(1000, 1000);
|
||||||
|
setTime(1000, 1000);
|
||||||
|
setTime(-100, 1000);
|
||||||
|
setTime(-100, 0);
|
||||||
|
setTime(-100, 0);
|
||||||
|
setTime(-100, -100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReplayFrames()
|
||||||
|
{
|
||||||
|
replay.Frames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TestReplayFrame(0),
|
||||||
|
new TestReplayFrame(1000),
|
||||||
|
new TestReplayFrame(2000),
|
||||||
|
new TestReplayFrame(3000, true),
|
||||||
|
new TestReplayFrame(4000, true),
|
||||||
|
new TestReplayFrame(5000, true),
|
||||||
|
new TestReplayFrame(7000, true),
|
||||||
|
new TestReplayFrame(8000),
|
||||||
|
};
|
||||||
|
replay.HasReceivedAllFrames = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fastForwardToPoint(double destination)
|
private void fastForwardToPoint(double destination)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
if (handler.SetFrameFromTime(destination) == null)
|
var time = handler.SetFrameFromTime(destination);
|
||||||
|
if (time == null || time == destination)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,33 +308,17 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
private void setTime(double set, double? expect)
|
private void setTime(double set, double? expect)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
|
Assert.AreEqual(expect, handler.SetFrameFromTime(set), "Unexpected return value");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmCurrentFrame(int? frame)
|
private void confirmCurrentFrame(int? frame)
|
||||||
{
|
{
|
||||||
if (frame.HasValue)
|
Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame");
|
||||||
{
|
|
||||||
Assert.IsNotNull(handler.CurrentFrame);
|
|
||||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.IsNull(handler.CurrentFrame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmNextFrame(int? frame)
|
private void confirmNextFrame(int? frame)
|
||||||
{
|
{
|
||||||
if (frame.HasValue)
|
Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame");
|
||||||
{
|
|
||||||
Assert.IsNotNull(handler.NextFrame);
|
|
||||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.IsNull(handler.NextFrame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestReplayFrame : ReplayFrame
|
private class TestReplayFrame : ReplayFrame
|
||||||
|
@ -1,296 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Game.Replays;
|
|
||||||
using osu.Game.Rulesets.Replays;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.NonVisual
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class StreamingFramedReplayInputHandlerTest
|
|
||||||
{
|
|
||||||
private Replay replay;
|
|
||||||
private TestInputHandler handler;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp()
|
|
||||||
{
|
|
||||||
handler = new TestInputHandler(replay = new Replay
|
|
||||||
{
|
|
||||||
HasReceivedAllFrames = false,
|
|
||||||
Frames = new List<ReplayFrame>
|
|
||||||
{
|
|
||||||
new TestReplayFrame(0),
|
|
||||||
new TestReplayFrame(1000),
|
|
||||||
new TestReplayFrame(2000),
|
|
||||||
new TestReplayFrame(3000, true),
|
|
||||||
new TestReplayFrame(4000, true),
|
|
||||||
new TestReplayFrame(5000, true),
|
|
||||||
new TestReplayFrame(7000, true),
|
|
||||||
new TestReplayFrame(8000),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNormalPlayback()
|
|
||||||
{
|
|
||||||
Assert.IsNull(handler.CurrentFrame);
|
|
||||||
|
|
||||||
confirmCurrentFrame(null);
|
|
||||||
confirmNextFrame(0);
|
|
||||||
|
|
||||||
setTime(0, 0);
|
|
||||||
confirmCurrentFrame(0);
|
|
||||||
confirmNextFrame(1);
|
|
||||||
|
|
||||||
// if we hit the first frame perfectly, time should progress to it.
|
|
||||||
setTime(1000, 1000);
|
|
||||||
confirmCurrentFrame(1);
|
|
||||||
confirmNextFrame(2);
|
|
||||||
|
|
||||||
// in between non-important frames should progress based on input.
|
|
||||||
setTime(1200, 1200);
|
|
||||||
confirmCurrentFrame(1);
|
|
||||||
|
|
||||||
setTime(1400, 1400);
|
|
||||||
confirmCurrentFrame(1);
|
|
||||||
|
|
||||||
// progressing beyond the next frame should force time to that frame once.
|
|
||||||
setTime(2200, 2000);
|
|
||||||
confirmCurrentFrame(2);
|
|
||||||
|
|
||||||
// second attempt should progress to input time
|
|
||||||
setTime(2200, 2200);
|
|
||||||
confirmCurrentFrame(2);
|
|
||||||
|
|
||||||
// entering important section
|
|
||||||
setTime(3000, 3000);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
|
|
||||||
// cannot progress within
|
|
||||||
setTime(3500, null);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
|
|
||||||
setTime(4000, 4000);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
|
|
||||||
// still cannot progress
|
|
||||||
setTime(4500, null);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
|
|
||||||
setTime(5200, 5000);
|
|
||||||
confirmCurrentFrame(5);
|
|
||||||
|
|
||||||
// important section AllowedImportantTimeSpan allowance
|
|
||||||
setTime(5200, 5200);
|
|
||||||
confirmCurrentFrame(5);
|
|
||||||
|
|
||||||
setTime(7200, 7000);
|
|
||||||
confirmCurrentFrame(6);
|
|
||||||
|
|
||||||
setTime(7200, null);
|
|
||||||
confirmCurrentFrame(6);
|
|
||||||
|
|
||||||
// exited important section
|
|
||||||
setTime(8200, 8000);
|
|
||||||
confirmCurrentFrame(7);
|
|
||||||
confirmNextFrame(null);
|
|
||||||
|
|
||||||
setTime(8200, null);
|
|
||||||
confirmCurrentFrame(7);
|
|
||||||
confirmNextFrame(null);
|
|
||||||
|
|
||||||
setTime(8400, null);
|
|
||||||
confirmCurrentFrame(7);
|
|
||||||
confirmNextFrame(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestIntroTime()
|
|
||||||
{
|
|
||||||
setTime(-1000, -1000);
|
|
||||||
confirmCurrentFrame(null);
|
|
||||||
confirmNextFrame(0);
|
|
||||||
|
|
||||||
setTime(-500, -500);
|
|
||||||
confirmCurrentFrame(null);
|
|
||||||
confirmNextFrame(0);
|
|
||||||
|
|
||||||
setTime(0, 0);
|
|
||||||
confirmCurrentFrame(0);
|
|
||||||
confirmNextFrame(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestBasicRewind()
|
|
||||||
{
|
|
||||||
setTime(2800, 0);
|
|
||||||
setTime(2800, 1000);
|
|
||||||
setTime(2800, 2000);
|
|
||||||
setTime(2800, 2800);
|
|
||||||
confirmCurrentFrame(2);
|
|
||||||
confirmNextFrame(3);
|
|
||||||
|
|
||||||
// pivot without crossing a frame boundary
|
|
||||||
setTime(2700, 2700);
|
|
||||||
confirmCurrentFrame(2);
|
|
||||||
confirmNextFrame(1);
|
|
||||||
|
|
||||||
// cross current frame boundary; should not yet update frame
|
|
||||||
setTime(1980, 1980);
|
|
||||||
confirmCurrentFrame(2);
|
|
||||||
confirmNextFrame(1);
|
|
||||||
|
|
||||||
setTime(1200, 1200);
|
|
||||||
confirmCurrentFrame(2);
|
|
||||||
confirmNextFrame(1);
|
|
||||||
|
|
||||||
// ensure each frame plays out until start
|
|
||||||
setTime(-500, 1000);
|
|
||||||
confirmCurrentFrame(1);
|
|
||||||
confirmNextFrame(0);
|
|
||||||
|
|
||||||
setTime(-500, 0);
|
|
||||||
confirmCurrentFrame(0);
|
|
||||||
confirmNextFrame(null);
|
|
||||||
|
|
||||||
setTime(-500, -500);
|
|
||||||
confirmCurrentFrame(0);
|
|
||||||
confirmNextFrame(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRewindInsideImportantSection()
|
|
||||||
{
|
|
||||||
fastForwardToPoint(3000);
|
|
||||||
|
|
||||||
setTime(4000, 4000);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
confirmNextFrame(5);
|
|
||||||
|
|
||||||
setTime(3500, null);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
confirmNextFrame(3);
|
|
||||||
|
|
||||||
setTime(3000, 3000);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(2);
|
|
||||||
|
|
||||||
setTime(3500, null);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(4);
|
|
||||||
|
|
||||||
setTime(4000, 4000);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
confirmNextFrame(5);
|
|
||||||
|
|
||||||
setTime(4500, null);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
confirmNextFrame(5);
|
|
||||||
|
|
||||||
setTime(4000, null);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
confirmNextFrame(5);
|
|
||||||
|
|
||||||
setTime(3500, null);
|
|
||||||
confirmCurrentFrame(4);
|
|
||||||
confirmNextFrame(3);
|
|
||||||
|
|
||||||
setTime(3000, 3000);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestRewindOutOfImportantSection()
|
|
||||||
{
|
|
||||||
fastForwardToPoint(3500);
|
|
||||||
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(4);
|
|
||||||
|
|
||||||
setTime(3200, null);
|
|
||||||
// next frame doesn't change even though direction reversed, because of important section.
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(4);
|
|
||||||
|
|
||||||
setTime(3000, null);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(4);
|
|
||||||
|
|
||||||
setTime(2800, 2800);
|
|
||||||
confirmCurrentFrame(3);
|
|
||||||
confirmNextFrame(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fastForwardToPoint(double destination)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 1000; i++)
|
|
||||||
{
|
|
||||||
if (handler.SetFrameFromTime(destination) == null)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TimeoutException("Seek was never fulfilled");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTime(double set, double? expect)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void confirmCurrentFrame(int? frame)
|
|
||||||
{
|
|
||||||
if (frame.HasValue)
|
|
||||||
{
|
|
||||||
Assert.IsNotNull(handler.CurrentFrame);
|
|
||||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.IsNull(handler.CurrentFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void confirmNextFrame(int? frame)
|
|
||||||
{
|
|
||||||
if (frame.HasValue)
|
|
||||||
{
|
|
||||||
Assert.IsNotNull(handler.NextFrame);
|
|
||||||
Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.IsNull(handler.NextFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestReplayFrame : ReplayFrame
|
|
||||||
{
|
|
||||||
public readonly bool IsImportant;
|
|
||||||
|
|
||||||
public TestReplayFrame(double time, bool isImportant = false)
|
|
||||||
: base(time)
|
|
||||||
{
|
|
||||||
IsImportant = isImportant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
|
||||||
{
|
|
||||||
public TestInputHandler(Replay replay)
|
|
||||||
: base(replay)
|
|
||||||
{
|
|
||||||
FrameAccuratePlayback = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double AllowedImportantTimeSpan => 1000;
|
|
||||||
|
|
||||||
protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,7 +11,10 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Online
|
namespace osu.Game.Tests.Online
|
||||||
{
|
{
|
||||||
@ -84,6 +87,36 @@ namespace osu.Game.Tests.Online
|
|||||||
Assert.That(converted?.OverallDifficulty.Value, Is.EqualTo(11));
|
Assert.That(converted?.OverallDifficulty.Value, Is.EqualTo(11));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeserialiseScoreInfoWithEmptyMods()
|
||||||
|
{
|
||||||
|
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||||
|
|
||||||
|
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score));
|
||||||
|
|
||||||
|
if (deserialised != null)
|
||||||
|
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
|
Assert.That(deserialised?.Mods.Length, Is.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeserialiseScoreInfoWithCustomModSetting()
|
||||||
|
{
|
||||||
|
var score = new ScoreInfo
|
||||||
|
{
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
|
||||||
|
};
|
||||||
|
|
||||||
|
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score));
|
||||||
|
|
||||||
|
if (deserialised != null)
|
||||||
|
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
|
Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
private class TestRuleset : Ruleset
|
private class TestRuleset : Ruleset
|
||||||
{
|
{
|
||||||
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]
|
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]
|
||||||
@ -107,6 +140,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
public override string Name => "Test Mod";
|
public override string Name => "Test Mod";
|
||||||
public override string Acronym => "TM";
|
public override string Acronym => "TM";
|
||||||
|
public override string Description => "This is a test mod.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Test")]
|
[SettingSource("Test")]
|
||||||
@ -123,6 +157,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
public override string Name => "Test Mod";
|
public override string Name => "Test Mod";
|
||||||
public override string Acronym => "TMTR";
|
public override string Acronym => "TMTR";
|
||||||
|
public override string Description => "This is a test mod.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
|
@ -100,6 +100,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
public override string Name => "Test Mod";
|
public override string Name => "Test Mod";
|
||||||
public override string Acronym => "TM";
|
public override string Acronym => "TM";
|
||||||
|
public override string Description => "This is a test mod.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Test")]
|
[SettingSource("Test")]
|
||||||
@ -116,6 +117,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
public override string Name => "Test Mod";
|
public override string Name => "Test Mod";
|
||||||
public override string Acronym => "TMTR";
|
public override string Acronym => "TMTR";
|
||||||
|
public override string Description => "This is a test mod.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Initial rate", "The starting speed of the track")]
|
[SettingSource("Initial rate", "The starting speed of the track")]
|
||||||
@ -150,6 +152,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
public override string Name => "Test Mod";
|
public override string Name => "Test Mod";
|
||||||
public override string Acronym => "TM";
|
public override string Acronym => "TM";
|
||||||
|
public override string Description => "This is a test mod.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
[SettingSource("Test")]
|
[SettingSource("Test")]
|
||||||
|
@ -65,6 +65,21 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
stack.Push(songSelect = new DummySongSelect());
|
stack.Push(songSelect = new DummySongSelect());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User settings should always be ignored on song select screen.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestUserSettingsIgnoredOnSongSelect()
|
||||||
|
{
|
||||||
|
setupUserSettings();
|
||||||
|
AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
|
||||||
|
AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur());
|
||||||
|
performFullSetup();
|
||||||
|
AddStep("Exit to song select", () => player.Exit());
|
||||||
|
AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
|
||||||
|
AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
|
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -142,9 +157,9 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
{
|
{
|
||||||
performFullSetup();
|
performFullSetup();
|
||||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||||
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
|
AddStep("Disable user dim", () => songSelect.IgnoreUserSettings.Value = true);
|
||||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
|
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
|
||||||
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
|
AddStep("Enable user dim", () => songSelect.IgnoreUserSettings.Value = false);
|
||||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,13 +176,36 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
player.ReplacesBackground.Value = true;
|
player.ReplacesBackground.Value = true;
|
||||||
player.StoryboardEnabled.Value = true;
|
player.StoryboardEnabled.Value = true;
|
||||||
});
|
});
|
||||||
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
|
AddStep("Enable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = false);
|
||||||
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
|
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
|
||||||
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
|
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
|
||||||
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
|
AddStep("Disable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true);
|
||||||
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
|
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardIgnoreUserSettings()
|
||||||
|
{
|
||||||
|
performFullSetup();
|
||||||
|
createFakeStoryboard();
|
||||||
|
AddStep("Enable replacing background", () => player.ReplacesBackground.Value = true);
|
||||||
|
|
||||||
|
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
|
||||||
|
AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
|
||||||
|
|
||||||
|
AddStep("Ignore user settings", () =>
|
||||||
|
{
|
||||||
|
player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
|
||||||
|
player.DimmableStoryboard.IgnoreUserSettings.Value = true;
|
||||||
|
});
|
||||||
|
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
|
||||||
|
AddUntilStep("Background is invisible", () => songSelect.IsBackgroundInvisible());
|
||||||
|
|
||||||
|
AddStep("Disable background replacement", () => player.ReplacesBackground.Value = false);
|
||||||
|
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
|
||||||
|
AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the visual settings container retains dim and blur when pausing
|
/// Check if the visual settings container retains dim and blur when pausing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -204,17 +242,6 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
|
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if background gets undimmed and unblurred when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void TestTransitionOut()
|
|
||||||
{
|
|
||||||
performFullSetup();
|
|
||||||
AddStep("Exit to song select", () => player.Exit());
|
|
||||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
|
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -281,11 +308,11 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
protected override BackgroundScreen CreateBackground()
|
protected override BackgroundScreen CreateBackground()
|
||||||
{
|
{
|
||||||
background = new FadeAccessibleBackground(Beatmap.Value);
|
background = new FadeAccessibleBackground(Beatmap.Value);
|
||||||
DimEnabled.BindTo(background.EnableUserDim);
|
IgnoreUserSettings.BindTo(background.IgnoreUserSettings);
|
||||||
return background;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Bindable<bool> DimEnabled = new Bindable<bool>();
|
public readonly Bindable<bool> IgnoreUserSettings = new Bindable<bool>();
|
||||||
public readonly Bindable<double> DimLevel = new BindableDouble();
|
public readonly Bindable<double> DimLevel = new BindableDouble();
|
||||||
public readonly Bindable<double> BlurLevel = new BindableDouble();
|
public readonly Bindable<double> BlurLevel = new BindableDouble();
|
||||||
|
|
||||||
@ -310,7 +337,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
|
|
||||||
public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
|
public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
|
||||||
|
|
||||||
public bool IsBlurCorrect() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
||||||
|
|
||||||
public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
|
public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
|
||||||
|
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
|
||||||
using osu.Game.Tests.Beatmaps;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
|
||||||
{
|
|
||||||
public class TestSceneEditorQuickDelete : EditorTestScene
|
|
||||||
{
|
|
||||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
|
||||||
|
|
||||||
private BlueprintContainer blueprintContainer
|
|
||||||
=> Editor.ChildrenOfType<BlueprintContainer>().First();
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestQuickDeleteRemovesObject()
|
|
||||||
{
|
|
||||||
var addedObject = new HitCircle { StartTime = 1000 };
|
|
||||||
|
|
||||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
|
||||||
|
|
||||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
|
||||||
|
|
||||||
AddStep("move mouse to object", () =>
|
|
||||||
{
|
|
||||||
var pos = blueprintContainer.ChildrenOfType<HitCirclePiece>().First().ScreenSpaceDrawQuad.Centre;
|
|
||||||
InputManager.MoveMouseTo(pos);
|
|
||||||
});
|
|
||||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
|
||||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
|
||||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
|
||||||
|
|
||||||
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestQuickDeleteRemovesSliderControlPoint()
|
|
||||||
{
|
|
||||||
Slider slider = new Slider { StartTime = 1000 };
|
|
||||||
|
|
||||||
PathControlPoint[] points =
|
|
||||||
{
|
|
||||||
new PathControlPoint(),
|
|
||||||
new PathControlPoint(new Vector2(50, 0)),
|
|
||||||
new PathControlPoint(new Vector2(100, 0))
|
|
||||||
};
|
|
||||||
|
|
||||||
AddStep("add slider", () =>
|
|
||||||
{
|
|
||||||
slider.Path = new SliderPath(points);
|
|
||||||
EditorBeatmap.Add(slider);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
|
||||||
|
|
||||||
AddStep("move mouse to controlpoint", () =>
|
|
||||||
{
|
|
||||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
|
||||||
InputManager.MoveMouseTo(pos);
|
|
||||||
});
|
|
||||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
|
||||||
|
|
||||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
|
||||||
AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2);
|
|
||||||
|
|
||||||
// second click should nuke the object completely.
|
|
||||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
|
||||||
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
|
|
||||||
|
|
||||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
219
osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs
Normal file
219
osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneEditorSelection : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
private BlueprintContainer blueprintContainer
|
||||||
|
=> Editor.ChildrenOfType<BlueprintContainer>().First();
|
||||||
|
|
||||||
|
private void moveMouseToObject(Func<HitObject> targetFunc)
|
||||||
|
{
|
||||||
|
AddStep("move mouse to object", () =>
|
||||||
|
{
|
||||||
|
var pos = blueprintContainer.SelectionBlueprints
|
||||||
|
.First(s => s.HitObject == targetFunc())
|
||||||
|
.ChildrenOfType<HitCirclePiece>()
|
||||||
|
.First().ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(pos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicSelect()
|
||||||
|
{
|
||||||
|
var addedObject = new HitCircle { StartTime = 100 };
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObject);
|
||||||
|
AddStep("left click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
|
||||||
|
|
||||||
|
var addedObject2 = new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 100,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add one more hitobject", () => EditorBeatmap.Add(addedObject2));
|
||||||
|
AddAssert("selection unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObject2);
|
||||||
|
AddStep("left click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultiSelect()
|
||||||
|
{
|
||||||
|
var addedObjects = new[]
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 100 },
|
||||||
|
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||||
|
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||||
|
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObjects[0]);
|
||||||
|
AddStep("click first", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObjects[0]);
|
||||||
|
|
||||||
|
AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObjects[1]);
|
||||||
|
AddStep("click second", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObjects[2]);
|
||||||
|
AddStep("click third", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("3 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 3 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[2]));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObjects[1]);
|
||||||
|
AddStep("click second", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(false)]
|
||||||
|
[TestCase(true)]
|
||||||
|
public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag)
|
||||||
|
{
|
||||||
|
HitCircle[] addedObjects = null;
|
||||||
|
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 100 },
|
||||||
|
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||||
|
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||||
|
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||||
|
}));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObjects[0]);
|
||||||
|
AddStep("click first", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObjects[1]);
|
||||||
|
|
||||||
|
if (alreadySelectedBeforeDrag)
|
||||||
|
AddStep("click second", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("mouse down on second", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1]));
|
||||||
|
|
||||||
|
AddStep("drag to centre", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
|
AddAssert("positions changed", () => addedObjects[0].Position != Vector2.Zero && addedObjects[1].Position != new Vector2(50));
|
||||||
|
|
||||||
|
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||||
|
AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicDeselect()
|
||||||
|
{
|
||||||
|
var addedObject = new HitCircle { StartTime = 100 };
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObject);
|
||||||
|
AddStep("left click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
|
||||||
|
|
||||||
|
AddStep("click away", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("selection lost", () => EditorBeatmap.SelectedHitObjects.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestQuickDeleteRemovesObject()
|
||||||
|
{
|
||||||
|
var addedObject = new HitCircle { StartTime = 1000 };
|
||||||
|
|
||||||
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
|
||||||
|
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||||
|
|
||||||
|
moveMouseToObject(() => addedObject);
|
||||||
|
|
||||||
|
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||||
|
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||||
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||||
|
|
||||||
|
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestQuickDeleteRemovesSliderControlPoint()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(50, 0)),
|
||||||
|
new PathControlPoint(new Vector2(100, 0))
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(points)
|
||||||
|
};
|
||||||
|
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
|
||||||
|
AddStep("move mouse to controlpoint", () =>
|
||||||
|
{
|
||||||
|
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||||
|
InputManager.MoveMouseTo(pos);
|
||||||
|
});
|
||||||
|
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||||
|
|
||||||
|
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||||
|
AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2);
|
||||||
|
|
||||||
|
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||||
|
|
||||||
|
// second click should nuke the object completely.
|
||||||
|
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
|
||||||
|
|
||||||
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -16,12 +15,21 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public class TestSceneEditorSummaryTimeline : EditorClockTestScene
|
public class TestSceneEditorSummaryTimeline : EditorClockTestScene
|
||||||
{
|
{
|
||||||
[Cached(typeof(EditorBeatmap))]
|
[Cached(typeof(EditorBeatmap))]
|
||||||
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
public TestSceneEditorSummaryTimeline()
|
||||||
private void load()
|
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AddStep("create timeline", () =>
|
||||||
|
{
|
||||||
|
// required for track
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
||||||
|
|
||||||
Add(new SummaryTimeline
|
Add(new SummaryTimeline
|
||||||
{
|
{
|
||||||
@ -29,6 +37,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(500, 50)
|
Size = new Vector2(500, 50)
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDisallowZeroDurationObjects()
|
public void TestDisallowZeroDurationObjects()
|
||||||
{
|
{
|
||||||
DragBar dragBar;
|
DragArea dragArea;
|
||||||
|
|
||||||
AddStep("add spinner", () =>
|
AddStep("add spinner", () =>
|
||||||
{
|
{
|
||||||
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
EditorBeatmap.Add(new Spinner
|
EditorBeatmap.Add(new Spinner
|
||||||
{
|
{
|
||||||
Position = new Vector2(256, 256),
|
Position = new Vector2(256, 256),
|
||||||
StartTime = 150,
|
StartTime = 2700,
|
||||||
Duration = 500
|
Duration = 500
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("hold down drag bar", () =>
|
AddStep("hold down drag bar", () =>
|
||||||
{
|
{
|
||||||
// distinguishes between the actual drag bar and its "underlay shadow".
|
// distinguishes between the actual drag bar and its "underlay shadow".
|
||||||
dragBar = this.ChildrenOfType<DragBar>().Single(bar => bar.HandlePositionalInput);
|
dragArea = this.ChildrenOfType<DragArea>().Single(bar => bar.HandlePositionalInput);
|
||||||
InputManager.MoveMouseTo(dragBar);
|
InputManager.MoveMouseTo(dragArea);
|
||||||
InputManager.PressButton(MouseButton.Left);
|
InputManager.PressButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -53,17 +53,21 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
new AudioVisualiser(),
|
new AudioVisualiser(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TimelineArea = new TimelineArea
|
TimelineArea = new TimelineArea(CreateTestComponent())
|
||||||
{
|
{
|
||||||
Child = CreateTestComponent(),
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Size = new Vector2(0.8f, 100),
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Clock.Seek(2500);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract Drawable CreateTestComponent();
|
public abstract Drawable CreateTestComponent();
|
||||||
|
|
||||||
private class AudioVisualiser : CompositeDrawable
|
private class AudioVisualiser : CompositeDrawable
|
||||||
|
@ -25,6 +25,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.PlayerSettings;
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly VolumeOverlay volumeOverlay;
|
private readonly VolumeOverlay volumeOverlay;
|
||||||
|
|
||||||
|
[Cached(typeof(BatteryInfo))]
|
||||||
|
private readonly LocalBatteryInfo batteryInfo = new LocalBatteryInfo();
|
||||||
|
|
||||||
private readonly ChangelogOverlay changelogOverlay;
|
private readonly ChangelogOverlay changelogOverlay;
|
||||||
|
|
||||||
public TestScenePlayerLoader()
|
public TestScenePlayerLoader()
|
||||||
@ -288,6 +292,33 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning
|
||||||
|
[TestCase(true, 0.1, false)] // charging, below cutoff --> no warning
|
||||||
|
[TestCase(false, 0.25, true)] // not charging, at cutoff --> warning
|
||||||
|
public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn)
|
||||||
|
{
|
||||||
|
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).Value = false);
|
||||||
|
|
||||||
|
// set charge status and level
|
||||||
|
AddStep("load player", () => resetPlayer(false, () =>
|
||||||
|
{
|
||||||
|
batteryInfo.SetCharging(isCharging);
|
||||||
|
batteryInfo.SetChargeLevel(chargeLevel);
|
||||||
|
}));
|
||||||
|
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
|
||||||
|
AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0));
|
||||||
|
AddStep("click notification", () =>
|
||||||
|
{
|
||||||
|
var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last();
|
||||||
|
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
|
||||||
|
var notification = flowContainer.First();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(notification);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for player load", () => player.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEpilepsyWarningEarlyExit()
|
public void TestEpilepsyWarningEarlyExit()
|
||||||
{
|
{
|
||||||
@ -321,6 +352,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public override string Name => string.Empty;
|
public override string Name => string.Empty;
|
||||||
public override string Acronym => string.Empty;
|
public override string Acronym => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override string Description => string.Empty;
|
||||||
|
|
||||||
public bool Applied { get; private set; }
|
public bool Applied { get; private set; }
|
||||||
|
|
||||||
@ -348,5 +380,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
throw new TimeoutException();
|
throw new TimeoutException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mutable dummy BatteryInfo class for <see cref="TestScenePlayerLoader.TestLowBatteryNotification"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
private class LocalBatteryInfo : BatteryInfo
|
||||||
|
{
|
||||||
|
private bool isCharging = true;
|
||||||
|
private double chargeLevel = 1;
|
||||||
|
|
||||||
|
public override bool IsCharging => isCharging;
|
||||||
|
|
||||||
|
public override double ChargeLevel => chargeLevel;
|
||||||
|
|
||||||
|
public void SetCharging(bool value)
|
||||||
|
{
|
||||||
|
isCharging = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetChargeLevel(double value)
|
||||||
|
{
|
||||||
|
chargeLevel = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
waitForPlayer();
|
waitForPlayer();
|
||||||
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
|
AddAssert("ensure frames arrived", () => replayHandler.HasFrames);
|
||||||
|
|
||||||
AddUntilStep("wait for frame starvation", () => replayHandler.NextFrame == null);
|
AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame);
|
||||||
checkPaused(true);
|
checkPaused(true);
|
||||||
|
|
||||||
double? pausedTime = null;
|
double? pausedTime = null;
|
||||||
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
sendFrames();
|
sendFrames();
|
||||||
|
|
||||||
AddUntilStep("wait for frame starvation", () => replayHandler.NextFrame == null);
|
AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame);
|
||||||
checkPaused(true);
|
checkPaused(true);
|
||||||
|
|
||||||
AddAssert("time advanced", () => currentFrameStableTime > pausedTime);
|
AddAssert("time advanced", () => currentFrameStableTime > pausedTime);
|
||||||
|
@ -204,8 +204,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replayHandler.NextFrame != null)
|
if (!replayHandler.HasFrames)
|
||||||
{
|
return;
|
||||||
|
|
||||||
var lastFrame = replay.Frames.LastOrDefault();
|
var lastFrame = replay.Frames.LastOrDefault();
|
||||||
|
|
||||||
// this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved).
|
// this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved).
|
||||||
@ -225,7 +226,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
manualClock.CurrentTime = time.Value;
|
manualClock.CurrentTime = time.Value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[TearDownSteps]
|
[TearDownSteps]
|
||||||
public void TearDown()
|
public void TearDown()
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -38,6 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
private OsuConfigManager config;
|
||||||
|
|
||||||
public TestSceneMultiplayerGameplayLeaderboard()
|
public TestSceneMultiplayerGameplayLeaderboard()
|
||||||
{
|
{
|
||||||
base.Content.Children = new Drawable[]
|
base.Content.Children = new Drawable[]
|
||||||
@ -48,6 +51,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
||||||
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
@ -97,6 +106,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users);
|
AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeScoringMode()
|
||||||
|
{
|
||||||
|
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 5);
|
||||||
|
AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic));
|
||||||
|
AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
|
||||||
|
}
|
||||||
|
|
||||||
public class TestMultiplayerStreaming : SpectatorStreamingClient
|
public class TestMultiplayerStreaming : SpectatorStreamingClient
|
||||||
{
|
{
|
||||||
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
||||||
@ -163,7 +180,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
((ISpectatorClient)this).UserSentFrames(userId, new FrameDataBundle(header, Array.Empty<LegacyReplayFrame>()));
|
((ISpectatorClient)this).UserSentFrames(userId, new FrameDataBundle(header, new[] { new LegacyReplayFrame(Time.Current, 0, 0, ReplayButtonState.None) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad);
|
AddUntilStep("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,229 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Online;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
|
using osu.Game.Replays.Legacy;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerSpectatorLeaderboard : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
[Cached(typeof(SpectatorStreamingClient))]
|
||||||
|
private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient();
|
||||||
|
|
||||||
|
[Cached(typeof(UserLookupCache))]
|
||||||
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, ManualClock> clocks = new Dictionary<int, ManualClock>
|
||||||
|
{
|
||||||
|
{ 55, new ManualClock() },
|
||||||
|
{ 56, new ManualClock() }
|
||||||
|
};
|
||||||
|
|
||||||
|
public TestSceneMultiplayerSpectatorLeaderboard()
|
||||||
|
{
|
||||||
|
base.Content.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
streamingClient,
|
||||||
|
lookupCache,
|
||||||
|
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public new void SetUpSteps()
|
||||||
|
{
|
||||||
|
MultiplayerSpectatorLeaderboard leaderboard = null;
|
||||||
|
|
||||||
|
AddStep("reset", () =>
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
foreach (var (userId, clock) in clocks)
|
||||||
|
{
|
||||||
|
streamingClient.EndPlay(userId, 0);
|
||||||
|
clock.CurrentTime = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("create leaderboard", () =>
|
||||||
|
{
|
||||||
|
foreach (var (userId, _) in clocks)
|
||||||
|
streamingClient.StartPlay(userId, 0);
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
|
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
var scoreProcessor = new OsuScoreProcessor();
|
||||||
|
scoreProcessor.ApplyBeatmap(playable);
|
||||||
|
|
||||||
|
LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
|
||||||
|
AddStep("add clock sources", () =>
|
||||||
|
{
|
||||||
|
foreach (var (userId, clock) in clocks)
|
||||||
|
leaderboard.AddClock(userId, clock);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLeaderboardTracksCurrentTime()
|
||||||
|
{
|
||||||
|
AddStep("send frames", () =>
|
||||||
|
{
|
||||||
|
// For user 55, send frames in sets of 1.
|
||||||
|
// For user 56, send frames in sets of 10.
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
streamingClient.SendFrames(55, i, 1);
|
||||||
|
|
||||||
|
if (i % 10 == 0)
|
||||||
|
streamingClient.SendFrames(56, i, 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertCombo(55, 1);
|
||||||
|
assertCombo(56, 10);
|
||||||
|
|
||||||
|
// Advance to a point where only user 55's frame changes.
|
||||||
|
setTime(500);
|
||||||
|
assertCombo(55, 5);
|
||||||
|
assertCombo(56, 10);
|
||||||
|
|
||||||
|
// Advance to a point where both user's frame changes.
|
||||||
|
setTime(1100);
|
||||||
|
assertCombo(55, 11);
|
||||||
|
assertCombo(56, 20);
|
||||||
|
|
||||||
|
// Advance user 56 only to a point where its frame changes.
|
||||||
|
setTime(56, 2100);
|
||||||
|
assertCombo(55, 11);
|
||||||
|
assertCombo(56, 30);
|
||||||
|
|
||||||
|
// Advance both users beyond their last frame
|
||||||
|
setTime(101 * 100);
|
||||||
|
assertCombo(55, 100);
|
||||||
|
assertCombo(56, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoFrames()
|
||||||
|
{
|
||||||
|
assertCombo(55, 0);
|
||||||
|
assertCombo(56, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTime(double time) => AddStep($"set time {time}", () =>
|
||||||
|
{
|
||||||
|
foreach (var (_, clock) in clocks)
|
||||||
|
clock.CurrentTime = time;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void setTime(int userId, double time)
|
||||||
|
=> AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time);
|
||||||
|
|
||||||
|
private void assertCombo(int userId, int expectedCombo)
|
||||||
|
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo);
|
||||||
|
|
||||||
|
private class TestSpectatorStreamingClient : SpectatorStreamingClient
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, int> userBeatmapDictionary = new Dictionary<int, int>();
|
||||||
|
private readonly Dictionary<int, bool> userSentStateDictionary = new Dictionary<int, bool>();
|
||||||
|
|
||||||
|
public TestSpectatorStreamingClient()
|
||||||
|
: base(new DevelopmentEndpointConfiguration())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartPlay(int userId, int beatmapId)
|
||||||
|
{
|
||||||
|
userBeatmapDictionary[userId] = beatmapId;
|
||||||
|
userSentStateDictionary[userId] = false;
|
||||||
|
sendState(userId, beatmapId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndPlay(int userId, int beatmapId)
|
||||||
|
{
|
||||||
|
((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapId,
|
||||||
|
RulesetID = 0,
|
||||||
|
});
|
||||||
|
userSentStateDictionary[userId] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendFrames(int userId, int index, int count)
|
||||||
|
{
|
||||||
|
var frames = new List<LegacyReplayFrame>();
|
||||||
|
|
||||||
|
for (int i = index; i < index + count; i++)
|
||||||
|
{
|
||||||
|
var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1;
|
||||||
|
frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
|
||||||
|
}
|
||||||
|
|
||||||
|
var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames);
|
||||||
|
((ISpectatorClient)this).UserSentFrames(userId, bundle);
|
||||||
|
if (!userSentStateDictionary[userId])
|
||||||
|
sendState(userId, userBeatmapDictionary[userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WatchUser(int userId)
|
||||||
|
{
|
||||||
|
if (userSentStateDictionary[userId])
|
||||||
|
{
|
||||||
|
// usually the server would do this.
|
||||||
|
sendState(userId, userBeatmapDictionary[userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.WatchUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendState(int userId, int beatmapId)
|
||||||
|
{
|
||||||
|
((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapId,
|
||||||
|
RulesetID = 0,
|
||||||
|
});
|
||||||
|
userSentStateDictionary[userId] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestUserLookupCache : UserLookupCache
|
||||||
|
{
|
||||||
|
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new User
|
||||||
|
{
|
||||||
|
Id = lookup,
|
||||||
|
Username = $"User {lookup}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerSpectatorPlayerGrid : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private PlayerGrid grid;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = grid = new PlayerGrid { RelativeSizeAxes = Axes.Both };
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMaximiseAndMinimise()
|
||||||
|
{
|
||||||
|
addCells(2);
|
||||||
|
|
||||||
|
assertMaximisation(0, false, true);
|
||||||
|
assertMaximisation(1, false, true);
|
||||||
|
|
||||||
|
clickCell(0);
|
||||||
|
assertMaximisation(0, true);
|
||||||
|
assertMaximisation(1, false, true);
|
||||||
|
clickCell(0);
|
||||||
|
assertMaximisation(0, false);
|
||||||
|
assertMaximisation(1, false, true);
|
||||||
|
|
||||||
|
clickCell(1);
|
||||||
|
assertMaximisation(1, true);
|
||||||
|
assertMaximisation(0, false, true);
|
||||||
|
clickCell(1);
|
||||||
|
assertMaximisation(1, false);
|
||||||
|
assertMaximisation(0, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClickBothCellsSimultaneously()
|
||||||
|
{
|
||||||
|
addCells(2);
|
||||||
|
|
||||||
|
AddStep("click cell 0 then 1", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(grid.Content.ElementAt(0));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(grid.Content.ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertMaximisation(1, true);
|
||||||
|
assertMaximisation(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(2)]
|
||||||
|
[TestCase(3)]
|
||||||
|
[TestCase(4)]
|
||||||
|
[TestCase(5)]
|
||||||
|
[TestCase(9)]
|
||||||
|
[TestCase(11)]
|
||||||
|
[TestCase(12)]
|
||||||
|
[TestCase(15)]
|
||||||
|
[TestCase(16)]
|
||||||
|
public void TestCellCount(int count)
|
||||||
|
{
|
||||||
|
addCells(count);
|
||||||
|
AddWaitStep("wait for display", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCells(int count) => AddStep($"add {count} grid cells", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
grid.Add(new GridContent());
|
||||||
|
});
|
||||||
|
|
||||||
|
private void clickCell(int index) => AddStep($"click cell index {index}", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(grid.Content.ElementAt(index));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void assertMaximisation(int index, bool shouldBeMaximised, bool instant = false)
|
||||||
|
{
|
||||||
|
string assertionText = $"cell index {index} {(shouldBeMaximised ? "is" : "is not")} maximised";
|
||||||
|
|
||||||
|
if (instant)
|
||||||
|
AddAssert(assertionText, checkAction);
|
||||||
|
else
|
||||||
|
AddUntilStep(assertionText, checkAction);
|
||||||
|
|
||||||
|
bool checkAction() => Precision.AlmostEquals(grid.MaximisedFacade.DrawSize, grid.Content.ElementAt(index).DrawSize, 10) == shouldBeMaximised;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GridContent : Box
|
||||||
|
{
|
||||||
|
public GridContent()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,13 +19,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
UserHistoryGraph graph;
|
UserHistoryGraph graph;
|
||||||
|
|
||||||
Add(graph = new UserHistoryGraph
|
Add(graph = new UserHistoryGraph("Test")
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 200,
|
Height = 200,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
TooltipCounterName = "Test"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var values = new[]
|
var values = new[]
|
||||||
|
@ -57,6 +57,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private abstract class TestMod : Mod, IApplicableMod
|
private abstract class TestMod : Mod, IApplicableMod
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.0;
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
|
||||||
|
public override string Description => "This is a test mod.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,6 +226,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.0;
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
|
||||||
|
public override string Description => "This is a customisable test mod.";
|
||||||
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
[SettingSource("Sample float", "Change something for a mod")]
|
[SettingSource("Sample float", "Change something for a mod")]
|
||||||
|
@ -141,7 +141,6 @@ namespace osu.Game.Tournament
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add missing player info based on user IDs.
|
/// Add missing player info based on user IDs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
private bool addPlayers()
|
private bool addPlayers()
|
||||||
{
|
{
|
||||||
bool addedInfo = false;
|
bool addedInfo = false;
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
MaxValue = 10
|
MaxValue = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.GreenDark;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The speed multiplier at this control point.
|
/// The speed multiplier at this control point.
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double default_beat_length = 60000.0 / 60.0;
|
private const double default_beat_length = 60000.0 / 60.0;
|
||||||
|
|
||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.YellowDark;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1;
|
||||||
|
|
||||||
public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
|
public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
|
||||||
{
|
{
|
||||||
|
@ -44,7 +44,6 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap.
|
/// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
IEnumerable<BeatmapStatistic> GetStatistics();
|
IEnumerable<BeatmapStatistic> GetStatistics();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Configuration.Tracking;
|
using osu.Framework.Configuration.Tracking;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -143,7 +142,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
|
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
|
||||||
|
|
||||||
SetDefault(OsuSetting.EditorWaveformOpacity, 1f);
|
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OsuConfigManager(Storage storage)
|
public OsuConfigManager(Storage storage)
|
||||||
@ -169,14 +168,9 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
int combined = (year * 10000) + monthDay;
|
int combined = (year * 10000) + monthDay;
|
||||||
|
|
||||||
if (combined < 20200305)
|
if (combined < 20210413)
|
||||||
{
|
{
|
||||||
// the maximum value of this setting was changed.
|
SetValue(OsuSetting.EditorWaveformOpacity, 0.25f);
|
||||||
// if we don't manually increase this, it causes song select to filter out beatmaps the user expects to see.
|
|
||||||
var maxStars = (BindableDouble)GetOriginalBindable<double>(OsuSetting.DisplayStarsMaximum);
|
|
||||||
|
|
||||||
if (maxStars.Value == 10)
|
|
||||||
maxStars.Value = maxStars.MaxValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ namespace osu.Game.Configuration
|
|||||||
{
|
{
|
||||||
SetDefault(Static.LoginOverlayDisplayed, false);
|
SetDefault(Static.LoginOverlayDisplayed, false);
|
||||||
SetDefault(Static.MutedAudioNotificationShownOnce, false);
|
SetDefault(Static.MutedAudioNotificationShownOnce, false);
|
||||||
|
SetDefault(Static.LowBatteryNotificationShownOnce, false);
|
||||||
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
||||||
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
||||||
}
|
}
|
||||||
@ -25,6 +26,7 @@ namespace osu.Game.Configuration
|
|||||||
{
|
{
|
||||||
LoginOverlayDisplayed,
|
LoginOverlayDisplayed,
|
||||||
MutedAudioNotificationShownOnce,
|
MutedAudioNotificationShownOnce,
|
||||||
|
LowBatteryNotificationShownOnce,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Info about seasonal backgrounds available fetched from API - see <see cref="APISeasonalBackgrounds"/>.
|
/// Info about seasonal backgrounds available fetched from API - see <see cref="APISeasonalBackgrounds"/>.
|
||||||
|
@ -22,7 +22,6 @@ namespace osu.Game.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rulesetId">The ruleset's internal ID.</param>
|
/// <param name="rulesetId">The ruleset's internal ID.</param>
|
||||||
/// <param name="variant">An optional variant.</param>
|
/// <param name="variant">An optional variant.</param>
|
||||||
/// <returns></returns>
|
|
||||||
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
|
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
|
||||||
ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ namespace osu.Game.Extensions
|
|||||||
{
|
{
|
||||||
public static class DrawableExtensions
|
public static class DrawableExtensions
|
||||||
{
|
{
|
||||||
|
public const double REPEAT_INTERVAL = 70;
|
||||||
|
public const double INITIAL_DELAY = 250;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper method that is used while <see cref="IKeyBindingHandler"/> doesn't support repetitions of <see cref="IKeyBindingHandler{T}.OnPressed"/>.
|
/// Helper method that is used while <see cref="IKeyBindingHandler"/> doesn't support repetitions of <see cref="IKeyBindingHandler{T}.OnPressed"/>.
|
||||||
/// Simulates repetitions by continually invoking a delegate according to the default key repeat rate.
|
/// Simulates repetitions by continually invoking a delegate according to the default key repeat rate.
|
||||||
@ -19,12 +22,13 @@ namespace osu.Game.Extensions
|
|||||||
/// <param name="handler">The <see cref="IKeyBindingHandler{T}"/> which is handling the repeat.</param>
|
/// <param name="handler">The <see cref="IKeyBindingHandler{T}"/> which is handling the repeat.</param>
|
||||||
/// <param name="scheduler">The <see cref="Scheduler"/> to schedule repetitions on.</param>
|
/// <param name="scheduler">The <see cref="Scheduler"/> to schedule repetitions on.</param>
|
||||||
/// <param name="action">The <see cref="Action"/> to be invoked once immediately and with every repetition.</param>
|
/// <param name="action">The <see cref="Action"/> to be invoked once immediately and with every repetition.</param>
|
||||||
|
/// <param name="initialRepeatDelay">The delay imposed on the first repeat. Defaults to <see cref="INITIAL_DELAY"/>.</param>
|
||||||
/// <returns>A <see cref="ScheduledDelegate"/> which can be cancelled to stop the repeat events from firing.</returns>
|
/// <returns>A <see cref="ScheduledDelegate"/> which can be cancelled to stop the repeat events from firing.</returns>
|
||||||
public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action)
|
public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action, double initialRepeatDelay = INITIAL_DELAY)
|
||||||
{
|
{
|
||||||
action();
|
action();
|
||||||
|
|
||||||
ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + 250, 70);
|
ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + initialRepeatDelay, REPEAT_INTERVAL);
|
||||||
scheduler.Add(repeatDelegate);
|
scheduler.Add(repeatDelegate);
|
||||||
return repeatDelegate;
|
return repeatDelegate;
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,6 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
/// such that the smaller triangles appear on top.
|
/// such that the smaller triangles appear on top.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other"></param>
|
/// <param name="other"></param>
|
||||||
/// <returns></returns>
|
|
||||||
public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale);
|
public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
protected virtual string PopInSampleName => "UI/overlay-pop-in";
|
protected virtual string PopInSampleName => "UI/overlay-pop-in";
|
||||||
protected virtual string PopOutSampleName => "UI/overlay-pop-out";
|
protected virtual string PopOutSampleName => "UI/overlay-pop-out";
|
||||||
|
|
||||||
|
protected override bool BlockScrollInput => false;
|
||||||
|
|
||||||
protected override bool BlockNonPositionalInput => true;
|
protected override bool BlockNonPositionalInput => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,11 +23,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected const double BACKGROUND_FADE_DURATION = 800;
|
protected const double BACKGROUND_FADE_DURATION = 800;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not user-configured dim levels should be applied to the container.
|
|
||||||
/// </summary>
|
|
||||||
public readonly Bindable<bool> EnableUserDim = new Bindable<bool>(true);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not user-configured settings relating to brightness of elements should be ignored
|
/// Whether or not user-configured settings relating to brightness of elements should be ignored
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,7 +52,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0;
|
private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0;
|
||||||
|
|
||||||
protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0);
|
protected float DimLevel => Math.Max(!IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0);
|
||||||
|
|
||||||
protected override Container<Drawable> Content => dimContent;
|
protected override Container<Drawable> Content => dimContent;
|
||||||
|
|
||||||
@ -78,7 +73,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
LightenDuringBreaks = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks);
|
LightenDuringBreaks = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks);
|
||||||
ShowStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
ShowStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
||||||
|
|
||||||
EnableUserDim.ValueChanged += _ => UpdateVisuals();
|
|
||||||
UserDimLevel.ValueChanged += _ => UpdateVisuals();
|
UserDimLevel.ValueChanged += _ => UpdateVisuals();
|
||||||
LightenDuringBreaks.ValueChanged += _ => UpdateVisuals();
|
LightenDuringBreaks.ValueChanged += _ => UpdateVisuals();
|
||||||
IsBreakTime.ValueChanged += _ => UpdateVisuals();
|
IsBreakTime.ValueChanged += _ => UpdateVisuals();
|
||||||
|
@ -186,6 +186,13 @@ namespace osu.Game.Graphics
|
|||||||
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
||||||
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
||||||
|
|
||||||
|
// in latest editor design logic, need to figure out where these sit...
|
||||||
|
public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66");
|
||||||
|
public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966");
|
||||||
|
|
||||||
|
// Content Background
|
||||||
|
public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28");
|
||||||
|
|
||||||
public readonly Color4 RedLighter = Color4Extensions.FromHex(@"ffeded");
|
public readonly Color4 RedLighter = Color4Extensions.FromHex(@"ffeded");
|
||||||
public readonly Color4 RedLight = Color4Extensions.FromHex(@"ed7787");
|
public readonly Color4 RedLight = Color4Extensions.FromHex(@"ed7787");
|
||||||
public readonly Color4 Red = Color4Extensions.FromHex(@"ed1121");
|
public readonly Color4 Red = Color4Extensions.FromHex(@"ed1121");
|
||||||
|
@ -22,7 +22,6 @@ namespace osu.Game.IO.Serialization
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the default <see cref="JsonSerializerSettings"/> that should be used for all <see cref="IJsonSerializable"/>s.
|
/// Creates the default <see cref="JsonSerializerSettings"/> that should be used for all <see cref="IJsonSerializable"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings
|
public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||||
|
@ -70,6 +70,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode),
|
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode),
|
||||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
||||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
||||||
@ -97,9 +98,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
public IEnumerable<KeyBinding> AudioControlKeyBindings => new[]
|
public IEnumerable<KeyBinding> AudioControlKeyBindings => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume),
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume),
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume),
|
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume),
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume),
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume),
|
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute),
|
new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute),
|
||||||
|
|
||||||
@ -249,5 +248,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[Description("Beatmap Options")]
|
[Description("Beatmap Options")]
|
||||||
ToggleBeatmapOptions,
|
ToggleBeatmapOptions,
|
||||||
|
|
||||||
|
[Description("Verify mode")]
|
||||||
|
EditorVerifyMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,6 @@ namespace osu.Game.Input.Handlers
|
|||||||
|
|
||||||
public override bool Initialize(GameHost host) => true;
|
public override bool Initialize(GameHost host) => true;
|
||||||
|
|
||||||
public override bool IsActive => true;
|
|
||||||
|
|
||||||
public class ReplayState<T> : IInput
|
public class ReplayState<T> : IInput
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
|
@ -85,7 +85,6 @@ namespace osu.Game.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rulesetId">The ruleset's internal ID.</param>
|
/// <param name="rulesetId">The ruleset's internal ID.</param>
|
||||||
/// <param name="variant">An optional variant.</param>
|
/// <param name="variant">An optional variant.</param>
|
||||||
/// <returns></returns>
|
|
||||||
public List<DatabasedKeyBinding> Query(int? rulesetId = null, int? variant = null) =>
|
public List<DatabasedKeyBinding> Query(int? rulesetId = null, int? variant = null) =>
|
||||||
ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
||||||
|
|
||||||
|
506
osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs
generated
Normal file
506
osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs
generated
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
|
namespace osu.Game.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(OsuDbContext))]
|
||||||
|
[Migration("20210412045700_RefreshVolumeBindingsAgain")]
|
||||||
|
partial class RefreshVolumeBindingsAgain
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<float>("ApproachRate");
|
||||||
|
|
||||||
|
b.Property<float>("CircleSize");
|
||||||
|
|
||||||
|
b.Property<float>("DrainRate");
|
||||||
|
|
||||||
|
b.Property<float>("OverallDifficulty");
|
||||||
|
|
||||||
|
b.Property<double>("SliderMultiplier");
|
||||||
|
|
||||||
|
b.Property<double>("SliderTickRate");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapDifficulty");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<double>("AudioLeadIn");
|
||||||
|
|
||||||
|
b.Property<double>("BPM");
|
||||||
|
|
||||||
|
b.Property<int>("BaseDifficultyID");
|
||||||
|
|
||||||
|
b.Property<int>("BeatDivisor");
|
||||||
|
|
||||||
|
b.Property<int>("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.Property<bool>("Countdown");
|
||||||
|
|
||||||
|
b.Property<double>("DistanceSpacing");
|
||||||
|
|
||||||
|
b.Property<int>("GridSize");
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<bool>("Hidden");
|
||||||
|
|
||||||
|
b.Property<double>("Length");
|
||||||
|
|
||||||
|
b.Property<bool>("LetterboxInBreaks");
|
||||||
|
|
||||||
|
b.Property<string>("MD5Hash");
|
||||||
|
|
||||||
|
b.Property<int?>("MetadataID");
|
||||||
|
|
||||||
|
b.Property<int?>("OnlineBeatmapID");
|
||||||
|
|
||||||
|
b.Property<string>("Path");
|
||||||
|
|
||||||
|
b.Property<int>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<bool>("SpecialStyle");
|
||||||
|
|
||||||
|
b.Property<float>("StackLeniency");
|
||||||
|
|
||||||
|
b.Property<double>("StarDifficulty");
|
||||||
|
|
||||||
|
b.Property<int>("Status");
|
||||||
|
|
||||||
|
b.Property<string>("StoredBookmarks");
|
||||||
|
|
||||||
|
b.Property<double>("TimelineZoom");
|
||||||
|
|
||||||
|
b.Property<string>("Version");
|
||||||
|
|
||||||
|
b.Property<bool>("WidescreenStoryboard");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("BaseDifficultyID");
|
||||||
|
|
||||||
|
b.HasIndex("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("Hash");
|
||||||
|
|
||||||
|
b.HasIndex("MD5Hash");
|
||||||
|
|
||||||
|
b.HasIndex("MetadataID");
|
||||||
|
|
||||||
|
b.HasIndex("OnlineBeatmapID")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Artist");
|
||||||
|
|
||||||
|
b.Property<string>("ArtistUnicode");
|
||||||
|
|
||||||
|
b.Property<string>("AudioFile");
|
||||||
|
|
||||||
|
b.Property<string>("AuthorString")
|
||||||
|
.HasColumnName("Author");
|
||||||
|
|
||||||
|
b.Property<string>("BackgroundFile");
|
||||||
|
|
||||||
|
b.Property<int>("PreviewTime");
|
||||||
|
|
||||||
|
b.Property<string>("Source");
|
||||||
|
|
||||||
|
b.Property<string>("Tags");
|
||||||
|
|
||||||
|
b.Property<string>("Title");
|
||||||
|
|
||||||
|
b.Property<string>("TitleUnicode");
|
||||||
|
|
||||||
|
b.Property<string>("VideoFile");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapMetadata");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.Property<int>("FileInfoID");
|
||||||
|
|
||||||
|
b.Property<string>("Filename")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("FileInfoID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapSetFileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("DateAdded");
|
||||||
|
|
||||||
|
b.Property<bool>("DeletePending");
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<int?>("MetadataID");
|
||||||
|
|
||||||
|
b.Property<int?>("OnlineBeatmapSetID");
|
||||||
|
|
||||||
|
b.Property<bool>("Protected");
|
||||||
|
|
||||||
|
b.Property<int>("Status");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("DeletePending");
|
||||||
|
|
||||||
|
b.HasIndex("Hash")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("MetadataID");
|
||||||
|
|
||||||
|
b.HasIndex("OnlineBeatmapSetID")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("BeatmapSetInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnName("Key");
|
||||||
|
|
||||||
|
b.Property<int?>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<int?>("SkinInfoID");
|
||||||
|
|
||||||
|
b.Property<string>("StringValue")
|
||||||
|
.HasColumnName("Value");
|
||||||
|
|
||||||
|
b.Property<int?>("Variant");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("SkinInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID", "Variant");
|
||||||
|
|
||||||
|
b.ToTable("Settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<int>("ReferenceCount");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("Hash")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ReferenceCount");
|
||||||
|
|
||||||
|
b.ToTable("FileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("IntAction")
|
||||||
|
.HasColumnName("Action");
|
||||||
|
|
||||||
|
b.Property<string>("KeysString")
|
||||||
|
.HasColumnName("Keys");
|
||||||
|
|
||||||
|
b.Property<int?>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<int?>("Variant");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("IntAction");
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID", "Variant");
|
||||||
|
|
||||||
|
b.ToTable("KeyBinding");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<bool>("Available");
|
||||||
|
|
||||||
|
b.Property<string>("InstantiationInfo");
|
||||||
|
|
||||||
|
b.Property<string>("Name");
|
||||||
|
|
||||||
|
b.Property<string>("ShortName");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("Available");
|
||||||
|
|
||||||
|
b.HasIndex("ShortName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RulesetInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("FileInfoID");
|
||||||
|
|
||||||
|
b.Property<string>("Filename")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<int?>("ScoreInfoID");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("FileInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("ScoreInfoID");
|
||||||
|
|
||||||
|
b.ToTable("ScoreFileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<double>("Accuracy")
|
||||||
|
.HasColumnType("DECIMAL(1,4)");
|
||||||
|
|
||||||
|
b.Property<int>("BeatmapInfoID");
|
||||||
|
|
||||||
|
b.Property<int>("Combo");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("Date");
|
||||||
|
|
||||||
|
b.Property<bool>("DeletePending");
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<int>("MaxCombo");
|
||||||
|
|
||||||
|
b.Property<string>("ModsJson")
|
||||||
|
.HasColumnName("Mods");
|
||||||
|
|
||||||
|
b.Property<long?>("OnlineScoreID");
|
||||||
|
|
||||||
|
b.Property<double?>("PP");
|
||||||
|
|
||||||
|
b.Property<int>("Rank");
|
||||||
|
|
||||||
|
b.Property<int>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<string>("StatisticsJson")
|
||||||
|
.HasColumnName("Statistics");
|
||||||
|
|
||||||
|
b.Property<long>("TotalScore");
|
||||||
|
|
||||||
|
b.Property<long?>("UserID")
|
||||||
|
.HasColumnName("UserID");
|
||||||
|
|
||||||
|
b.Property<string>("UserString")
|
||||||
|
.HasColumnName("User");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("BeatmapInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("OnlineScoreID")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID");
|
||||||
|
|
||||||
|
b.ToTable("ScoreInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("FileInfoID");
|
||||||
|
|
||||||
|
b.Property<string>("Filename")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<int>("SkinInfoID");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("FileInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("SkinInfoID");
|
||||||
|
|
||||||
|
b.ToTable("SkinFileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Creator");
|
||||||
|
|
||||||
|
b.Property<bool>("DeletePending");
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<string>("Name");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("DeletePending");
|
||||||
|
|
||||||
|
b.HasIndex("Hash")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("SkinInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BaseDifficultyID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
|
||||||
|
.WithMany("Beatmaps")
|
||||||
|
.HasForeignKey("BeatmapSetInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||||
|
.WithMany("Beatmaps")
|
||||||
|
.HasForeignKey("MetadataID");
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RulesetID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("BeatmapSetInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||||
|
.WithMany("BeatmapSets")
|
||||||
|
.HasForeignKey("MetadataID");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||||
|
.WithMany("Settings")
|
||||||
|
.HasForeignKey("SkinInfoID");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Scoring.ScoreInfo")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ScoreInfoID");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
|
||||||
|
.WithMany("Scores")
|
||||||
|
.HasForeignKey("BeatmapInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RulesetID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("SkinInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace osu.Game.Migrations
|
||||||
|
{
|
||||||
|
public partial class RefreshVolumeBindingsAgain : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,11 +11,12 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Online.API
|
namespace osu.Game.Online.API
|
||||||
{
|
{
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class APIMod : IMod
|
public class APIMod : IMod, IEquatable<APIMod>
|
||||||
{
|
{
|
||||||
[JsonProperty("acronym")]
|
[JsonProperty("acronym")]
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
@ -63,7 +64,16 @@ namespace osu.Game.Online.API
|
|||||||
return resultMod;
|
return resultMod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(IMod other) => Acronym == other?.Acronym;
|
public bool Equals(IMod other) => other is APIMod them && Equals(them);
|
||||||
|
|
||||||
|
public bool Equals(APIMod other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other)) return false;
|
||||||
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
|
||||||
|
return Acronym == other.Acronym &&
|
||||||
|
Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
@ -72,5 +82,20 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
return $"{Acronym}";
|
return $"{Acronym}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ModSettingsEqualityComparer : IEqualityComparer<KeyValuePair<string, object>>
|
||||||
|
{
|
||||||
|
public static ModSettingsEqualityComparer Default { get; } = new ModSettingsEqualityComparer();
|
||||||
|
|
||||||
|
public bool Equals(KeyValuePair<string, object> x, KeyValuePair<string, object> y)
|
||||||
|
{
|
||||||
|
object xValue = ModUtils.GetSettingUnderlyingValue(x.Value);
|
||||||
|
object yValue = ModUtils.GetSettingUnderlyingValue(y.Value);
|
||||||
|
|
||||||
|
return x.Key == y.Key && EqualityComparer<object>.Default.Equals(xValue, yValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(KeyValuePair<string, object> obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using MessagePack.Formatters;
|
using MessagePack.Formatters;
|
||||||
using osu.Framework.Bindables;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Online.API
|
namespace osu.Game.Online.API
|
||||||
{
|
{
|
||||||
@ -24,36 +23,7 @@ namespace osu.Game.Online.API
|
|||||||
var stringBytes = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(kvp.Key));
|
var stringBytes = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(kvp.Key));
|
||||||
writer.WriteString(in stringBytes);
|
writer.WriteString(in stringBytes);
|
||||||
|
|
||||||
switch (kvp.Value)
|
primitiveFormatter.Serialize(ref writer, ModUtils.GetSettingUnderlyingValue(kvp.Value), options);
|
||||||
{
|
|
||||||
case Bindable<double> d:
|
|
||||||
primitiveFormatter.Serialize(ref writer, d.Value, options);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Bindable<int> i:
|
|
||||||
primitiveFormatter.Serialize(ref writer, i.Value, options);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Bindable<float> f:
|
|
||||||
primitiveFormatter.Serialize(ref writer, f.Value, options);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Bindable<bool> b:
|
|
||||||
primitiveFormatter.Serialize(ref writer, b.Value, options);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IBindable u:
|
|
||||||
// A mod with unknown (e.g. enum) generic type.
|
|
||||||
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
|
|
||||||
Debug.Assert(valueMethod != null);
|
|
||||||
primitiveFormatter.Serialize(ref writer, valueMethod.GetValue(u), options);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// fall back for non-bindable cases.
|
|
||||||
primitiveFormatter.Serialize(ref writer, kvp.Value, options);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
||||||
|
|
||||||
@ -156,6 +157,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
||||||
|
|
||||||
|
protected virtual BatteryInfo CreateBatteryInfo() => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
|
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -281,6 +284,11 @@ namespace osu.Game
|
|||||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||||
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
||||||
|
|
||||||
|
var powerStatus = CreateBatteryInfo();
|
||||||
|
if (powerStatus != null)
|
||||||
|
dependencies.CacheAs(powerStatus);
|
||||||
|
|
||||||
dependencies.Cache(new SessionStatics());
|
dependencies.Cache(new SessionStatics());
|
||||||
dependencies.Cache(new OsuColour());
|
dependencies.Cache(new OsuColour());
|
||||||
|
|
||||||
@ -433,12 +441,15 @@ namespace osu.Game
|
|||||||
if (paths.Length == 0)
|
if (paths.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var extension = Path.GetExtension(paths.First())?.ToLowerInvariant();
|
var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant());
|
||||||
|
|
||||||
|
foreach (var groups in filesPerExtension)
|
||||||
|
{
|
||||||
foreach (var importer in fileImporters)
|
foreach (var importer in fileImporters)
|
||||||
{
|
{
|
||||||
if (importer.HandledExtensions.Contains(extension))
|
if (importer.HandledExtensions.Contains(groups.Key))
|
||||||
await importer.Import(paths).ConfigureAwait(false);
|
await importer.Import(groups.ToArray()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
{
|
{
|
||||||
private ProfileLineChart chart;
|
private ProfileLineChart chart;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the history graph tooltip.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract string GraphCounterName { get; }
|
||||||
|
|
||||||
protected ChartProfileSubsection(Bindable<User> user, string headerText)
|
protected ChartProfileSubsection(Bindable<User> user, string headerText)
|
||||||
: base(user, headerText)
|
: base(user, headerText)
|
||||||
{
|
{
|
||||||
@ -30,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
Left = 20,
|
Left = 20,
|
||||||
Right = 40
|
Right = 40
|
||||||
},
|
},
|
||||||
Child = chart = new ProfileLineChart()
|
Child = chart = new ProfileLineChart(GraphCounterName)
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -9,6 +9,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
{
|
{
|
||||||
public class PlayHistorySubsection : ChartProfileSubsection
|
public class PlayHistorySubsection : ChartProfileSubsection
|
||||||
{
|
{
|
||||||
|
protected override string GraphCounterName => "Plays";
|
||||||
|
|
||||||
public PlayHistorySubsection(Bindable<User> user)
|
public PlayHistorySubsection(Bindable<User> user)
|
||||||
: base(user, "Play History")
|
: base(user, "Play History")
|
||||||
{
|
{
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
private readonly Container<TickLine> rowLinesContainer;
|
private readonly Container<TickLine> rowLinesContainer;
|
||||||
private readonly Container<TickLine> columnLinesContainer;
|
private readonly Container<TickLine> columnLinesContainer;
|
||||||
|
|
||||||
public ProfileLineChart()
|
public ProfileLineChart(string graphCounterName)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = 250;
|
Height = 250;
|
||||||
@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
graph = new UserHistoryGraph
|
graph = new UserHistoryGraph(graphCounterName)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
{
|
{
|
||||||
public class ReplaysSubsection : ChartProfileSubsection
|
public class ReplaysSubsection : ChartProfileSubsection
|
||||||
{
|
{
|
||||||
|
protected override string GraphCounterName => "Replays Watched";
|
||||||
|
|
||||||
public ReplaysSubsection(Bindable<User> user)
|
public ReplaysSubsection(Bindable<User> user)
|
||||||
: base(user, "Replays Watched History")
|
: base(user, "Replays Watched History")
|
||||||
{
|
{
|
||||||
|
@ -11,25 +11,28 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
{
|
{
|
||||||
public class UserHistoryGraph : UserGraph<DateTime, long>
|
public class UserHistoryGraph : UserGraph<DateTime, long>
|
||||||
{
|
{
|
||||||
|
private readonly string tooltipCounterName;
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public UserHistoryCount[] Values
|
public UserHistoryCount[] Values
|
||||||
{
|
{
|
||||||
set => Data = value?.Select(v => new KeyValuePair<DateTime, long>(v.Date, v.Count)).ToArray();
|
set => Data = value?.Select(v => new KeyValuePair<DateTime, long>(v.Date, v.Count)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public UserHistoryGraph(string tooltipCounterName)
|
||||||
/// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the <see cref="HistoryGraphTooltip"/>.
|
{
|
||||||
/// </summary>
|
this.tooltipCounterName = tooltipCounterName;
|
||||||
public string TooltipCounterName { get; set; } = "Plays";
|
}
|
||||||
|
|
||||||
protected override float GetDataPointHeight(long playCount) => playCount;
|
protected override float GetDataPointHeight(long playCount) => playCount;
|
||||||
|
|
||||||
protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(TooltipCounterName);
|
protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(tooltipCounterName);
|
||||||
|
|
||||||
protected override object GetTooltipContent(DateTime date, long playCount)
|
protected override object GetTooltipContent(DateTime date, long playCount)
|
||||||
{
|
{
|
||||||
return new TooltipDisplayContent
|
return new TooltipDisplayContent
|
||||||
{
|
{
|
||||||
|
Name = tooltipCounterName,
|
||||||
Count = playCount.ToString("N0"),
|
Count = playCount.ToString("N0"),
|
||||||
Date = date.ToString("MMMM yyyy")
|
Date = date.ToString("MMMM yyyy")
|
||||||
};
|
};
|
||||||
@ -37,14 +40,17 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
|
|
||||||
protected class HistoryGraphTooltip : UserGraphTooltip
|
protected class HistoryGraphTooltip : UserGraphTooltip
|
||||||
{
|
{
|
||||||
|
private readonly string tooltipCounterName;
|
||||||
|
|
||||||
public HistoryGraphTooltip(string tooltipCounterName)
|
public HistoryGraphTooltip(string tooltipCounterName)
|
||||||
: base(tooltipCounterName)
|
: base(tooltipCounterName)
|
||||||
{
|
{
|
||||||
|
this.tooltipCounterName = tooltipCounterName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool SetContent(object content)
|
public override bool SetContent(object content)
|
||||||
{
|
{
|
||||||
if (!(content is TooltipDisplayContent info))
|
if (!(content is TooltipDisplayContent info) || info.Name != tooltipCounterName)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Counter.Text = info.Count;
|
Counter.Text = info.Count;
|
||||||
@ -55,6 +61,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
|||||||
|
|
||||||
private class TooltipDisplayContent
|
private class TooltipDisplayContent
|
||||||
{
|
{
|
||||||
|
public string Name;
|
||||||
public string Count;
|
public string Count;
|
||||||
public string Date;
|
public string Date;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Volume
|
namespace osu.Game.Overlays.Volume
|
||||||
@ -15,8 +17,30 @@ namespace osu.Game.Overlays.Volume
|
|||||||
public Func<GlobalAction, bool> ActionRequested;
|
public Func<GlobalAction, bool> ActionRequested;
|
||||||
public Func<GlobalAction, float, bool, bool> ScrollActionRequested;
|
public Func<GlobalAction, float, bool, bool> ScrollActionRequested;
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action) =>
|
private ScheduledDelegate keyRepeat;
|
||||||
ActionRequested?.Invoke(action) ?? false;
|
|
||||||
|
public bool OnPressed(GlobalAction action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case GlobalAction.DecreaseVolume:
|
||||||
|
case GlobalAction.IncreaseVolume:
|
||||||
|
keyRepeat?.Cancel();
|
||||||
|
keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(action), 150);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.ToggleMute:
|
||||||
|
ActionRequested?.Invoke(action);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(GlobalAction action)
|
||||||
|
{
|
||||||
|
keyRepeat?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
{
|
{
|
||||||
@ -27,9 +51,5 @@ namespace osu.Game.Overlays.Volume
|
|||||||
|
|
||||||
public bool OnScroll(GlobalAction action, float amount, bool isPrecise) =>
|
public bool OnScroll(GlobalAction action, float amount, bool isPrecise) =>
|
||||||
ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
|
ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
|
||||||
|
|
||||||
public void OnReleased(GlobalAction action)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -225,7 +226,7 @@ namespace osu.Game.Overlays.Volume
|
|||||||
private set => Bindable.Value = value;
|
private set => Bindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const double adjust_step = 0.05;
|
private const double adjust_step = 0.01;
|
||||||
|
|
||||||
public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
|
public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
|
||||||
public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
|
public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
|
||||||
@ -233,18 +234,42 @@ namespace osu.Game.Overlays.Volume
|
|||||||
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
|
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
|
||||||
private double scrollAccumulation;
|
private double scrollAccumulation;
|
||||||
|
|
||||||
|
private double accelerationModifier = 1;
|
||||||
|
|
||||||
|
private const double max_acceleration = 5;
|
||||||
|
private const double acceleration_multiplier = 1.8;
|
||||||
|
|
||||||
|
private ScheduledDelegate accelerationDebounce;
|
||||||
|
|
||||||
|
private void resetAcceleration() => accelerationModifier = 1;
|
||||||
|
|
||||||
private void adjust(double delta, bool isPrecise)
|
private void adjust(double delta, bool isPrecise)
|
||||||
{
|
{
|
||||||
scrollAccumulation += delta * adjust_step * (isPrecise ? 0.1 : 1);
|
// every adjust increment increases the rate at which adjustments happen up to a cutoff.
|
||||||
|
// this debounce will reset on inactivity.
|
||||||
|
accelerationDebounce?.Cancel();
|
||||||
|
accelerationDebounce = Scheduler.AddDelayed(resetAcceleration, 150);
|
||||||
|
|
||||||
|
delta *= accelerationModifier;
|
||||||
|
accelerationModifier = Math.Min(max_acceleration, accelerationModifier * acceleration_multiplier);
|
||||||
|
|
||||||
var precision = Bindable.Precision;
|
var precision = Bindable.Precision;
|
||||||
|
|
||||||
|
if (isPrecise)
|
||||||
|
{
|
||||||
|
scrollAccumulation += delta * adjust_step * 0.1;
|
||||||
|
|
||||||
while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
|
while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
|
||||||
{
|
{
|
||||||
Volume += Math.Sign(scrollAccumulation) * precision;
|
Volume += Math.Sign(scrollAccumulation) * precision;
|
||||||
scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision);
|
scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Volume += Math.Sign(delta) * Math.Max(precision, Math.Abs(delta * adjust_step));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
{
|
{
|
||||||
|
24
osu.Game/Rulesets/Edit/BeatmapVerifier.cs
Normal file
24
osu.Game/Rulesets/Edit/BeatmapVerifier.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ruleset-agnostic beatmap verifier that identifies issues in common metadata or mapping standards.
|
||||||
|
/// </summary>
|
||||||
|
public class BeatmapVerifier : IBeatmapVerifier
|
||||||
|
{
|
||||||
|
private readonly List<ICheck> checks = new List<ICheck>
|
||||||
|
{
|
||||||
|
new CheckBackground(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game/Rulesets/Edit/Checks/CheckBackground.cs
Normal file
61
osu.Game/Rulesets/Edit/Checks/CheckBackground.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckBackground : ICheck
|
||||||
|
{
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateNoneSet(this),
|
||||||
|
new IssueTemplateDoesNotExist(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
if (beatmap.Metadata.BackgroundFile == null)
|
||||||
|
{
|
||||||
|
yield return new IssueTemplateNoneSet(this).Create();
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the background is set, also make sure it still exists.
|
||||||
|
|
||||||
|
var set = beatmap.BeatmapInfo.BeatmapSet;
|
||||||
|
var file = set.Files.FirstOrDefault(f => f.Filename == beatmap.Metadata.BackgroundFile);
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateNoneSet : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateNoneSet(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "No background has been set.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create() => new Issue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateDoesNotExist : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateDoesNotExist(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "The background file \"{0}\" does not exist.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(string filename) => new Issue(this, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs
Normal file
61
osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category of an issue.
|
||||||
|
/// </summary>
|
||||||
|
public enum CheckCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with control points.
|
||||||
|
/// </summary>
|
||||||
|
Timing,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with artist, title, creator, etc.
|
||||||
|
/// </summary>
|
||||||
|
Metadata,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with non-audio files, e.g. background, skin, sprites, and video.
|
||||||
|
/// </summary>
|
||||||
|
Resources,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with audio files, e.g. song and hitsounds.
|
||||||
|
/// </summary>
|
||||||
|
Audio,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb.
|
||||||
|
/// </summary>
|
||||||
|
Files,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with hitobjects unrelated to spread.
|
||||||
|
/// </summary>
|
||||||
|
Compose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with difficulty levels or their progression.
|
||||||
|
/// </summary>
|
||||||
|
Spread,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with variables like CS, OD, AR, HP, and global SV.
|
||||||
|
/// </summary>
|
||||||
|
Settings,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with hitobject feedback.
|
||||||
|
/// </summary>
|
||||||
|
HitObjects,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with storyboarding, breaks, video offset, etc.
|
||||||
|
/// </summary>
|
||||||
|
Events
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs
Normal file
24
osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
public class CheckMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category this check belongs to. E.g. <see cref="CheckCategory.Metadata"/>, <see cref="CheckCategory.Timing"/>, or <see cref="CheckCategory.Compose"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly CheckCategory Category;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the issue(s) that this check looks for. Keep this brief, such that it fits into "No {description}". E.g. "Offscreen objects" / "Too short sliders".
|
||||||
|
/// </summary>
|
||||||
|
public readonly string Description;
|
||||||
|
|
||||||
|
public CheckMetadata(CheckCategory category, string description)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs
Normal file
30
osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A specific check that can be run on a beatmap to verify or find issues.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICheck
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The metadata for this check.
|
||||||
|
/// </summary>
|
||||||
|
public CheckMetadata Metadata { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All possible templates for issues that this check may return.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs this check and returns any issues detected for the provided beatmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmap">The beatmap to run the check on.</param>
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap);
|
||||||
|
}
|
||||||
|
}
|
77
osu.Game/Rulesets/Edit/Checks/Components/Issue.cs
Normal file
77
osu.Game/Rulesets/Edit/Checks/Components/Issue.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
public class Issue
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time which this issue is associated with, if any, otherwise null.
|
||||||
|
/// </summary>
|
||||||
|
public double? Time;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hitobjects which this issue is associated with. Empty by default.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<HitObject> HitObjects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The template which this issue is using. This provides properties such as the <see cref="IssueType"/>, and the <see cref="IssueTemplate.UnformattedMessage"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IssueTemplate Template;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The check that this issue originates from.
|
||||||
|
/// </summary>
|
||||||
|
public ICheck Check => Template.Check;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arguments that give this issue its context, based on the <see cref="IssueTemplate"/>. These are then substituted into the <see cref="IssueTemplate.UnformattedMessage"/>.
|
||||||
|
/// This could for instance include timestamps, which diff is being compared to, what some volume is, etc.
|
||||||
|
/// </summary>
|
||||||
|
public object[] Arguments;
|
||||||
|
|
||||||
|
public Issue(IssueTemplate template, params object[] args)
|
||||||
|
{
|
||||||
|
Time = null;
|
||||||
|
HitObjects = Array.Empty<HitObject>();
|
||||||
|
Template = template;
|
||||||
|
Arguments = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(double? time, IssueTemplate template, params object[] args)
|
||||||
|
: this(template, args)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(HitObject hitObject, IssueTemplate template, params object[] args)
|
||||||
|
: this(template, args)
|
||||||
|
{
|
||||||
|
Time = hitObject.StartTime;
|
||||||
|
HitObjects = new[] { hitObject };
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(IEnumerable<HitObject> hitObjects, IssueTemplate template, params object[] args)
|
||||||
|
: this(template, args)
|
||||||
|
{
|
||||||
|
var hitObjectList = hitObjects.ToList();
|
||||||
|
|
||||||
|
Time = hitObjectList.FirstOrDefault()?.StartTime;
|
||||||
|
HitObjects = hitObjectList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Template.GetMessage(Arguments);
|
||||||
|
|
||||||
|
public string GetEditorTimestamp()
|
||||||
|
{
|
||||||
|
return Time == null ? string.Empty : Time.Value.ToEditorFormattedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs
Normal file
74
osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 Humanizer;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
public class IssueTemplate
|
||||||
|
{
|
||||||
|
private static readonly Color4 problem_red = new Colour4(1.0f, 0.4f, 0.4f, 1.0f);
|
||||||
|
private static readonly Color4 warning_yellow = new Colour4(1.0f, 0.8f, 0.2f, 1.0f);
|
||||||
|
private static readonly Color4 negligible_green = new Colour4(0.33f, 0.8f, 0.5f, 1.0f);
|
||||||
|
private static readonly Color4 error_gray = new Colour4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The check that this template originates from.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ICheck Check;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the issue.
|
||||||
|
/// </summary>
|
||||||
|
public readonly IssueType Type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unformatted message given when this issue is detected.
|
||||||
|
/// This gets populated later when an issue is constructed with this template.
|
||||||
|
/// E.g. "Inconsistent snapping (1/{0}) with [{1}] (1/{2})."
|
||||||
|
/// </summary>
|
||||||
|
public readonly string UnformattedMessage;
|
||||||
|
|
||||||
|
public IssueTemplate(ICheck check, IssueType type, string unformattedMessage)
|
||||||
|
{
|
||||||
|
Check = check;
|
||||||
|
Type = type;
|
||||||
|
UnformattedMessage = unformattedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the formatted message given the arguments used to format it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments used to format the message.</param>
|
||||||
|
public string GetMessage(params object[] args) => UnformattedMessage.FormatWith(args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the colour corresponding to the type of this issue.
|
||||||
|
/// </summary>
|
||||||
|
public Colour4 Colour
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case IssueType.Problem:
|
||||||
|
return problem_red;
|
||||||
|
|
||||||
|
case IssueType.Warning:
|
||||||
|
return warning_yellow;
|
||||||
|
|
||||||
|
case IssueType.Negligible:
|
||||||
|
return negligible_green;
|
||||||
|
|
||||||
|
case IssueType.Error:
|
||||||
|
return error_gray;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs
Normal file
25
osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type, or severity, of an issue.
|
||||||
|
/// </summary>
|
||||||
|
public enum IssueType
|
||||||
|
{
|
||||||
|
/// <summary> A must-fix in the vast majority of cases. </summary>
|
||||||
|
Problem,
|
||||||
|
|
||||||
|
/// <summary> A possible mistake. Often requires critical thinking. </summary>
|
||||||
|
Warning,
|
||||||
|
|
||||||
|
// TODO: Try/catch all checks run and return error templates if exceptions occur.
|
||||||
|
/// <summary> An error occurred and a complete check could not be made. </summary>
|
||||||
|
Error,
|
||||||
|
|
||||||
|
// TODO: Negligible issues should be hidden by default.
|
||||||
|
/// <summary> A possible mistake so minor/unlikely that it can often be safely ignored. </summary>
|
||||||
|
Negligible,
|
||||||
|
}
|
||||||
|
}
|
17
osu.Game/Rulesets/Edit/IBeatmapVerifier.cs
Normal file
17
osu.Game/Rulesets/Edit/IBeatmapVerifier.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class which can run against a beatmap and surface issues to the user which could go against known criteria or hinder gameplay.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBeatmapVerifier
|
||||||
|
{
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap);
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// The base class for gameplay modifiers.
|
/// The base class for gameplay modifiers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
public abstract class Mod : IMod, IJsonSerializable
|
public abstract class Mod : IMod, IEquatable<Mod>, IJsonSerializable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of this mod.
|
/// The name of this mod.
|
||||||
@ -48,7 +49,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// The user readable description of this mod.
|
/// The user readable description of this mod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public virtual string Description => string.Empty;
|
public abstract string Description { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tooltip to display for this mod when used in a <see cref="ModIcon"/>.
|
/// The tooltip to display for this mod when used in a <see cref="ModIcon"/>.
|
||||||
@ -172,7 +173,19 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
target.Parse(source);
|
target.Parse(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(IMod other) => GetType() == other?.GetType();
|
public bool Equals(IMod other) => other is Mod them && Equals(them);
|
||||||
|
|
||||||
|
public bool Equals(Mod other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other)) return false;
|
||||||
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
|
||||||
|
return GetType() == other.GetType() &&
|
||||||
|
this.GetSettingsSourceProperties().All(pair =>
|
||||||
|
EqualityComparer<object>.Default.Equals(
|
||||||
|
ModUtils.GetSettingUnderlyingValue(pair.Item2.GetValue(this)),
|
||||||
|
ModUtils.GetSettingUnderlyingValue(pair.Item2.GetValue(other))));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reset all custom settings for this mod back to their defaults.
|
/// Reset all custom settings for this mod back to their defaults.
|
||||||
|
@ -37,8 +37,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public void ApplyToPlayer(Player player)
|
public void ApplyToPlayer(Player player)
|
||||||
{
|
{
|
||||||
player.ApplyToBackground(b => b.EnableUserDim.Value = false);
|
player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
|
||||||
|
|
||||||
player.DimmableStoryboard.IgnoreUserSettings.Value = true;
|
player.DimmableStoryboard.IgnoreUserSettings.Value = true;
|
||||||
|
|
||||||
player.BreakOverlay.Hide();
|
player.BreakOverlay.Hide();
|
||||||
|
@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "No Mod";
|
public override string Name => "No Mod";
|
||||||
public override string Acronym => "NM";
|
public override string Acronym => "NM";
|
||||||
|
public override string Description => "No mods applied.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Ban;
|
public override IconUsage? Icon => FontAwesome.Solid.Ban;
|
||||||
public override ModType Type => ModType.System;
|
public override ModType Type => ModType.System;
|
||||||
|
@ -56,8 +56,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
||||||
|
|
||||||
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
|
private readonly List<DrawableHitObject> nestedHitObjects = new List<DrawableHitObject>();
|
||||||
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList<DrawableHitObject>)Array.Empty<DrawableHitObject>();
|
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this object should handle any user input events.
|
/// Whether this object should handle any user input events.
|
||||||
@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
// Must be done before the nested DHO is added to occur before the nested Apply()!
|
// Must be done before the nested DHO is added to occur before the nested Apply()!
|
||||||
drawableNested.ParentHitObject = this;
|
drawableNested.ParentHitObject = this;
|
||||||
|
|
||||||
nestedHitObjects.Value.Add(drawableNested);
|
nestedHitObjects.Add(drawableNested);
|
||||||
AddNestedHitObject(drawableNested);
|
AddNestedHitObject(drawableNested);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,18 +305,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
if (Samples != null)
|
if (Samples != null)
|
||||||
Samples.Samples = null;
|
Samples.Samples = null;
|
||||||
|
|
||||||
if (nestedHitObjects.IsValueCreated)
|
foreach (var obj in nestedHitObjects)
|
||||||
{
|
|
||||||
foreach (var obj in nestedHitObjects.Value)
|
|
||||||
{
|
{
|
||||||
obj.OnNewResult -= onNewResult;
|
obj.OnNewResult -= onNewResult;
|
||||||
obj.OnRevertResult -= onRevertResult;
|
obj.OnRevertResult -= onRevertResult;
|
||||||
obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
|
obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
|
||||||
}
|
}
|
||||||
|
|
||||||
nestedHitObjects.Value.Clear();
|
nestedHitObjects.Clear();
|
||||||
ClearNestedHitObjects();
|
ClearNestedHitObjects();
|
||||||
}
|
|
||||||
|
|
||||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
|
||||||
@ -574,7 +571,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// Calculate the position to be used for sample playback at a specified X position (0..1).
|
/// Calculate the position to be used for sample playback at a specified X position (0..1).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="position">The lookup X position. Generally should be <see cref="SamplePlaybackPosition"/>.</param>
|
/// <param name="position">The lookup X position. Generally should be <see cref="SamplePlaybackPosition"/>.</param>
|
||||||
/// <returns></returns>
|
|
||||||
protected double CalculateSamplePlaybackBalance(double position)
|
protected double CalculateSamplePlaybackBalance(double position)
|
||||||
{
|
{
|
||||||
const float balance_adjust_amount = 0.4f;
|
const float balance_adjust_amount = 0.4f;
|
||||||
|
@ -147,7 +147,6 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// to 1 (end of the path).
|
/// to 1 (end of the path).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="progress">Ranges from 0 (beginning of the path) to 1 (end of the path).</param>
|
/// <param name="progress">Ranges from 0 (beginning of the path) to 1 (end of the path).</param>
|
||||||
/// <returns></returns>
|
|
||||||
public Vector2 PositionAt(double progress)
|
public Vector2 PositionAt(double progress)
|
||||||
{
|
{
|
||||||
ensureValid();
|
ensureValid();
|
||||||
@ -161,7 +160,6 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// The first point has a PathType which all other points inherit.
|
/// The first point has a PathType which all other points inherit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="controlPoint">One of the control points in the segment.</param>
|
/// <param name="controlPoint">One of the control points in the segment.</param>
|
||||||
/// <returns></returns>
|
|
||||||
public List<PathControlPoint> PointsInSegment(PathControlPoint controlPoint)
|
public List<PathControlPoint> PointsInSegment(PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
@ -17,80 +16,92 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
public abstract class FramedReplayInputHandler<TFrame> : ReplayInputHandler
|
public abstract class FramedReplayInputHandler<TFrame> : ReplayInputHandler
|
||||||
where TFrame : ReplayFrame
|
where TFrame : ReplayFrame
|
||||||
{
|
{
|
||||||
private readonly Replay replay;
|
/// <summary>
|
||||||
|
/// Whether we have at least one replay frame.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasFrames => Frames.Count != 0;
|
||||||
|
|
||||||
protected List<ReplayFrame> Frames => replay.Frames;
|
/// <summary>
|
||||||
|
/// Whether we are waiting for new frames to be received.
|
||||||
|
/// </summary>
|
||||||
|
public bool WaitingForFrame => !replay.HasReceivedAllFrames && currentFrameIndex == Frames.Count - 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current frame of the replay.
|
||||||
|
/// The current time is always between the start and the end time of the current frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Returns null if the current time is strictly before the first frame.</remarks>
|
||||||
|
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
|
||||||
public TFrame CurrentFrame
|
public TFrame CurrentFrame
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!HasFrames || !currentFrameIndex.HasValue)
|
if (!HasFrames)
|
||||||
return null;
|
throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay");
|
||||||
|
|
||||||
return (TFrame)Frames[currentFrameIndex.Value];
|
return currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next frame of the replay.
|
||||||
|
/// The start time is always greater or equal to the start time of <see cref="CurrentFrame"/> regardless of the seeking direction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Returns null if the current frame is the last frame.</remarks>
|
||||||
|
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
|
||||||
public TFrame NextFrame
|
public TFrame NextFrame
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!HasFrames)
|
if (!HasFrames)
|
||||||
return null;
|
throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay");
|
||||||
|
|
||||||
if (!currentFrameIndex.HasValue)
|
return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1];
|
||||||
return currentDirection > 0 ? (TFrame)Frames[0] : null;
|
|
||||||
|
|
||||||
int nextFrame = clampedNextFrameIndex;
|
|
||||||
|
|
||||||
if (nextFrame == currentFrameIndex.Value)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (TFrame)Frames[clampedNextFrameIndex];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? currentFrameIndex;
|
|
||||||
|
|
||||||
private int clampedNextFrameIndex =>
|
|
||||||
currentFrameIndex.HasValue ? Math.Clamp(currentFrameIndex.Value + currentDirection, 0, Frames.Count - 1) : 0;
|
|
||||||
|
|
||||||
protected FramedReplayInputHandler(Replay replay)
|
|
||||||
{
|
|
||||||
this.replay = replay;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const double sixty_frame_time = 1000.0 / 60;
|
|
||||||
|
|
||||||
protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2;
|
|
||||||
|
|
||||||
protected double? CurrentTime { get; private set; }
|
|
||||||
|
|
||||||
private int currentDirection = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
|
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
|
||||||
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
|
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool FrameAccuratePlayback;
|
public bool FrameAccuratePlayback;
|
||||||
|
|
||||||
public bool HasFrames => Frames.Count > 0;
|
// This input handler should be enabled only if there is at least one replay frame.
|
||||||
|
public override bool IsActive => HasFrames;
|
||||||
|
|
||||||
|
// Can make it non-null but that is a breaking change.
|
||||||
|
protected double? CurrentTime { get; private set; }
|
||||||
|
|
||||||
|
protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2;
|
||||||
|
|
||||||
|
protected List<ReplayFrame> Frames => replay.Frames;
|
||||||
|
|
||||||
|
private readonly Replay replay;
|
||||||
|
|
||||||
|
private int currentFrameIndex;
|
||||||
|
|
||||||
|
private const double sixty_frame_time = 1000.0 / 60;
|
||||||
|
|
||||||
|
protected FramedReplayInputHandler(Replay replay)
|
||||||
|
{
|
||||||
|
// TODO: This replay frame ordering should be enforced on the Replay type.
|
||||||
|
// Currently, the ordering can be broken if the frames are added after this construction.
|
||||||
|
replay.Frames.Sort((x, y) => x.Time.CompareTo(y.Time));
|
||||||
|
|
||||||
|
this.replay = replay;
|
||||||
|
currentFrameIndex = -1;
|
||||||
|
CurrentTime = double.NegativeInfinity;
|
||||||
|
}
|
||||||
|
|
||||||
private bool inImportantSection
|
private bool inImportantSection
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!HasFrames || !FrameAccuratePlayback)
|
if (!HasFrames || !FrameAccuratePlayback || CurrentFrame == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var frame = currentDirection > 0 ? CurrentFrame : NextFrame;
|
return IsImportant(CurrentFrame) && // a button is in a pressed state
|
||||||
|
Math.Abs(CurrentTime - NextFrame.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span
|
||||||
if (frame == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return IsImportant(frame) && // a button is in a pressed state
|
|
||||||
Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,71 +116,52 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
||||||
public override double? SetFrameFromTime(double time)
|
public override double? SetFrameFromTime(double time)
|
||||||
{
|
{
|
||||||
updateDirection(time);
|
|
||||||
|
|
||||||
Debug.Assert(currentDirection != 0);
|
|
||||||
|
|
||||||
if (!HasFrames)
|
if (!HasFrames)
|
||||||
{
|
{
|
||||||
// in the case all frames are received, allow time to progress regardless.
|
// In the case all frames are received, allow time to progress regardless.
|
||||||
if (replay.HasReceivedAllFrames)
|
if (replay.HasReceivedAllFrames)
|
||||||
return CurrentTime = time;
|
return CurrentTime = time;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
TFrame next = NextFrame;
|
double frameStart = getFrameTime(currentFrameIndex);
|
||||||
|
double frameEnd = getFrameTime(currentFrameIndex + 1);
|
||||||
|
|
||||||
// if we have a next frame, check if it is before or at the current time in playback, and advance time to it if so.
|
// If the proposed time is after the current frame end time, we progress forwards to precisely the new frame's time (regardless of incoming time).
|
||||||
if (next != null)
|
if (frameEnd <= time)
|
||||||
{
|
{
|
||||||
int compare = time.CompareTo(next.Time);
|
time = frameEnd;
|
||||||
|
currentFrameIndex++;
|
||||||
if (compare == 0 || compare == currentDirection)
|
|
||||||
{
|
|
||||||
currentFrameIndex = clampedNextFrameIndex;
|
|
||||||
return CurrentTime = CurrentFrame.Time;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// If the proposed time is before the current frame start time, and we are at the frame boundary, we progress backwards.
|
||||||
|
else if (time < frameStart && CurrentTime == frameStart)
|
||||||
|
currentFrameIndex--;
|
||||||
|
|
||||||
// at this point, the frame index can't be advanced.
|
frameStart = getFrameTime(currentFrameIndex);
|
||||||
// even so, we may be able to propose the clock progresses forward due to being at an extent of the replay,
|
frameEnd = getFrameTime(currentFrameIndex + 1);
|
||||||
// or moving towards the next valid frame (ie. interpolating in a non-important section).
|
|
||||||
|
|
||||||
// the exception is if currently in an important section, which is respected above all.
|
// Pause until more frames are arrived.
|
||||||
if (inImportantSection)
|
if (WaitingForFrame && frameStart < time)
|
||||||
{
|
{
|
||||||
Debug.Assert(next != null || !replay.HasReceivedAllFrames);
|
CurrentTime = frameStart;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a next frame does exist, allow interpolation.
|
CurrentTime = Math.Clamp(time, frameStart, frameEnd);
|
||||||
if (next != null)
|
|
||||||
return CurrentTime = time;
|
|
||||||
|
|
||||||
// if all frames have been received, allow playing beyond extents.
|
// In an important section, a mid-frame time cannot be used and a null is returned instead.
|
||||||
if (replay.HasReceivedAllFrames)
|
return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime;
|
||||||
return CurrentTime = time;
|
|
||||||
|
|
||||||
// if not all frames are received but we are before the first frame, allow playing.
|
|
||||||
if (time < Frames[0].Time)
|
|
||||||
return CurrentTime = time;
|
|
||||||
|
|
||||||
// in the case we have no next frames and haven't received enough frame data, block.
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDirection(double time)
|
private double getFrameTime(int index)
|
||||||
{
|
{
|
||||||
if (!CurrentTime.HasValue)
|
if (index < 0)
|
||||||
{
|
return double.NegativeInfinity;
|
||||||
currentDirection = 1;
|
if (index >= Frames.Count)
|
||||||
}
|
return double.PositiveInfinity;
|
||||||
else
|
|
||||||
{
|
return Frames[index].Time;
|
||||||
currentDirection = time.CompareTo(CurrentTime);
|
|
||||||
if (currentDirection == 0) currentDirection = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,6 @@ namespace osu.Game.Rulesets
|
|||||||
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
|
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
|
||||||
/// <param name="mods">The <see cref="Mod"/>s to apply.</param>
|
/// <param name="mods">The <see cref="Mod"/>s to apply.</param>
|
||||||
/// <exception cref="BeatmapInvalidForRulesetException">Unable to successfully load the beatmap to be usable with this ruleset.</exception>
|
/// <exception cref="BeatmapInvalidForRulesetException">Unable to successfully load the beatmap to be usable with this ruleset.</exception>
|
||||||
/// <returns></returns>
|
|
||||||
public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null);
|
public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -202,6 +201,8 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public virtual HitObjectComposer CreateHitObjectComposer() => null;
|
public virtual HitObjectComposer CreateHitObjectComposer() => null;
|
||||||
|
|
||||||
|
public virtual IBeatmapVerifier CreateBeatmapVerifier() => null;
|
||||||
|
|
||||||
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
|
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
|
||||||
|
|
||||||
public virtual IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), @"Resources");
|
public virtual IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), @"Resources");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user