mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:20:04 +08:00
Merge branch 'master' into speedpp
This commit is contained in:
commit
2dd9d457e8
4
.github/workflows/test-diffcalc.yml
vendored
4
.github/workflows/test-diffcalc.yml
vendored
@ -23,9 +23,9 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
if: |
|
||||
${{ github.event.issue.pull_request }} &&
|
||||
github.event.issue.pull_request &&
|
||||
contains(github.event.comment.body, '!pp check') &&
|
||||
${{ github.event.comment.author_association == 'MEMBER' }}
|
||||
(github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--filter *" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -14,7 +14,7 @@
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net5.0" />
|
||||
<method v="2">
|
||||
<option name="Build" enabled="true" />
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
|
||||
|
||||
**Latest build:**
|
||||
|
||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
|
||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||
|
||||
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
|
||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
@ -51,8 +51,8 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.907.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.830.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.913.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.907.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Benchmarks
|
||||
[Params(1, 10, 100)]
|
||||
public int Times { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
mod = new OsuModDoubleTime();
|
||||
}
|
||||
|
||||
|
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
@ -0,0 +1,62 @@
|
||||
// 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 BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkRuleset : BenchmarkTest
|
||||
{
|
||||
private OsuRuleset ruleset;
|
||||
private APIMod apiModDoubleTime;
|
||||
private APIMod apiModDifficultyAdjust;
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
ruleset = new OsuRuleset();
|
||||
apiModDoubleTime = new APIMod { Acronym = "DT" };
|
||||
apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkToModDoubleTime()
|
||||
{
|
||||
apiModDoubleTime.ToMod(ruleset);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkToModDifficultyAdjust()
|
||||
{
|
||||
apiModDifficultyAdjust.ToMod(ruleset);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetAllMods()
|
||||
{
|
||||
ruleset.CreateAllMods().Consume(new Consumer());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetAllModsForReference()
|
||||
{
|
||||
ruleset.AllMods.Consume(new Consumer());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetForAcronym()
|
||||
{
|
||||
ruleset.CreateModFromAcronym("DT");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetForType()
|
||||
{
|
||||
ruleset.CreateMod<ModDoubleTime>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
|
||||
{
|
||||
BenchmarkSwitcher
|
||||
.FromAssembly(typeof(Program).Assembly)
|
||||
.Run(args);
|
||||
.Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
c.Add(CreateHitObject().With(h =>
|
||||
{
|
||||
h.HitObject.StartTime = START_TIME;
|
||||
h.HitObject.StartTime = Time.Current + 5000;
|
||||
h.AccentColour.Value = Color4.Orange;
|
||||
}));
|
||||
})
|
||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
c.Add(CreateHitObject().With(h =>
|
||||
{
|
||||
h.HitObject.StartTime = START_TIME;
|
||||
h.HitObject.StartTime = Time.Current + 5000;
|
||||
h.AccentColour.Value = Color4.Orange;
|
||||
}));
|
||||
})
|
||||
|
@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
/// </summary>
|
||||
public abstract class ManiaSkinnableTestScene : SkinnableTestScene
|
||||
{
|
||||
protected const double START_TIME = 1000000000;
|
||||
|
||||
[Cached(Type = typeof(IScrollingInfo))]
|
||||
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
|
||||
|
||||
@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
|
||||
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000);
|
||||
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm();
|
||||
}
|
||||
|
||||
private class ZeroScrollAlgorithm : IScrollAlgorithm
|
||||
{
|
||||
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
|
||||
=> double.MinValue;
|
||||
|
||||
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
|
||||
=> scrollLength;
|
||||
|
||||
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
|
||||
=> (float)((time - START_TIME) / timeRange) * scrollLength;
|
||||
|
||||
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
|
||||
=> 0;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
}
|
||||
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(5000);
|
||||
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[Resolved]
|
||||
private RulesetConfigCache configCache { get; set; }
|
||||
|
||||
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>();
|
||||
private Bindable<bool> configTimingBasedNoteColouring;
|
||||
|
||||
protected override void LoadComplete()
|
||||
private ManualClock clock;
|
||||
private DrawableManiaRuleset drawableRuleset;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("setup hierarchy", () => Child = new Container
|
||||
{
|
||||
Clock = new FramedClock(clock = new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
|
||||
}
|
||||
});
|
||||
AddStep("retrieve config bindable", () =>
|
||||
{
|
||||
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
|
||||
configTimingBasedNoteColouring = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimple()
|
||||
{
|
||||
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleOffScreen()
|
||||
{
|
||||
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
seekTo(0);
|
||||
AddAssert("all notes not coloured", () => this.ChildrenOfType<DrawableNote>().All(note => note.Colour == Colour4.White));
|
||||
|
||||
seekTo(10000);
|
||||
AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
|
||||
seekTo(0);
|
||||
AddAssert("some notes coloured", () => this.ChildrenOfType<DrawableNote>().Any(note => note.Colour != Colour4.White));
|
||||
}
|
||||
|
||||
private void seekTo(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => clock.CurrentTime = time);
|
||||
AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
|
||||
}
|
||||
|
||||
private ManiaBeatmap createTestBeatmap()
|
||||
{
|
||||
const double beat_length = 500;
|
||||
|
||||
var ruleset = new ManiaRuleset();
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
|
||||
{
|
||||
HitObjects =
|
||||
@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new Note { StartTime = beat_length }
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo(),
|
||||
BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
|
||||
BeatmapInfo = { Ruleset = Ruleset.Value },
|
||||
};
|
||||
|
||||
foreach (var note in beatmap.HitObjects)
|
||||
@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
BeatLength = beat_length
|
||||
});
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Clock = new FramedClock(new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
ruleset.CreateDrawableRulesetWith(beatmap)
|
||||
}
|
||||
};
|
||||
|
||||
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
|
||||
config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||
|
||||
AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
|
||||
AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
updateSnapColour();
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
base.OnDirectionChanged(e);
|
||||
|
@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.7568168283591499d, "diffcalc-test")]
|
||||
[TestCase(1.0348244046058293d, "zero-length-sliders")]
|
||||
[TestCase(6.6634445062299665d, "diffcalc-test")]
|
||||
[TestCase(1.0414203870195022d, "zero-length-sliders")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(8.4783236764532557d, "diffcalc-test")]
|
||||
[TestCase(1.2708532136987165d, "zero-length-sliders")]
|
||||
[TestCase(8.3858089051603368d, "diffcalc-test")]
|
||||
[TestCase(1.2723279173428435d, "zero-length-sliders")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new OsuModDoubleTime());
|
||||
|
||||
|
@ -35,7 +35,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||
|
||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||
double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1);
|
||||
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
||||
|
||||
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
|
||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
|
||||
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Mods
|
||||
@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods
|
||||
public class ModSettingsEqualityComparison
|
||||
{
|
||||
[Test]
|
||||
public void Test()
|
||||
public void TestAPIMod()
|
||||
{
|
||||
var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } });
|
||||
var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
|
||||
var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
|
||||
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMod()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
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);
|
||||
|
||||
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
|
||||
var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
|
||||
var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
|
||||
|
||||
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod3));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
|
||||
|
||||
Assert.That(mod3, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
||||
assertPlaybackPosition(0);
|
||||
|
||||
AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000);
|
||||
assertPlaybackPosition(-1000);
|
||||
assertPlaybackPosition(0);
|
||||
|
||||
AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500);
|
||||
assertPlaybackPosition(-500);
|
||||
assertPlaybackPosition(0);
|
||||
}
|
||||
|
||||
private void assertPlaybackPosition(double expectedPosition)
|
||||
|
177
osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
Normal file
177
osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
Normal file
@ -0,0 +1,177 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneDifficultySwitching : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override bool IsolateSavingFromDatabase => false;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
private BeatmapSetInfo importedBeatmapSet;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result);
|
||||
base.SetUpSteps();
|
||||
}
|
||||
|
||||
protected override void LoadEditor()
|
||||
{
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
|
||||
base.LoadEditor();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicSwitch()
|
||||
{
|
||||
BeatmapInfo targetDifficulty = null;
|
||||
|
||||
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
|
||||
switchToDifficulty(() => targetDifficulty);
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
|
||||
AddStep("exit editor", () => Stack.Exit());
|
||||
// ensure editor loader didn't resume.
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClockPositionPreservedBetweenSwitches()
|
||||
{
|
||||
BeatmapInfo targetDifficulty = null;
|
||||
AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000));
|
||||
|
||||
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
|
||||
switchToDifficulty(() => targetDifficulty);
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000);
|
||||
|
||||
AddStep("exit editor", () => Stack.Exit());
|
||||
// ensure editor loader didn't resume.
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset)
|
||||
{
|
||||
BeatmapInfo targetDifficulty = null;
|
||||
|
||||
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
|
||||
AddStep("copy object", () => Editor.Copy());
|
||||
|
||||
AddStep("set target difficulty", () =>
|
||||
{
|
||||
targetDifficulty = sameRuleset
|
||||
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
|
||||
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
|
||||
});
|
||||
switchToDifficulty(() => targetDifficulty);
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
|
||||
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
|
||||
AddStep("paste object", () => Editor.Paste());
|
||||
|
||||
if (sameRuleset)
|
||||
AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any());
|
||||
else
|
||||
AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any());
|
||||
|
||||
AddStep("exit editor", () => Stack.Exit());
|
||||
|
||||
if (sameRuleset)
|
||||
{
|
||||
AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
|
||||
AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction());
|
||||
}
|
||||
|
||||
// ensure editor loader didn't resume.
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPreventSwitchDueToUnsavedChanges()
|
||||
{
|
||||
BeatmapInfo targetDifficulty = null;
|
||||
PromptForSaveDialog saveDialog = null;
|
||||
|
||||
AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
|
||||
|
||||
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
|
||||
switchToDifficulty(() => targetDifficulty);
|
||||
|
||||
AddUntilStep("prompt for save dialog shown", () =>
|
||||
{
|
||||
saveDialog = this.ChildrenOfType<PromptForSaveDialog>().Single();
|
||||
return saveDialog != null;
|
||||
});
|
||||
AddStep("continue editing", () =>
|
||||
{
|
||||
var continueButton = saveDialog.ChildrenOfType<PopupDialogCancelButton>().Last();
|
||||
continueButton.TriggerClick();
|
||||
});
|
||||
|
||||
confirmEditingBeatmap(() => importedBeatmapSet.Beatmaps.First());
|
||||
|
||||
AddRepeatStep("exit editor forcefully", () => Stack.Exit(), 2);
|
||||
// ensure editor loader didn't resume.
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllowSwitchAfterDiscardingUnsavedChanges()
|
||||
{
|
||||
BeatmapInfo targetDifficulty = null;
|
||||
PromptForSaveDialog saveDialog = null;
|
||||
|
||||
AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
|
||||
|
||||
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
|
||||
switchToDifficulty(() => targetDifficulty);
|
||||
|
||||
AddUntilStep("prompt for save dialog shown", () =>
|
||||
{
|
||||
saveDialog = this.ChildrenOfType<PromptForSaveDialog>().Single();
|
||||
return saveDialog != null;
|
||||
});
|
||||
AddStep("discard changes", () =>
|
||||
{
|
||||
var continueButton = saveDialog.ChildrenOfType<PopupDialogOkButton>().Single();
|
||||
continueButton.TriggerClick();
|
||||
});
|
||||
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
|
||||
AddStep("exit editor forcefully", () => Stack.Exit());
|
||||
// ensure editor loader didn't resume.
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
private void switchToDifficulty(Func<BeatmapInfo> difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
|
||||
|
||||
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
|
||||
{
|
||||
AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke()));
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Tests.Resources;
|
||||
using SharpCompress.Archives;
|
||||
@ -55,6 +56,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Test]
|
||||
public void TestExitWithoutSave()
|
||||
{
|
||||
EditorBeatmap editorBeatmap = null;
|
||||
|
||||
AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
|
||||
AddStep("exit without save", () =>
|
||||
{
|
||||
Editor.Exit();
|
||||
@ -62,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
});
|
||||
|
||||
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
|
||||
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
|
||||
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Configuration;
|
||||
@ -71,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var working = CreateWorkingBeatmap(rulesetInfo);
|
||||
|
||||
Beatmap.Value = working;
|
||||
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
|
||||
SelectedMods.Value = new[] { ruleset.CreateMod<ModNoFail>() };
|
||||
|
||||
Player = CreatePlayer(ruleset);
|
||||
|
||||
|
@ -54,7 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay })
|
||||
Recorder = recorder = new TestReplayRecorder(new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
|
||||
})
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
|
@ -45,7 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Recorder = new TestReplayRecorder(new Score { Replay = replay })
|
||||
Recorder = new TestReplayRecorder(new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
|
||||
})
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
|
||||
},
|
||||
|
@ -354,7 +354,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
internal class TestReplayRecorder : ReplayRecorder<TestAction>
|
||||
{
|
||||
public TestReplayRecorder()
|
||||
: base(new Score())
|
||||
: base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } })
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -130,6 +130,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Type = { Value = MatchType.HeadToHead },
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType<RecentParticipantsList>().Any());
|
||||
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("set password", () => room.Password.Value = "password");
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@ -51,6 +52,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("room join password correct", () => lastJoinedPassword == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPopoverHidesOnBackButton()
|
||||
{
|
||||
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
|
||||
|
||||
AddStep("hit escape", () => InputManager.Key(Key.Escape));
|
||||
AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
|
||||
|
||||
AddStep("hit escape", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPopoverHidesOnLeavingScreen()
|
||||
{
|
||||
@ -64,7 +83,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinRoomWithPassword()
|
||||
public void TestJoinRoomWithIncorrectPassword()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
|
||||
|
||||
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
|
||||
AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinRoomWithCorrectPassword()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
|
||||
@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
SelectedRoom.Value = new Room();
|
||||
|
||||
Child = new MultiplayerMatchFooter
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Height = 50
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Child = new MultiplayerMatchFooter()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
typeof(FileStore),
|
||||
typeof(ScoreManager),
|
||||
typeof(BeatmapManager),
|
||||
typeof(SettingsStore),
|
||||
typeof(RulesetConfigCache),
|
||||
typeof(OsuColour),
|
||||
typeof(IBindable<WorkingBeatmap>),
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -388,6 +389,19 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitGameFromSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
exitViaEscapeAndConfirm();
|
||||
|
||||
pushEscape(); // returns to osu! logo
|
||||
|
||||
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
|
||||
AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
|
||||
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
private void pushEscape() =>
|
||||
AddStep("Press escape", () => InputManager.Key(Key.Escape));
|
||||
|
||||
|
@ -5,10 +5,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneBeatmapListingOverlay : OsuTestScene
|
||||
public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene
|
||||
{
|
||||
private readonly List<APIBeatmapSet> setsForResponse = new List<APIBeatmapSet>();
|
||||
|
||||
@ -28,27 +29,33 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType<BeatmapListingSearchControl>().Single();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
AddStep("setup overlay", () =>
|
||||
{
|
||||
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
|
||||
|
||||
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
|
||||
{
|
||||
BeatmapSets = setsForResponse,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
|
||||
setsForResponse.Clear();
|
||||
});
|
||||
|
||||
AddStep("initialize dummy", () =>
|
||||
{
|
||||
var api = (DummyAPIAccess)API;
|
||||
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
|
||||
|
||||
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
|
||||
{
|
||||
BeatmapSets = setsForResponse,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// non-supporter user
|
||||
((DummyAPIAccess)API).LocalUser.Value = new User
|
||||
api.LocalUser.Value = new User
|
||||
{
|
||||
Username = "TestBot",
|
||||
Id = API.LocalUser.Value.Id + 1,
|
||||
@ -56,6 +63,51 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideViaBack()
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
AddStep("hide", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideViaBackWithSearch()
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("search something", () => overlay.ChildrenOfType<SearchTextBox>().First().Text = "search");
|
||||
|
||||
AddStep("kill search", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddAssert("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType<SearchTextBox>().First().Text));
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("hide", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideViaBackWithScrolledSearch()
|
||||
{
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray()));
|
||||
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
|
||||
AddStep("scroll to bottom", () => overlay.ChildrenOfType<OverlayScrollContainer>().First().ScrollToEnd());
|
||||
|
||||
AddStep("kill search", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType<SearchTextBox>().First().Text));
|
||||
AddUntilStep("is scrolled to top", () => overlay.ChildrenOfType<OverlayScrollContainer>().First().Current == 0);
|
||||
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("hide", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoBeatmapsPlaceholder()
|
||||
{
|
||||
@ -63,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
|
||||
|
||||
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
|
||||
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
|
||||
AddStep("fetch for 0 beatmaps", () => fetchFor());
|
||||
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
|
||||
@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
noPlaceholderShown();
|
||||
}
|
||||
|
||||
private static int searchCount;
|
||||
|
||||
private void fetchFor(params BeatmapSetInfo[] beatmaps)
|
||||
{
|
||||
setsForResponse.Clear();
|
||||
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
|
||||
|
||||
// trigger arbitrary change for fetching.
|
||||
searchControl.Query.TriggerChange();
|
||||
searchControl.Query.Value = $"search {searchCount++}";
|
||||
}
|
||||
|
||||
private void setRankAchievedFilter(ScoreRank[] ranks)
|
||||
@ -229,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private void noPlaceholderShown()
|
||||
{
|
||||
AddUntilStep("no placeholder shown", () =>
|
||||
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any()
|
||||
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
|
||||
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any(d => d.IsPresent)
|
||||
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
|
||||
}
|
||||
|
||||
private class TestAPIBeatmapSet : APIBeatmapSet
|
||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
||||
private int nextBeatmapSetId = 1;
|
||||
|
||||
public TestSceneBeatmapSetOverlay()
|
||||
{
|
||||
Add(overlay = new TestBeatmapSetOverlay());
|
||||
@ -240,12 +242,23 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("show explicit map", () =>
|
||||
{
|
||||
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
var beatmapSet = getBeatmapSet();
|
||||
beatmapSet.OnlineInfo.HasExplicitContent = true;
|
||||
overlay.ShowBeatmapSet(beatmapSet);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFeaturedBeatmap()
|
||||
{
|
||||
AddStep("show featured map", () =>
|
||||
{
|
||||
var beatmapSet = getBeatmapSet();
|
||||
beatmapSet.OnlineInfo.TrackId = 1;
|
||||
overlay.ShowBeatmapSet(beatmapSet);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHide()
|
||||
{
|
||||
@ -308,6 +321,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
}
|
||||
|
||||
private BeatmapSetInfo getBeatmapSet()
|
||||
{
|
||||
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
// Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`).
|
||||
beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++;
|
||||
return beatmapSet;
|
||||
}
|
||||
|
||||
private void downloadAssert(bool shown)
|
||||
{
|
||||
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -85,6 +86,22 @@ namespace osu.Game.Tests.Visual.Online
|
||||
case JoinChannelRequest joinChannel:
|
||||
joinChannel.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
case GetUserRequest getUser:
|
||||
if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
getUser.TriggerSuccess(new User
|
||||
{
|
||||
Username = "some body",
|
||||
Id = 1,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
getUser.TriggerFailure(new Exception());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -322,6 +339,27 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChatCommand()
|
||||
{
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
|
||||
AddAssert("PM channel is selected", () =>
|
||||
channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
|
||||
|
||||
AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat nobody"));
|
||||
AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage);
|
||||
|
||||
// Make sure no unnecessary requests are made when the PM channel is already open.
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null);
|
||||
AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
|
||||
AddAssert("PM channel is selected", () =>
|
||||
channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
|
||||
}
|
||||
|
||||
private void pressChannelHotkey(int number)
|
||||
{
|
||||
var channelKey = Key.Number0 + number;
|
||||
|
@ -99,16 +99,23 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
var normal = getBeatmapSet();
|
||||
normal.OnlineInfo.HasVideo = true;
|
||||
normal.OnlineInfo.HasStoryboard = true;
|
||||
|
||||
var undownloadable = getUndownloadableBeatmapSet();
|
||||
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
|
||||
|
||||
var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
var explicitMap = getBeatmapSet();
|
||||
explicitMap.OnlineInfo.HasExplicitContent = true;
|
||||
|
||||
var featuredMap = getBeatmapSet();
|
||||
featuredMap.OnlineInfo.TrackId = 1;
|
||||
|
||||
var explicitFeaturedMap = getBeatmapSet();
|
||||
explicitFeaturedMap.OnlineInfo.HasExplicitContent = true;
|
||||
explicitFeaturedMap.OnlineInfo.TrackId = 2;
|
||||
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online
|
||||
new GridBeatmapPanel(undownloadable),
|
||||
new GridBeatmapPanel(manyDifficulties),
|
||||
new GridBeatmapPanel(explicitMap),
|
||||
new GridBeatmapPanel(featuredMap),
|
||||
new GridBeatmapPanel(explicitFeaturedMap),
|
||||
new ListBeatmapPanel(normal),
|
||||
new ListBeatmapPanel(undownloadable),
|
||||
new ListBeatmapPanel(manyDifficulties),
|
||||
new ListBeatmapPanel(explicitMap)
|
||||
new ListBeatmapPanel(explicitMap),
|
||||
new ListBeatmapPanel(featuredMap),
|
||||
new ListBeatmapPanel(explicitFeaturedMap)
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType<RoomsContainer>().First();
|
||||
|
||||
[Test]
|
||||
public void TestManyRooms()
|
||||
{
|
||||
AddStep("add rooms", () => RoomManager.AddRooms(500));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollByDraggingRooms()
|
||||
{
|
||||
|
@ -160,11 +160,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
}));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => resultsScreen.ChildrenOfType<ScorePanelList>().FirstOrDefault()?.AllPanelsVisible == true);
|
||||
}
|
||||
|
||||
private void waitForDisplay()
|
||||
{
|
||||
AddUntilStep("wait for request to complete", () => requestComplete);
|
||||
AddUntilStep("wait for panels to be visible", () => resultsScreen.ChildrenOfType<ScorePanelList>().FirstOrDefault()?.AllPanelsVisible == true);
|
||||
AddWaitStep("wait for display", 5);
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
AddStep("click expanded panel", () =>
|
||||
{
|
||||
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
AddStep("click expanded panel", () =>
|
||||
{
|
||||
@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
ScorePanel expandedPanel = null;
|
||||
ScorePanel contractedPanel = null;
|
||||
@ -223,6 +223,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
AddAssert("download button is disabled", () => !screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||
|
||||
|
@ -159,6 +159,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
firstScore.User.Username = "A";
|
||||
secondScore.User.Username = "B";
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add scores and select first", () =>
|
||||
@ -168,6 +171,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
list.SelectedScore.Value = firstScore;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => list.AllPanelsVisible);
|
||||
|
||||
assertScoreState(firstScore, true);
|
||||
assertScoreState(secondScore, false);
|
||||
|
||||
@ -182,6 +187,87 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddScoreImmediately()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() =>
|
||||
{
|
||||
var newList = new ScorePanelList { SelectedScore = { Value = score } };
|
||||
newList.AddScore(score);
|
||||
return newList;
|
||||
});
|
||||
|
||||
assertScoreState(score, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardNavigation()
|
||||
{
|
||||
var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 };
|
||||
var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 };
|
||||
var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 };
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add scores and select middle", () =>
|
||||
{
|
||||
// order of addition purposefully scrambled.
|
||||
list.AddScore(middleScore);
|
||||
list.AddScore(lowestScore);
|
||||
list.AddScore(highestScore);
|
||||
list.SelectedScore.Value = middleScore;
|
||||
});
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, true);
|
||||
assertScoreState(lowestScore, false);
|
||||
|
||||
AddStep("press left", () => InputManager.Key(Key.Left));
|
||||
|
||||
assertScoreState(highestScore, true);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press left at start of list", () => InputManager.Key(Key.Left));
|
||||
|
||||
assertScoreState(highestScore, true);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press right", () => InputManager.Key(Key.Right));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, true);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press right again", () => InputManager.Key(Key.Right));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press right at end of list", () => InputManager.Key(Key.Right));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, false);
|
||||
assertScoreState(lowestScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
|
||||
AddStep("press left", () => InputManager.Key(Key.Left));
|
||||
|
||||
assertScoreState(highestScore, false);
|
||||
assertScoreState(middleScore, true);
|
||||
assertScoreState(lowestScore, false);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
private void createListStep(Func<ScorePanelList> creationFunc)
|
||||
{
|
||||
AddStep("create list", () => Child = list = creationFunc().With(d =>
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select EZ mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModEasy>().Single() };
|
||||
SelectedMods.Value = new[] { ruleset.CreateMod<ModEasy>() };
|
||||
});
|
||||
|
||||
AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue));
|
||||
@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select HR mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModHardRock>().Single() };
|
||||
SelectedMods.Value = new[] { ruleset.CreateMod<ModHardRock>() };
|
||||
});
|
||||
|
||||
AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue));
|
||||
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select unchanged Difficulty Adjust mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
var difficultyAdjustMod = ruleset.GetAllMods().OfType<ModDifficultyAdjust>().Single();
|
||||
var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>();
|
||||
difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty);
|
||||
SelectedMods.Value = new[] { difficultyAdjustMod };
|
||||
});
|
||||
@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("select changed Difficulty Adjust mod", () =>
|
||||
{
|
||||
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
|
||||
var difficultyAdjustMod = ruleset.GetAllMods().OfType<OsuModDifficultyAdjust>().Single();
|
||||
var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>();
|
||||
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
|
||||
|
||||
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
AddStep("setup display", () =>
|
||||
{
|
||||
var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList();
|
||||
var randomMods = Ruleset.Value.CreateInstance().CreateAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList();
|
||||
|
||||
OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) };
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
};
|
||||
|
||||
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
|
||||
control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToLowerInvariant())) : "")}", true);
|
||||
control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true);
|
||||
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
|
||||
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
|
||||
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private BeatmapManager beatmapManager;
|
||||
private ScoreManager scoreManager;
|
||||
|
||||
private readonly List<ScoreInfo> scores = new List<ScoreInfo>();
|
||||
private readonly List<ScoreInfo> importedScores = new List<ScoreInfo>();
|
||||
private BeatmapInfo beatmap;
|
||||
|
||||
[Cached]
|
||||
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
|
||||
|
||||
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
|
||||
|
||||
@ -100,11 +100,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
User = new User { Username = "TestUser" },
|
||||
};
|
||||
|
||||
scores.Add(scoreManager.Import(score).Result);
|
||||
importedScores.Add(scoreManager.Import(score).Result);
|
||||
}
|
||||
|
||||
scores.Sort(Comparer<ScoreInfo>.Create((s1, s2) => s2.TotalScore.CompareTo(s1.TotalScore)));
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@ -134,9 +132,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestDeleteViaRightClick()
|
||||
{
|
||||
ScoreInfo scoreBeingDeleted = null;
|
||||
AddStep("open menu for top score", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(leaderboard.ChildrenOfType<LeaderboardScore>().First());
|
||||
var leaderboardScore = leaderboard.ChildrenOfType<LeaderboardScore>().First();
|
||||
|
||||
scoreBeingDeleted = leaderboardScore.Score;
|
||||
|
||||
InputManager.MoveMouseTo(leaderboardScore);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
@ -158,14 +161,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteViaDatabase()
|
||||
{
|
||||
AddStep("delete top score", () => scoreManager.Delete(scores[0]));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
|
||||
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Width = 200,
|
||||
Current =
|
||||
{
|
||||
Value = new OsuRuleset().GetAllMods().ToArray(),
|
||||
Value = new OsuRuleset().CreateAllMods().ToArray(),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
|
||||
AddStep("change mod", () => icon.Mod = new OsuModEasy());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInterfaceModType()
|
||||
{
|
||||
ModIcon icon = null;
|
||||
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
|
||||
AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
var mania = new ManiaRuleset();
|
||||
|
||||
testModsWithSameBaseType(
|
||||
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)),
|
||||
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden)));
|
||||
mania.CreateMod<ManiaModFadeIn>(),
|
||||
mania.CreateMod<ManiaModHidden>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
private void success(APIBeatmap apiBeatmap)
|
||||
{
|
||||
beatmap = apiBeatmap.ToBeatmap(rulesets);
|
||||
var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods();
|
||||
var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods;
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
|
||||
var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0);
|
||||
var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym);
|
||||
var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym);
|
||||
|
||||
if (modIcon == null)
|
||||
return;
|
||||
|
@ -93,6 +93,12 @@ namespace osu.Game.Beatmaps
|
||||
public bool WidescreenStoryboard { get; set; }
|
||||
public bool EpilepsyWarning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not sound samples should change rate when playing with speed-changing mods.
|
||||
/// TODO: only read/write supported for now, requires implementation in gameplay.
|
||||
/// </summary>
|
||||
public bool SamplesMatchPlaybackRate { get; set; }
|
||||
|
||||
public CountdownType Countdown { get; set; } = CountdownType.Normal;
|
||||
|
||||
/// <summary>
|
||||
|
@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps
|
||||
Ruleset = ruleset,
|
||||
Metadata = metadata,
|
||||
WidescreenStoryboard = true,
|
||||
SamplesMatchPlaybackRate = true,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps
|
||||
/// The song language of this beatmap set.
|
||||
/// </summary>
|
||||
public BeatmapSetOnlineLanguage Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The track ID of this beatmap set.
|
||||
/// Non-null only if the track is linked to a featured artist track entry.
|
||||
/// </summary>
|
||||
public int? TrackId { get; set; }
|
||||
}
|
||||
|
||||
public class BeatmapSetOnlineGenre
|
||||
|
@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"SamplesMatchPlaybackRate":
|
||||
beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"Countdown":
|
||||
beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value);
|
||||
break;
|
||||
|
@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (beatmap.BeatmapInfo.RulesetID == 3)
|
||||
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
|
||||
// if (b.SamplesMatchPlaybackRate)
|
||||
// writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
|
||||
if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
|
||||
writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
|
||||
}
|
||||
|
||||
private void handleEditor(TextWriter writer)
|
||||
|
@ -1,103 +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 System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
public abstract class DatabasedConfigManager<TLookup> : ConfigManager<TLookup>
|
||||
where TLookup : struct, Enum
|
||||
{
|
||||
private readonly SettingsStore settings;
|
||||
|
||||
private readonly int? variant;
|
||||
|
||||
private List<DatabasedSetting> databasedSettings;
|
||||
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
private bool legacySettingsExist;
|
||||
|
||||
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.ruleset = ruleset;
|
||||
this.variant = variant;
|
||||
|
||||
Load();
|
||||
|
||||
InitialiseDefaults();
|
||||
}
|
||||
|
||||
protected override void PerformLoad()
|
||||
{
|
||||
databasedSettings = settings.Query(ruleset?.ID, variant);
|
||||
legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out _));
|
||||
}
|
||||
|
||||
protected override bool PerformSave()
|
||||
{
|
||||
lock (dirtySettings)
|
||||
{
|
||||
foreach (var setting in dirtySettings)
|
||||
settings.Update(setting);
|
||||
dirtySettings.Clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private readonly List<DatabasedSetting> dirtySettings = new List<DatabasedSetting>();
|
||||
|
||||
protected override void AddBindable<TBindable>(TLookup lookup, Bindable<TBindable> bindable)
|
||||
{
|
||||
base.AddBindable(lookup, bindable);
|
||||
|
||||
if (legacySettingsExist)
|
||||
{
|
||||
var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString());
|
||||
|
||||
if (legacySetting != null)
|
||||
{
|
||||
bindable.Parse(legacySetting.Value);
|
||||
settings.Delete(legacySetting);
|
||||
}
|
||||
}
|
||||
|
||||
var setting = databasedSettings.Find(s => s.Key == lookup.ToString());
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
bindable.Parse(setting.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.Update(setting = new DatabasedSetting
|
||||
{
|
||||
Key = lookup.ToString(),
|
||||
Value = bindable.Value,
|
||||
RulesetID = ruleset?.ID,
|
||||
Variant = variant,
|
||||
});
|
||||
|
||||
databasedSettings.Add(setting);
|
||||
}
|
||||
|
||||
bindable.ValueChanged += b =>
|
||||
{
|
||||
setting.Value = b.NewValue;
|
||||
|
||||
lock (dirtySettings)
|
||||
{
|
||||
if (!dirtySettings.Contains(setting))
|
||||
dirtySettings.Add(setting);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using osu.Game.Database;
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
[Table("Settings")]
|
||||
public class DatabasedSetting : IHasPrimaryKey
|
||||
public class DatabasedSetting : IHasPrimaryKey // can be removed 20220315.
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
|
32
osu.Game/Configuration/RealmRulesetSetting.cs
Normal file
32
osu.Game/Configuration/RealmRulesetSetting.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 osu.Game.Database;
|
||||
using Realms;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
[MapTo(@"RulesetSetting")]
|
||||
public class RealmRulesetSetting : RealmObject, IHasGuidPrimaryKey
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid ID { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Indexed]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[Indexed]
|
||||
public int Variant { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Value { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString() => $"{Key} => {Value}";
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -167,9 +168,21 @@ namespace osu.Game.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]> property_info_cache = new ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]>();
|
||||
|
||||
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj)
|
||||
{
|
||||
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
|
||||
var type = obj.GetType();
|
||||
|
||||
if (!property_info_cache.TryGetValue(type, out var properties))
|
||||
property_info_cache[type] = properties = getSettingsSourceProperties(type).ToArray();
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static IEnumerable<(SettingSourceAttribute, PropertyInfo)> getSettingsSourceProperties(Type type)
|
||||
{
|
||||
foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);
|
||||
|
||||
|
@ -1,46 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
public class SettingsStore : DatabaseBackedStore
|
||||
public class SettingsStore
|
||||
{
|
||||
public event Action SettingChanged;
|
||||
// this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager).
|
||||
// it may cease to exist going forward, depending on how the structure of the config data layer changes.
|
||||
|
||||
public SettingsStore(DatabaseContextFactory contextFactory)
|
||||
: base(contextFactory)
|
||||
public readonly RealmContextFactory Realm;
|
||||
|
||||
public SettingsStore(RealmContextFactory realmFactory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve <see cref="DatabasedSetting"/>s for a specified ruleset/variant content.
|
||||
/// </summary>
|
||||
/// <param name="rulesetId">The ruleset's internal ID.</param>
|
||||
/// <param name="variant">An optional variant.</param>
|
||||
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
|
||||
ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
||||
|
||||
public void Update(DatabasedSetting setting)
|
||||
{
|
||||
using (ContextFactory.GetForWrite())
|
||||
{
|
||||
var newValue = setting.Value;
|
||||
Refresh(ref setting);
|
||||
setting.Value = newValue;
|
||||
}
|
||||
|
||||
SettingChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Delete(DatabasedSetting setting)
|
||||
{
|
||||
using (var usage = ContextFactory.GetForWrite())
|
||||
usage.Context.Remove(setting);
|
||||
Realm = realmFactory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ using osu.Game.Configuration;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -24,14 +23,13 @@ namespace osu.Game.Database
|
||||
public DbSet<BeatmapDifficulty> BeatmapDifficulty { get; set; }
|
||||
public DbSet<BeatmapMetadata> BeatmapMetadata { get; set; }
|
||||
public DbSet<BeatmapSetInfo> BeatmapSetInfo { get; set; }
|
||||
public DbSet<DatabasedSetting> DatabasedSetting { get; set; }
|
||||
public DbSet<FileInfo> FileInfo { get; set; }
|
||||
public DbSet<RulesetInfo> RulesetInfo { get; set; }
|
||||
public DbSet<SkinInfo> SkinInfo { get; set; }
|
||||
public DbSet<ScoreInfo> ScoreInfo { get; set; }
|
||||
|
||||
// migrated to realm
|
||||
public DbSet<DatabasedKeyBinding> DatabasedKeyBinding { get; set; }
|
||||
public DbSet<DatabasedSetting> DatabasedSetting { get; set; }
|
||||
|
||||
private readonly string connectionString;
|
||||
|
||||
@ -138,11 +136,6 @@ namespace osu.Game.Database
|
||||
modelBuilder.Entity<SkinInfo>().HasIndex(b => b.Hash).IsUnique();
|
||||
modelBuilder.Entity<SkinInfo>().HasIndex(b => b.DeletePending);
|
||||
|
||||
modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => new { b.RulesetID, b.Variant });
|
||||
modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => b.IntAction);
|
||||
modelBuilder.Entity<DatabasedKeyBinding>().Ignore(b => b.KeyCombination);
|
||||
modelBuilder.Entity<DatabasedKeyBinding>().Ignore(b => b.Action);
|
||||
|
||||
modelBuilder.Entity<DatabasedSetting>().HasIndex(b => new { b.RulesetID, b.Variant });
|
||||
|
||||
modelBuilder.Entity<FileInfo>().HasIndex(b => b.Hash).IsUnique();
|
||||
|
@ -38,6 +38,33 @@ namespace osu.Game.Extensions
|
||||
return repeatDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shakes this drawable.
|
||||
/// </summary>
|
||||
/// <param name="target">The target to shake.</param>
|
||||
/// <param name="shakeDuration">The length of a single shake.</param>
|
||||
/// <param name="shakeMagnitude">Pixels of displacement per shake.</param>
|
||||
/// <param name="maximumLength">The maximum length the shake should last.</param>
|
||||
public static void Shake(this Drawable target, double shakeDuration = 80, float shakeMagnitude = 8, double? maximumLength = null)
|
||||
{
|
||||
// if we don't have enough time, don't bother shaking.
|
||||
if (maximumLength < shakeDuration * 2)
|
||||
return;
|
||||
|
||||
var sequence = target.MoveToX(shakeMagnitude, shakeDuration / 2, Easing.OutSine).Then()
|
||||
.MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then();
|
||||
|
||||
// if we don't have enough time for the second shake, skip it.
|
||||
if (!maximumLength.HasValue || maximumLength >= shakeDuration * 4)
|
||||
{
|
||||
sequence = sequence
|
||||
.MoveToX(shakeMagnitude, shakeDuration, Easing.InOutSine).Then()
|
||||
.MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then();
|
||||
}
|
||||
|
||||
sequence.MoveToX(0, shakeDuration / 2, Easing.InSine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position.
|
||||
/// </summary>
|
||||
|
@ -87,23 +87,25 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
|
||||
{
|
||||
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
|
||||
var linkCompiler = CreateLinkCompiler(drawables.OfType<SpriteText>());
|
||||
linkCompiler.RelativeSizeAxes = Axes.Both;
|
||||
linkCompiler.TooltipText = tooltipText;
|
||||
linkCompiler.Action = () =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
TooltipText = tooltipText,
|
||||
Action = () =>
|
||||
{
|
||||
if (action != null)
|
||||
action();
|
||||
else if (game != null)
|
||||
game.HandleLink(link);
|
||||
// fallback to handle cases where OsuGame is not available, ie. tournament client.
|
||||
else if (link.Action == LinkAction.External)
|
||||
host.OpenUrlExternally(link.Argument);
|
||||
},
|
||||
});
|
||||
if (action != null)
|
||||
action();
|
||||
else if (game != null)
|
||||
game.HandleLink(link);
|
||||
// fallback to handle cases where OsuGame is not available, ie. tournament client.
|
||||
else if (link.Action == LinkAction.External)
|
||||
host.OpenUrlExternally(link.Argument);
|
||||
};
|
||||
|
||||
AddInternal(linkCompiler);
|
||||
}
|
||||
|
||||
protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new DrawableLinkCompiler(parts);
|
||||
|
||||
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
|
||||
// However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation.
|
||||
// Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow.
|
||||
|
@ -1,8 +1,8 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Extensions;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
@ -16,40 +16,10 @@ namespace osu.Game.Graphics.Containers
|
||||
/// </summary>
|
||||
public float ShakeDuration = 80;
|
||||
|
||||
/// <summary>
|
||||
/// Total number of shakes. May be shortened if possible.
|
||||
/// </summary>
|
||||
public float TotalShakes = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Pixels of displacement per shake.
|
||||
/// </summary>
|
||||
public float ShakeMagnitude = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Shake the contents of this container.
|
||||
/// </summary>
|
||||
/// <param name="maximumLength">The maximum length the shake should last.</param>
|
||||
public void Shake(double? maximumLength = null)
|
||||
{
|
||||
const float shake_amount = 8;
|
||||
|
||||
// if we don't have enough time, don't bother shaking.
|
||||
if (maximumLength < ShakeDuration * 2)
|
||||
return;
|
||||
|
||||
var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then()
|
||||
.MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
|
||||
|
||||
// if we don't have enough time for the second shake, skip it.
|
||||
if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4)
|
||||
{
|
||||
sequence = sequence
|
||||
.MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then()
|
||||
.MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
|
||||
}
|
||||
|
||||
sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine);
|
||||
}
|
||||
public void Shake(double? maximumLength = null) => this.Shake(ShakeDuration, maximumLength: maximumLength);
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
public virtual bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!HasFocus) return false;
|
||||
|
||||
|
@ -46,7 +46,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
},
|
||||
};
|
||||
|
||||
Current.ValueChanged += filled => fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
|
||||
Current.ValueChanged += filled =>
|
||||
{
|
||||
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
|
||||
this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -4,14 +4,17 @@
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public class OsuPopover : Popover
|
||||
public class OsuPopover : Popover, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float fade_duration = 250;
|
||||
private const double scale_duration = 500;
|
||||
@ -51,5 +54,23 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
|
||||
this.FadeOut(fade_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return false;
|
||||
|
||||
if (action == GlobalAction.Back)
|
||||
{
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(GlobalAction action)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +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.ComponentModel.DataAnnotations.Schema;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Input.Bindings
|
||||
{
|
||||
[Table("KeyBinding")]
|
||||
public class DatabasedKeyBinding : IKeyBinding, IHasPrimaryKey
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public int? RulesetID { get; set; }
|
||||
|
||||
public int? Variant { get; set; }
|
||||
|
||||
[Column("Keys")]
|
||||
public string KeysString { get; set; }
|
||||
|
||||
[Column("Action")]
|
||||
public int IntAction { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public KeyCombination KeyCombination
|
||||
{
|
||||
get => KeysString;
|
||||
set => KeysString = value.ToString();
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public object Action
|
||||
{
|
||||
get => IntAction;
|
||||
set => IntAction = (int)value;
|
||||
}
|
||||
}
|
||||
}
|
515
osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
generated
Normal file
515
osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
generated
Normal file
@ -0,0 +1,515 @@
|
||||
// <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("20210912144011_AddSamplesMatchPlaybackRate")]
|
||||
partial class AddSamplesMatchPlaybackRate
|
||||
{
|
||||
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<int>("Countdown");
|
||||
|
||||
b.Property<int>("CountdownOffset");
|
||||
|
||||
b.Property<double>("DistanceSpacing");
|
||||
|
||||
b.Property<bool>("EpilepsyWarning");
|
||||
|
||||
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>("SamplesMatchPlaybackRate");
|
||||
|
||||
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<int>("AuthorID")
|
||||
.HasColumnName("AuthorID");
|
||||
|
||||
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.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<int?>("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>("InstantiationInfo");
|
||||
|
||||
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,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
public partial class AddSamplesMatchPlaybackRate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SamplesMatchPlaybackRate",
|
||||
table: "BeatmapInfo",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SamplesMatchPlaybackRate",
|
||||
table: "BeatmapInfo");
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,8 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<bool>("SamplesMatchPlaybackRate");
|
||||
|
||||
b.Property<bool>("SpecialStyle");
|
||||
|
||||
b.Property<float>("StackLeniency");
|
||||
|
@ -16,7 +16,7 @@ using osu.Game.Utils;
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
[MessagePackObject]
|
||||
public class APIMod : IMod, IEquatable<APIMod>
|
||||
public class APIMod : IEquatable<APIMod>
|
||||
{
|
||||
[JsonProperty("acronym")]
|
||||
[Key(0)]
|
||||
@ -48,31 +48,31 @@ namespace osu.Game.Online.API
|
||||
|
||||
public Mod ToMod(Ruleset ruleset)
|
||||
{
|
||||
Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym);
|
||||
Mod resultMod = ruleset.CreateModFromAcronym(Acronym);
|
||||
|
||||
if (resultMod == null)
|
||||
throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}.");
|
||||
|
||||
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
|
||||
if (Settings.Count > 0)
|
||||
{
|
||||
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
|
||||
continue;
|
||||
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
|
||||
{
|
||||
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
|
||||
continue;
|
||||
|
||||
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
|
||||
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
|
||||
}
|
||||
}
|
||||
|
||||
return resultMod;
|
||||
}
|
||||
|
||||
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);
|
||||
return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
|
||||
private readonly BeatmapInfo beatmap;
|
||||
private readonly BeatmapLeaderboardScope scope;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly IEnumerable<Mod> mods;
|
||||
private readonly IEnumerable<IMod> mods;
|
||||
|
||||
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<Mod> mods = null)
|
||||
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
|
||||
{
|
||||
if (!beatmap.OnlineBeatmapID.HasValue)
|
||||
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
|
||||
@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests
|
||||
this.beatmap = beatmap;
|
||||
this.scope = scope;
|
||||
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
|
||||
this.mods = mods ?? Array.Empty<Mod>();
|
||||
this.mods = mods ?? Array.Empty<IMod>();
|
||||
|
||||
Success += onSuccess;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserRequest : APIRequest<User>
|
||||
{
|
||||
private readonly string lookup;
|
||||
public readonly string Lookup;
|
||||
public readonly RulesetInfo Ruleset;
|
||||
private readonly LookupType lookupType;
|
||||
|
||||
@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests
|
||||
/// <param name="ruleset">The ruleset to get the user's info for.</param>
|
||||
public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
|
||||
{
|
||||
lookup = userId.ToString();
|
||||
Lookup = userId.ToString();
|
||||
lookupType = LookupType.Id;
|
||||
Ruleset = ruleset;
|
||||
}
|
||||
@ -38,12 +38,12 @@ namespace osu.Game.Online.API.Requests
|
||||
/// <param name="ruleset">The ruleset to get the user's info for.</param>
|
||||
public GetUserRequest(string username = null, RulesetInfo ruleset = null)
|
||||
{
|
||||
lookup = username;
|
||||
Lookup = username;
|
||||
lookupType = LookupType.Username;
|
||||
Ruleset = ruleset;
|
||||
}
|
||||
|
||||
protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}";
|
||||
protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}";
|
||||
|
||||
private enum LookupType
|
||||
{
|
||||
|
@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"ratings")]
|
||||
private int[] ratings { get; set; }
|
||||
|
||||
[JsonProperty(@"track_id")]
|
||||
private int? trackId { get; set; }
|
||||
|
||||
[JsonProperty(@"user_id")]
|
||||
private int creatorId
|
||||
{
|
||||
@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
Availability = availability,
|
||||
HasFavourited = hasFavourited,
|
||||
Genre = genre,
|
||||
Language = language
|
||||
Language = language,
|
||||
TrackId = trackId
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty<Mod>();
|
||||
var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty<Mod>();
|
||||
|
||||
// all API scores provided by this class are considered to be legacy.
|
||||
mods = mods.Append(rulesetInstance.GetAllMods().OfType<ModClassic>().Single()).ToArray();
|
||||
mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
|
||||
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Extensions;
|
||||
@ -83,7 +84,7 @@ namespace osu.Game.Online.API.Requests
|
||||
req.AddParameter("q", query);
|
||||
|
||||
if (General != null && General.Any())
|
||||
req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToLowerInvariant())));
|
||||
req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore())));
|
||||
|
||||
if (ruleset.ID.HasValue)
|
||||
req.AddParameter("m", ruleset.ID.Value.ToString());
|
||||
|
@ -256,8 +256,36 @@ namespace osu.Game.Online.Chat
|
||||
JoinChannel(channel);
|
||||
break;
|
||||
|
||||
case "chat":
|
||||
case "msg":
|
||||
case "query":
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
target.AddNewMessages(new ErrorMessage($"Usage: /{command} [user]"));
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the user has joined the requested channel already.
|
||||
// This uses the channel name for comparison as the PM user's username is unavailable after a restart.
|
||||
var privateChannel = JoinedChannels.FirstOrDefault(
|
||||
c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (privateChannel != null)
|
||||
{
|
||||
CurrentChannel.Value = privateChannel;
|
||||
break;
|
||||
}
|
||||
|
||||
var request = new GetUserRequest(content);
|
||||
request.Success += OpenPrivateChannel;
|
||||
request.Failure += e => target.AddNewMessages(
|
||||
new ErrorMessage(e.InnerException?.Message == @"NotFound" ? $"User '{content}' was not found." : $"Could not fetch user '{content}'."));
|
||||
|
||||
api.Queue(request);
|
||||
break;
|
||||
|
||||
case "help":
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np"));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -34,6 +34,8 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public const float HEIGHT = 60;
|
||||
|
||||
public readonly ScoreInfo Score;
|
||||
|
||||
private const float corner_radius = 5;
|
||||
private const float edge_margin = 5;
|
||||
private const float background_alpha = 0.25f;
|
||||
@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
protected Container RankContainer { get; private set; }
|
||||
|
||||
private readonly ScoreInfo score;
|
||||
private readonly int? rank;
|
||||
private readonly bool allowHighlight;
|
||||
|
||||
@ -67,7 +68,8 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true)
|
||||
{
|
||||
this.score = score;
|
||||
Score = score;
|
||||
|
||||
this.rank = rank;
|
||||
this.allowHighlight = allowHighlight;
|
||||
|
||||
@ -78,9 +80,9 @@ namespace osu.Game.Online.Leaderboards
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager)
|
||||
{
|
||||
var user = score.User;
|
||||
var user = Score.User;
|
||||
|
||||
statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList();
|
||||
statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList();
|
||||
|
||||
ClickableAvatar innerAvatar;
|
||||
|
||||
@ -198,7 +200,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
TextColour = Color4.White,
|
||||
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
|
||||
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||
Current = scoreManager.GetBindableTotalScoreString(Score),
|
||||
Font = OsuFont.Numeric.With(size: 23),
|
||||
},
|
||||
RankContainer = new Container
|
||||
@ -206,7 +208,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
Size = new Vector2(40f, 20f),
|
||||
Children = new[]
|
||||
{
|
||||
scoreRank = new UpdateableRank(score.Rank)
|
||||
scoreRank = new UpdateableRank(Score.Rank)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -223,7 +225,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1),
|
||||
ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
||||
ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -389,14 +391,14 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods));
|
||||
if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||
|
||||
if (score.Files?.Count > 0)
|
||||
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(score)));
|
||||
if (Score.Files?.Count > 0)
|
||||
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score)));
|
||||
|
||||
if (score.ID != 0)
|
||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score))));
|
||||
if (Score.ID != 0)
|
||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
@ -179,11 +179,7 @@ namespace osu.Game.Online.Rooms
|
||||
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
|
||||
Status.Value = new RoomStatusEnded();
|
||||
|
||||
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
|
||||
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
|
||||
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
|
||||
if (!(Status.Value is RoomStatusEnded))
|
||||
other.Playlist.RemoveAll(i => i.Expired);
|
||||
other.RemoveExpiredPlaylistItems();
|
||||
|
||||
if (!Playlist.SequenceEqual(other.Playlist))
|
||||
{
|
||||
@ -200,6 +196,15 @@ namespace osu.Game.Online.Rooms
|
||||
Position.Value = other.Position.Value;
|
||||
}
|
||||
|
||||
public void RemoveExpiredPlaylistItems()
|
||||
{
|
||||
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
|
||||
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
|
||||
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
|
||||
if (!(Status.Value is RoomStatusEnded))
|
||||
Playlist.RemoveAll(i => i.Expired);
|
||||
}
|
||||
|
||||
public bool ShouldSerializeRoomID() => false;
|
||||
public bool ShouldSerializeHost() => false;
|
||||
public bool ShouldSerializeEndDate() => false;
|
||||
|
@ -15,8 +15,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Scoring;
|
||||
@ -46,15 +44,8 @@ namespace osu.Game.Online.Spectator
|
||||
private readonly BindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
|
||||
|
||||
private IBeatmap? currentBeatmap;
|
||||
|
||||
private Score? currentScore;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> currentRuleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> currentMods { get; set; } = null!;
|
||||
|
||||
private readonly SpectatorState currentState = new SpectatorState();
|
||||
|
||||
/// <summary>
|
||||
@ -153,9 +144,9 @@ namespace osu.Game.Online.Spectator
|
||||
IsPlaying = true;
|
||||
|
||||
// transfer state at point of beginning play
|
||||
currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID;
|
||||
currentState.RulesetID = currentRuleset.Value.ID;
|
||||
currentState.Mods = currentMods.Value.Select(m => new APIMod(m));
|
||||
currentState.BeatmapID = score.ScoreInfo.Beatmap.OnlineBeatmapID;
|
||||
currentState.RulesetID = score.ScoreInfo.RulesetID;
|
||||
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
|
||||
|
||||
currentBeatmap = beatmap.PlayableBeatmap;
|
||||
currentScore = score;
|
||||
|
@ -140,8 +140,6 @@ namespace osu.Game
|
||||
|
||||
private FileStore fileStore;
|
||||
|
||||
private SettingsStore settingsStore;
|
||||
|
||||
private RulesetConfigCache rulesetConfigCache;
|
||||
|
||||
private SpectatorClient spectatorClient;
|
||||
@ -243,7 +241,7 @@ namespace osu.Game
|
||||
dependencies.Cache(fileStore = new FileStore(contextFactory, Storage));
|
||||
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true));
|
||||
|
||||
// this should likely be moved to ArchiveModelManager when another case appears where it is necessary
|
||||
@ -279,8 +277,7 @@ namespace osu.Game
|
||||
|
||||
migrateDataToRealm();
|
||||
|
||||
dependencies.Cache(settingsStore = new SettingsStore(contextFactory));
|
||||
dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore));
|
||||
dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore));
|
||||
|
||||
var powerStatus = CreateBatteryInfo();
|
||||
if (powerStatus != null)
|
||||
@ -453,24 +450,27 @@ namespace osu.Game
|
||||
using (var db = contextFactory.GetForWrite())
|
||||
using (var usage = realmFactory.GetForWrite())
|
||||
{
|
||||
var existingBindings = db.Context.DatabasedKeyBinding;
|
||||
// migrate ruleset settings. can be removed 20220315.
|
||||
var existingSettings = db.Context.DatabasedSetting;
|
||||
|
||||
// only migrate data if the realm database is empty.
|
||||
if (!usage.Realm.All<RealmKeyBinding>().Any())
|
||||
if (!usage.Realm.All<RealmRulesetSetting>().Any())
|
||||
{
|
||||
foreach (var dkb in existingBindings)
|
||||
foreach (var dkb in existingSettings)
|
||||
{
|
||||
usage.Realm.Add(new RealmKeyBinding
|
||||
if (dkb.RulesetID == null) continue;
|
||||
|
||||
usage.Realm.Add(new RealmRulesetSetting
|
||||
{
|
||||
KeyCombinationString = dkb.KeyCombination.ToString(),
|
||||
ActionInt = (int)dkb.Action,
|
||||
RulesetID = dkb.RulesetID,
|
||||
Variant = dkb.Variant
|
||||
Key = dkb.Key,
|
||||
Value = dkb.StringValue,
|
||||
RulesetID = dkb.RulesetID.Value,
|
||||
Variant = dkb.Variant ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
db.Context.RemoveRange(existingBindings);
|
||||
db.Context.RemoveRange(existingSettings);
|
||||
|
||||
usage.Commit();
|
||||
}
|
||||
|
@ -3,21 +3,22 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
@ -117,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
textBox = new BeatmapSearchTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TypingStarted = () => TypingStarted?.Invoke(),
|
||||
TextChanged = () => TypingStarted?.Invoke(),
|
||||
},
|
||||
new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
@ -127,7 +128,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
Padding = new MarginPadding { Horizontal = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
generalFilter = new BeatmapSearchMultipleSelectionFilterRow<SearchGeneral>(BeatmapsStrings.ListingSearchFiltersGeneral),
|
||||
generalFilter = new BeatmapSearchGeneralFilterRow(),
|
||||
modeFilter = new BeatmapSearchRulesetFilterRow(),
|
||||
categoryFilter = new BeatmapSearchFilterRow<SearchCategory>(BeatmapsStrings.ListingSearchFiltersStatus),
|
||||
genreFilter = new BeatmapSearchFilterRow<SearchGenre>(BeatmapsStrings.ListingSearchFiltersGenre),
|
||||
@ -167,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
/// <summary>
|
||||
/// Any time the text box receives key events (even while masked).
|
||||
/// </summary>
|
||||
public Action TypingStarted;
|
||||
public Action TextChanged;
|
||||
|
||||
protected override Color4 SelectionColour => Color4.Gray;
|
||||
|
||||
@ -181,7 +182,16 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
if (!base.OnKeyDown(e))
|
||||
return false;
|
||||
|
||||
TypingStarted?.Invoke();
|
||||
TextChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!base.OnPressed(action))
|
||||
return false;
|
||||
|
||||
TextChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
// 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.Game.Resources.Localisation.Web;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapSearchGeneralFilterRow : BeatmapSearchMultipleSelectionFilterRow<SearchGeneral>
|
||||
{
|
||||
public BeatmapSearchGeneralFilterRow()
|
||||
: base(BeatmapsStrings.ListingSearchFiltersGeneral)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter();
|
||||
|
||||
private class GeneralFilter : MultipleSelectionFilter
|
||||
{
|
||||
protected override MultipleSelectionFilterTabItem CreateTabItem(SearchGeneral value)
|
||||
{
|
||||
if (value == SearchGeneral.FeaturedArtists)
|
||||
return new FeaturedArtistsTabItem();
|
||||
|
||||
return new MultipleSelectionFilterTabItem(value);
|
||||
}
|
||||
}
|
||||
|
||||
private class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem
|
||||
{
|
||||
public FeaturedArtistsTabItem()
|
||||
: base(SearchGeneral.FeaturedArtists)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1;
|
||||
}
|
||||
}
|
||||
}
|
@ -71,10 +71,10 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint);
|
||||
text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint);
|
||||
text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular);
|
||||
}
|
||||
|
||||
private Color4 getStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2;
|
||||
protected virtual Color4 GetStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
private const float horizontal_padding = 10;
|
||||
private const float vertical_padding = 5;
|
||||
|
||||
private FillFlowContainer bottomPanel, statusContainer, titleContainer;
|
||||
private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer;
|
||||
private PlayButton playButton;
|
||||
private Box progressBar;
|
||||
|
||||
@ -89,11 +89,19 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
artistContainer = new FillFlowContainer
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
},
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Container
|
||||
@ -213,6 +221,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.TrackId != null)
|
||||
{
|
||||
artistContainer.Add(new FeaturedArtistBeatmapPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 10f, Top = 2f },
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.HasVideo ?? false)
|
||||
{
|
||||
statusContainer.Add(new IconPill(FontAwesome.Solid.Film));
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
private const float vertical_padding = 5;
|
||||
private const float height = 70;
|
||||
|
||||
private FillFlowContainer statusContainer, titleContainer;
|
||||
private FillFlowContainer statusContainer, titleContainer, artistContainer;
|
||||
protected BeatmapPanelDownloadButton DownloadButton;
|
||||
private PlayButton playButton;
|
||||
private Box progressBar;
|
||||
@ -112,10 +112,18 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
artistContainer = new FillFlowContainer
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -227,6 +235,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.TrackId != null)
|
||||
{
|
||||
artistContainer.Add(new FeaturedArtistBeatmapPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 10f, Top = 2f },
|
||||
});
|
||||
}
|
||||
|
||||
if (SetInfo.OnlineInfo?.HasVideo ?? false)
|
||||
{
|
||||
statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
||||
|
@ -19,6 +19,10 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFollows))]
|
||||
[Description("Subscribed mappers")]
|
||||
Follows
|
||||
Follows,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFeaturedArtists))]
|
||||
[Description("Featured artists")]
|
||||
FeaturedArtists
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Masking = true,
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -186,21 +187,16 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (lastContent != null)
|
||||
{
|
||||
var transform = lastContent.FadeOut(100, Easing.OutQuint);
|
||||
lastContent.FadeOut(100, Easing.OutQuint);
|
||||
|
||||
if (lastContent == notFoundContent || lastContent == supporterRequiredContent)
|
||||
{
|
||||
// the placeholders may be used multiple times, so don't expire/dispose them.
|
||||
transform.Schedule(() => panelTarget.Remove(lastContent));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Consider the case when the new content is smaller than the last content.
|
||||
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
||||
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
||||
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
||||
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire());
|
||||
}
|
||||
// Consider the case when the new content is smaller than the last content.
|
||||
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
||||
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
||||
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
||||
var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
|
||||
|
||||
if (lastContent != notFoundContent && lastContent != supporterRequiredContent)
|
||||
sequence.Then().Schedule(() => lastContent.Expire());
|
||||
}
|
||||
|
||||
if (!content.IsAlive)
|
||||
@ -208,6 +204,9 @@ namespace osu.Game.Overlays
|
||||
|
||||
content.FadeInFromZero(200, Easing.OutQuint);
|
||||
currentContent = content;
|
||||
// currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out.
|
||||
// restore to the initial state.
|
||||
currentContent.BypassAutoSizeAxes = Axes.None;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private readonly OsuSpriteText title, artist;
|
||||
private readonly AuthorInfo author;
|
||||
private readonly ExplicitContentBeatmapPill explicitContentPill;
|
||||
private readonly FeaturedArtistBeatmapPill featuredArtistPill;
|
||||
private readonly FillFlowContainer downloadButtonsContainer;
|
||||
private readonly BeatmapAvailability beatmapAvailability;
|
||||
private readonly BeatmapSetOnlineStatusPill onlineStatusPill;
|
||||
@ -129,10 +130,25 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
}
|
||||
}
|
||||
},
|
||||
artist = new OsuSpriteText
|
||||
new FillFlowContainer
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true),
|
||||
Margin = new MarginPadding { Bottom = 20 }
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
artist = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true),
|
||||
},
|
||||
featuredArtistPill = new FeaturedArtistBeatmapPill
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 10 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -233,6 +249,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist);
|
||||
|
||||
explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0;
|
||||
featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0;
|
||||
|
||||
onlineStatusPill.FadeIn(500, Easing.OutQuint);
|
||||
onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status;
|
||||
|
47
osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs
Normal file
47
osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapPill.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public class FeaturedArtistBeatmapPill : CompositeDrawable
|
||||
{
|
||||
public FeaturedArtistBeatmapPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, OverlayColourProvider colourProvider)
|
||||
{
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider?.Background5 ?? colours.Gray2,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f },
|
||||
Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(),
|
||||
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
|
||||
Colour = OverlayColourProvider.Blue.Colour1,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public class LeaderboardModSelector : CompositeDrawable
|
||||
{
|
||||
public readonly BindableList<Mod> SelectedMods = new BindableList<Mod>();
|
||||
public readonly BindableList<IMod> SelectedMods = new BindableList<IMod>();
|
||||
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
private readonly FillFlowContainer<ModButton> modsContainer;
|
||||
@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
return;
|
||||
|
||||
modsContainer.Add(new ModButton(new ModNoMod()));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m)));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m)));
|
||||
|
||||
modsContainer.ForEach(button =>
|
||||
{
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
updateHighlighted();
|
||||
}
|
||||
|
||||
private void selectionChanged(Mod mod, bool selected)
|
||||
private void selectionChanged(IMod mod, bool selected)
|
||||
{
|
||||
if (selected)
|
||||
SelectedMods.Add(mod);
|
||||
@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private const int duration = 200;
|
||||
|
||||
public readonly BindableBool Highlighted = new BindableBool();
|
||||
public Action<Mod, bool> OnSelectionChanged;
|
||||
public Action<IMod, bool> OnSelectionChanged;
|
||||
|
||||
public ModButton(Mod mod)
|
||||
public ModButton(IMod mod)
|
||||
: base(mod)
|
||||
{
|
||||
Scale = new Vector2(0.4f);
|
||||
|
@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
@ -14,6 +16,7 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Users;
|
||||
|
||||
@ -42,34 +45,46 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
private GetScoresRequest getScoresRequest;
|
||||
|
||||
private CancellationTokenSource loadCancellationSource;
|
||||
|
||||
protected APILegacyScores Scores
|
||||
{
|
||||
set => Schedule(() =>
|
||||
{
|
||||
loadCancellationSource?.Cancel();
|
||||
loadCancellationSource = new CancellationTokenSource();
|
||||
|
||||
topScoresContainer.Clear();
|
||||
scoreTable.ClearScores();
|
||||
scoreTable.Hide();
|
||||
|
||||
if (value?.Scores.Any() != true)
|
||||
{
|
||||
scoreTable.ClearScores();
|
||||
scoreTable.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList();
|
||||
var topScore = scoreInfos.First();
|
||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(ordered => Schedule(() =>
|
||||
{
|
||||
if (loadCancellationSource.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true);
|
||||
scoreTable.Show();
|
||||
var topScore = ordered.Result.First();
|
||||
|
||||
var userScore = value.UserScore;
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets);
|
||||
scoreTable.DisplayScores(ordered.Result, topScore.Beatmap?.Status.GrantsPerformancePoints() == true);
|
||||
scoreTable.Show();
|
||||
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
var userScore = value.UserScore;
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets);
|
||||
|
||||
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID)
|
||||
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
|
||||
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID)
|
||||
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Overlays.Changelog
|
||||
private const float image_container_width = 164;
|
||||
private const float heart_size = 75;
|
||||
|
||||
private readonly FillFlowContainer textContainer;
|
||||
private readonly Container imageContainer;
|
||||
|
||||
public ChangelogSupporterPromo()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -38,6 +35,12 @@ namespace osu.Game.Overlays.Changelog
|
||||
Vertical = 20,
|
||||
Horizontal = 50,
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider)
|
||||
{
|
||||
SupporterPromoLinkFlowContainer supportLinkText;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Changelog
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.3f),
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Changelog
|
||||
Padding = new MarginPadding { Horizontal = 75 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textContainer = new FillFlowContainer
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@ -76,91 +79,84 @@ namespace osu.Game.Overlays.Changelog
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Right = 50 + image_container_width },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = ChangelogStrings.SupportHeading,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
},
|
||||
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 14);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
new OsuTextFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 12);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
Text = ChangelogStrings.SupportText2.ToString(),
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
},
|
||||
},
|
||||
imageContainer = new Container
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = image_container_width,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 28 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = textures.Get(@"Online/supporter-pippi"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(heart_size),
|
||||
Margin = new MarginPadding { Top = 70 },
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = colour.Pink,
|
||||
Radius = 10,
|
||||
Roundness = heart_size / 2,
|
||||
},
|
||||
Child = new Sprite
|
||||
{
|
||||
Size = new Vector2(heart_size),
|
||||
Texture = textures.Get(@"Online/supporter-heart"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour, TextureStore textures)
|
||||
{
|
||||
SupporterPromoLinkFlowContainer supportLinkText;
|
||||
textContainer.Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = ChangelogStrings.SupportHeading,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
},
|
||||
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 14);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
new OsuTextFlowContainer(t =>
|
||||
{
|
||||
t.Font = t.Font.With(size: 12);
|
||||
t.Colour = colour.PinkLighter;
|
||||
})
|
||||
{
|
||||
Text = ChangelogStrings.SupportText2.ToString(),
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
};
|
||||
|
||||
supportLinkText.AddText("Support further development of osu! and ");
|
||||
supportLinkText.AddLink("become and osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||
supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||
supportLinkText.AddText(" today!");
|
||||
|
||||
imageContainer.Children = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 28 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = textures.Get(@"Online/supporter-pippi"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(heart_size),
|
||||
Margin = new MarginPadding { Top = 70 },
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = colour.Pink,
|
||||
Radius = 10,
|
||||
Roundness = heart_size / 2,
|
||||
},
|
||||
Child = new Sprite
|
||||
{
|
||||
Size = new Vector2(heart_size),
|
||||
Texture = textures.Get(@"Online/supporter-heart"),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private class SupporterPromoLinkFlowContainer : LinkFlowContainer
|
||||
@ -170,27 +166,18 @@ namespace osu.Game.Overlays.Changelog
|
||||
{
|
||||
}
|
||||
|
||||
public new void AddLink(string text, string url, Action<SpriteText> creationParameters) =>
|
||||
AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url });
|
||||
protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new SupporterPromoLinkCompiler(parts);
|
||||
|
||||
private class SupporterPromoLinkCompiler : DrawableLinkCompiler
|
||||
{
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
public string Url;
|
||||
|
||||
public SupporterPromoLinkCompiler(IEnumerable<Drawable> parts)
|
||||
: base(parts)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour)
|
||||
{
|
||||
TooltipText = Url;
|
||||
Action = () => game?.HandleLink(Url);
|
||||
IdleColour = colour.PinkDark;
|
||||
HoverColour = Color4.White;
|
||||
}
|
||||
|
@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
var incompatibleTypes = mod.IncompatibleMods;
|
||||
|
||||
var allMods = ruleset.Value.CreateInstance().GetAllMods();
|
||||
var allMods = ruleset.Value.CreateInstance().AllMods;
|
||||
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList();
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList();
|
||||
incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods";
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
avatar = new UpdateableAvatar(openOnClick: false, showGuestOnNull: false)
|
||||
avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false)
|
||||
{
|
||||
Size = new Vector2(avatar_size),
|
||||
Masking = true,
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
{
|
||||
Text = InputSettingsStrings.ResetSectionButton;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Width = 0.5f;
|
||||
Width = 0.8f;
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
Margin = new MarginPadding { Top = 15 };
|
||||
|
@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
|
||||
private OsuDirectorySelector directorySelector;
|
||||
|
||||
public override bool AllowTrackAdjustments => false;
|
||||
|
||||
/// <summary>
|
||||
/// Text to display in the header to inform the user of what they are selecting.
|
||||
/// </summary>
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
Add(new OpaqueBackground { Depth = 1 });
|
||||
|
||||
Flow.Add(avatar = new UpdateableAvatar(openOnClick: false)
|
||||
Flow.Add(avatar = new UpdateableAvatar(isInteractive: false)
|
||||
{
|
||||
Masking = true,
|
||||
Size = new Vector2(32),
|
||||
|
@ -355,6 +355,12 @@ namespace osu.Game.Overlays.Volume
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
State = SelectionState.Selected;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
}
|
||||
|
@ -2,16 +2,86 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Rulesets.Configuration
|
||||
{
|
||||
public abstract class RulesetConfigManager<TLookup> : DatabasedConfigManager<TLookup>, IRulesetConfigManager
|
||||
public abstract class RulesetConfigManager<TLookup> : ConfigManager<TLookup>, IRulesetConfigManager
|
||||
where TLookup : struct, Enum
|
||||
{
|
||||
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
|
||||
: base(settings, ruleset, variant)
|
||||
private readonly RealmContextFactory realmFactory;
|
||||
|
||||
private readonly int variant;
|
||||
|
||||
private List<RealmRulesetSetting> databasedSettings = new List<RealmRulesetSetting>();
|
||||
|
||||
private readonly int rulesetId;
|
||||
|
||||
protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null)
|
||||
{
|
||||
realmFactory = store?.Realm;
|
||||
|
||||
if (realmFactory != null && !ruleset.ID.HasValue)
|
||||
throw new InvalidOperationException("Attempted to add databased settings for a non-databased ruleset");
|
||||
|
||||
rulesetId = ruleset.ID ?? -1;
|
||||
|
||||
this.variant = variant ?? 0;
|
||||
|
||||
Load();
|
||||
|
||||
InitialiseDefaults();
|
||||
}
|
||||
|
||||
protected override void PerformLoad()
|
||||
{
|
||||
if (realmFactory != null)
|
||||
{
|
||||
// As long as RulesetConfigCache exists, there is no need to subscribe to realm events.
|
||||
databasedSettings = realmFactory.Context.All<RealmRulesetSetting>().Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool PerformSave()
|
||||
{
|
||||
// do nothing, realm saves immediately
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void AddBindable<TBindable>(TLookup lookup, Bindable<TBindable> bindable)
|
||||
{
|
||||
base.AddBindable(lookup, bindable);
|
||||
|
||||
var setting = databasedSettings.Find(s => s.Key == lookup.ToString());
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
bindable.Parse(setting.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
setting = new RealmRulesetSetting
|
||||
{
|
||||
Key = lookup.ToString(),
|
||||
Value = bindable.Value.ToString(),
|
||||
RulesetID = rulesetId,
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
realmFactory?.Context.Write(() => realmFactory.Context.Add(setting));
|
||||
|
||||
databasedSettings.Add(setting);
|
||||
}
|
||||
|
||||
bindable.ValueChanged += b =>
|
||||
{
|
||||
realmFactory?.Context.Write(() => setting.Value = b.NewValue.ToString());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -11,7 +11,37 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// <summary>
|
||||
/// The shortened name of this mod.
|
||||
/// </summary>
|
||||
[JsonProperty("acronym")]
|
||||
string Acronym { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this mod.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user readable description of this mod.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this mod.
|
||||
/// </summary>
|
||||
ModType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon of this mod.
|
||||
/// </summary>
|
||||
IconUsage? Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod is playable by an end user.
|
||||
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example).
|
||||
/// </summary>
|
||||
bool UserPlayable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||
/// </summary>
|
||||
Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType());
|
||||
}
|
||||
}
|
||||
|
@ -22,32 +22,17 @@ namespace osu.Game.Rulesets.Mods
|
||||
[ExcludeFromDynamicCompile]
|
||||
public abstract class Mod : IMod, IEquatable<Mod>, IDeepCloneable<Mod>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The shortened name of this mod.
|
||||
/// </summary>
|
||||
public abstract string Acronym { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual IconUsage? Icon => null;
|
||||
|
||||
/// <summary>
|
||||
/// The type of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual ModType Type => ModType.Fun;
|
||||
|
||||
/// <summary>
|
||||
/// The user readable description of this mod.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public abstract string Description { get; }
|
||||
|
||||
@ -106,10 +91,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
[JsonIgnore]
|
||||
public virtual bool HasImplementation => this is IApplicableMod;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod is playable by an end user.
|
||||
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example).
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public virtual bool UserPlayable => true;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
@ -38,13 +39,58 @@ namespace osu.Game.Rulesets
|
||||
{
|
||||
public RulesetInfo RulesetInfo { get; internal set; }
|
||||
|
||||
public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
|
||||
// Confine all mods of each mod type into a single IEnumerable<Mod>
|
||||
.SelectMany(GetModsFor)
|
||||
// Filter out all null mods
|
||||
.Where(mod => mod != null)
|
||||
// Resolve MultiMods as their .Mods property
|
||||
.SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
|
||||
private static readonly ConcurrentDictionary<int, IMod[]> mod_reference_cache = new ConcurrentDictionary<int, IMod[]>();
|
||||
|
||||
/// <summary>
|
||||
/// A queryable source containing all available mods.
|
||||
/// Call <see cref="IMod.CreateInstance"/> for consumption purposes.
|
||||
/// </summary>
|
||||
public IEnumerable<IMod> AllMods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!(RulesetInfo.ID is int id))
|
||||
return CreateAllMods();
|
||||
|
||||
if (!mod_reference_cache.TryGetValue(id, out var mods))
|
||||
mod_reference_cache[id] = mods = CreateAllMods().Cast<IMod>().ToArray();
|
||||
|
||||
return mods;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns fresh instances of all mods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings)
|
||||
/// use <see cref="AllMods"/> instead.
|
||||
/// </remarks>
|
||||
public IEnumerable<Mod> CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
|
||||
// Confine all mods of each mod type into a single IEnumerable<Mod>
|
||||
.SelectMany(GetModsFor)
|
||||
// Filter out all null mods
|
||||
.Where(mod => mod != null)
|
||||
// Resolve MultiMods as their .Mods property
|
||||
.SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
|
||||
|
||||
/// <summary>
|
||||
/// Returns a fresh instance of the mod matching the specified acronym.
|
||||
/// </summary>
|
||||
/// <param name="acronym">The acronym to query for .</param>
|
||||
public Mod CreateModFromAcronym(string acronym)
|
||||
{
|
||||
return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a fresh instance of the mod matching the specified type.
|
||||
/// </summary>
|
||||
public T CreateMod<T>()
|
||||
where T : Mod
|
||||
{
|
||||
return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T;
|
||||
}
|
||||
|
||||
public abstract IEnumerable<Mod> GetModsFor(ModType type);
|
||||
|
||||
@ -126,7 +172,7 @@ namespace osu.Game.Rulesets
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault();
|
||||
public ModAutoplay GetAutoplayMod() => CreateMod<ModAutoplay>();
|
||||
|
||||
public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null;
|
||||
|
||||
|
@ -2,9 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
@ -15,12 +16,31 @@ namespace osu.Game.Rulesets
|
||||
/// </summary>
|
||||
public class RulesetConfigCache : Component
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, IRulesetConfigManager> configCache = new ConcurrentDictionary<int, IRulesetConfigManager>();
|
||||
private readonly SettingsStore settingsStore;
|
||||
private readonly RealmContextFactory realmFactory;
|
||||
private readonly RulesetStore rulesets;
|
||||
|
||||
public RulesetConfigCache(SettingsStore settingsStore)
|
||||
private readonly Dictionary<int, IRulesetConfigManager> configCache = new Dictionary<int, IRulesetConfigManager>();
|
||||
|
||||
public RulesetConfigCache(RealmContextFactory realmFactory, RulesetStore rulesets)
|
||||
{
|
||||
this.settingsStore = settingsStore;
|
||||
this.realmFactory = realmFactory;
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var settingsStore = new SettingsStore(realmFactory);
|
||||
|
||||
// let's keep things simple for now and just retrieve all the required configs at startup..
|
||||
foreach (var ruleset in rulesets.AvailableRulesets)
|
||||
{
|
||||
if (ruleset.ID == null)
|
||||
continue;
|
||||
|
||||
configCache[ruleset.ID.Value] = ruleset.CreateInstance().CreateConfig(settingsStore);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -34,7 +54,12 @@ namespace osu.Game.Rulesets
|
||||
if (ruleset.RulesetInfo.ID == null)
|
||||
return null;
|
||||
|
||||
return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore));
|
||||
if (!configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var config))
|
||||
// any ruleset request which wasn't initialised on startup should not be stored to realm.
|
||||
// this should only be used by tests.
|
||||
return ruleset.CreateConfig(null);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user