mirror of
https://github.com/ppy/osu.git
synced 2025-01-29 08:52:57 +08:00
Merge remote-tracking branch 'upstream/master' into duplicate-multi-room
This commit is contained in:
commit
7a33a6b08a
73
Gemfile.lock
73
Gemfile.lock
@ -6,35 +6,36 @@ GEM
|
|||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.329.0)
|
aws-partitions (1.354.0)
|
||||||
aws-sdk-core (3.99.2)
|
aws-sdk-core (3.104.3)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.34.1)
|
aws-sdk-kms (1.36.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.68.1)
|
aws-sdk-s3 (1.78.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.104.3)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.1.4)
|
aws-sigv4 (1.2.1)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.3)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander-fastlane (4.4.6)
|
commander-fastlane (4.4.6)
|
||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
declarative (0.0.10)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
digest-crc (0.5.1)
|
digest-crc (0.6.1)
|
||||||
|
rake (~> 13.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.6)
|
||||||
emoji_regex (1.0.1)
|
emoji_regex (3.0.0)
|
||||||
excon (0.74.0)
|
excon (0.76.0)
|
||||||
faraday (1.0.1)
|
faraday (1.0.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
@ -42,34 +43,32 @@ GEM
|
|||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.1.7)
|
fastimage (2.2.0)
|
||||||
fastlane (2.149.1)
|
fastlane (2.156.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 2.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
faraday (>= 0.17, < 2.0)
|
faraday (~> 1.0)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (>= 0.13.1, < 2.0)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.37.0, < 0.39.0)
|
google-api-client (>= 0.37.0, < 0.39.0)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
highline (>= 1.7.2, < 2.0.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (~> 2.1.0)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multi_xml (~> 0.5)
|
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
public_suffix (~> 2.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
rubyzip (>= 1.3.0, < 2.0.0)
|
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||||
@ -97,17 +96,17 @@ GEM
|
|||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.2)
|
google-cloud-env (1.3.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.26.2)
|
google-cloud-storage (1.27.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-api-client (~> 0.33)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.12.0)
|
googleauth (0.13.1)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@ -119,29 +118,29 @@ GEM
|
|||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.0)
|
json (2.3.1)
|
||||||
jwt (2.1.0)
|
jwt (2.2.1)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.10.1)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
multi_json (1.14.1)
|
multi_json (1.15.0)
|
||||||
multi_xml (0.6.0)
|
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.2.6)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.0)
|
naturally (2.2.0)
|
||||||
nokogiri (1.10.7)
|
nokogiri (1.10.10)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
os (1.1.0)
|
os (1.1.1)
|
||||||
plist (3.5.0)
|
plist (3.5.0)
|
||||||
public_suffix (2.0.5)
|
public_suffix (4.0.5)
|
||||||
|
rake (13.0.1)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
rubyzip (1.3.0)
|
rubyzip (2.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.14.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
@ -160,7 +159,7 @@ GEM
|
|||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.0)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
@ -169,12 +168,12 @@ GEM
|
|||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.16.0)
|
xcodeproj (1.18.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.2.6)
|
nanaimo (~> 0.3.0)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.0)
|
xcpretty-travis-formatter (1.0.0)
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.731.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.812.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.806.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.814.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
65
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
Normal file
65
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModSpunOut : OsuModTestScene
|
||||||
|
{
|
||||||
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinnerAutoCompleted() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSpunOut(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = singleSpinnerBeatmap,
|
||||||
|
PassCondition = () => Player.ChildrenOfType<DrawableSpinner>().Single().Progress >= 1
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCase(null)]
|
||||||
|
[TestCase(typeof(OsuModDoubleTime))]
|
||||||
|
[TestCase(typeof(OsuModHalfTime))]
|
||||||
|
public void TestSpinRateUnaffectedByMods(Type additionalModType)
|
||||||
|
{
|
||||||
|
var mods = new List<Mod> { new OsuModSpunOut() };
|
||||||
|
if (additionalModType != null)
|
||||||
|
mods.Add((Mod)Activator.CreateInstance(additionalModType));
|
||||||
|
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mods = mods,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = singleSpinnerBeatmap,
|
||||||
|
PassCondition = () => Precision.AlmostEquals(Player.ChildrenOfType<SpinnerSpmCounter>().Single().SpinsPerMinute, 286, 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Beatmap singleSpinnerBeatmap => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = 500,
|
||||||
|
Duration = 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -69,11 +70,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||||
});
|
});
|
||||||
AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
|
AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
|
||||||
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100));
|
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100));
|
||||||
|
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
|
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
|
||||||
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, 0, 100));
|
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -94,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
||||||
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
||||||
});
|
});
|
||||||
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.CumulativeRotation);
|
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.RateAdjustedRotation);
|
||||||
|
|
||||||
addSeekStep(2500);
|
addSeekStep(2500);
|
||||||
AddAssert("disc rotation rewound",
|
AddAssert("disc rotation rewound",
|
||||||
@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
|
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
|
||||||
AddAssert("is cumulative rotation rewound",
|
AddAssert("is cumulative rotation rewound",
|
||||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalCumulativeTrackerRotation / 2, 100));
|
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
||||||
|
|
||||||
addSeekStep(5000);
|
addSeekStep(5000);
|
||||||
AddAssert("is disc rotation almost same",
|
AddAssert("is disc rotation almost same",
|
||||||
@ -114,26 +115,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("is symbol rotation almost same",
|
AddAssert("is symbol rotation almost same",
|
||||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
|
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
|
||||||
AddAssert("is cumulative rotation almost same",
|
AddAssert("is cumulative rotation almost same",
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.CumulativeRotation, finalCumulativeTrackerRotation, 100));
|
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRotationDirection([Values(true, false)] bool clockwise)
|
public void TestRotationDirection([Values(true, false)] bool clockwise)
|
||||||
{
|
{
|
||||||
if (clockwise)
|
if (clockwise)
|
||||||
{
|
transformReplay(flip);
|
||||||
AddStep("flip replay", () =>
|
|
||||||
{
|
|
||||||
var drawableRuleset = this.ChildrenOfType<DrawableOsuRuleset>().Single();
|
|
||||||
var score = drawableRuleset.ReplayScore;
|
|
||||||
var scoreWithFlippedReplay = new Score
|
|
||||||
{
|
|
||||||
ScoreInfo = score.ScoreInfo,
|
|
||||||
Replay = flipReplay(score.Replay)
|
|
||||||
};
|
|
||||||
drawableRuleset.SetReplayScore(scoreWithFlippedReplay);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addSeekStep(5000);
|
addSeekStep(5000);
|
||||||
|
|
||||||
@ -141,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0);
|
AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Replay flipReplay(Replay scoreReplay) => new Replay
|
private Replay flip(Replay scoreReplay) => new Replay
|
||||||
{
|
{
|
||||||
Frames = scoreReplay
|
Frames = scoreReplay
|
||||||
.Frames
|
.Frames
|
||||||
@ -164,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||||
var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
|
var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
|
||||||
return totalScore == (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360) * SpinnerTick.SCORE_PER_TICK;
|
return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * SpinnerTick.SCORE_PER_TICK;
|
||||||
});
|
});
|
||||||
|
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
@ -196,6 +185,49 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(0.5)]
|
||||||
|
[TestCase(2.0)]
|
||||||
|
public void TestSpinUnaffectedByClockRate(double rate)
|
||||||
|
{
|
||||||
|
double expectedProgress = 0;
|
||||||
|
double expectedSpm = 0;
|
||||||
|
|
||||||
|
addSeekStep(1000);
|
||||||
|
AddStep("retrieve spinner state", () =>
|
||||||
|
{
|
||||||
|
expectedProgress = drawableSpinner.Progress;
|
||||||
|
expectedSpm = drawableSpinner.SpmCounter.SpinsPerMinute;
|
||||||
|
});
|
||||||
|
|
||||||
|
addSeekStep(0);
|
||||||
|
|
||||||
|
AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
|
||||||
|
// autoplay replay frames use track time;
|
||||||
|
// if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time.
|
||||||
|
// therefore we need to apply the rate adjustment to the replay itself to change from track time to real time,
|
||||||
|
// as real time is what we care about for spinners
|
||||||
|
// (so we're making the spin take 1000ms in real time *always*, regardless of the track clock's rate).
|
||||||
|
transformReplay(replay => applyRateAdjustment(replay, rate));
|
||||||
|
|
||||||
|
addSeekStep(1000);
|
||||||
|
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
||||||
|
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpmCounter.SpinsPerMinute, 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay
|
||||||
|
{
|
||||||
|
Frames = scoreReplay
|
||||||
|
.Frames
|
||||||
|
.Cast<OsuReplayFrame>()
|
||||||
|
.Select(replayFrame =>
|
||||||
|
{
|
||||||
|
var adjustedTime = replayFrame.Time * rate;
|
||||||
|
return new OsuReplayFrame(adjustedTime, replayFrame.Position, replayFrame.Actions.ToArray());
|
||||||
|
})
|
||||||
|
.Cast<ReplayFrame>()
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
private void addSeekStep(double time)
|
private void addSeekStep(double time)
|
||||||
{
|
{
|
||||||
AddStep($"seek to {time}", () => track.Seek(time));
|
AddStep($"seek to {time}", () => track.Seek(time));
|
||||||
@ -203,6 +235,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
||||||
|
{
|
||||||
|
var drawableRuleset = this.ChildrenOfType<DrawableOsuRuleset>().Single();
|
||||||
|
var score = drawableRuleset.ReplayScore;
|
||||||
|
var transformedScore = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = score.ScoreInfo,
|
||||||
|
Replay = replayTransformation.Invoke(score.Replay)
|
||||||
|
};
|
||||||
|
drawableRuleset.SetReplayScore(transformedScore);
|
||||||
|
});
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
{
|
{
|
||||||
HitObjects = new List<HitObject>
|
HitObjects = new List<HitObject>
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestSceneSpinnerSpunOut : OsuTestScene
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp() => Schedule(() =>
|
|
||||||
{
|
|
||||||
SelectedMods.Value = new[] { new OsuModSpunOut() };
|
|
||||||
});
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSpunOut()
|
|
||||||
{
|
|
||||||
DrawableSpinner spinner = null;
|
|
||||||
|
|
||||||
AddStep("create spinner", () => spinner = createSpinner());
|
|
||||||
|
|
||||||
AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd);
|
|
||||||
|
|
||||||
AddAssert("spinner is completed", () => spinner.Progress >= 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DrawableSpinner createSpinner()
|
|
||||||
{
|
|
||||||
var spinner = new Spinner
|
|
||||||
{
|
|
||||||
StartTime = Time.Current + 500,
|
|
||||||
EndTime = Time.Current + 2500
|
|
||||||
};
|
|
||||||
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
|
||||||
|
|
||||||
var drawableSpinner = new DrawableSpinner(spinner)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawableSpinner });
|
|
||||||
|
|
||||||
Add(drawableSpinner);
|
|
||||||
return drawableSpinner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -41,7 +41,16 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var spinner = (DrawableSpinner)drawable;
|
var spinner = (DrawableSpinner)drawable;
|
||||||
|
|
||||||
spinner.RotationTracker.Tracking = true;
|
spinner.RotationTracker.Tracking = true;
|
||||||
spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f));
|
|
||||||
|
// early-return if we were paused to avoid division-by-zero in the subsequent calculations.
|
||||||
|
if (Precision.AlmostEquals(spinner.Clock.Rate, 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// because the spinner is under the gameplay clock, it is affected by rate adjustments on the track;
|
||||||
|
// for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time.
|
||||||
|
// for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here.
|
||||||
|
var rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate;
|
||||||
|
spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * 0.03f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -82,8 +83,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private SkinnableSound spinningSample;
|
private SkinnableSound spinningSample;
|
||||||
|
|
||||||
private const float minimum_volume = 0.0001f;
|
|
||||||
|
|
||||||
protected override void LoadSamples()
|
protected override void LoadSamples()
|
||||||
{
|
{
|
||||||
base.LoadSamples();
|
base.LoadSamples();
|
||||||
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
AddInternal(spinningSample = new SkinnableSound(clone)
|
AddInternal(spinningSample = new SkinnableSound(clone)
|
||||||
{
|
{
|
||||||
Volume = { Value = minimum_volume },
|
Volume = { Value = 0 },
|
||||||
Looping = true,
|
Looping = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -118,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spinningSample?.VolumeTo(minimum_volume, 200).Finally(_ => spinningSample.Stop());
|
spinningSample?.VolumeTo(0, 200).Finally(_ => spinningSample.Stop());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// these become implicitly hit.
|
// these become implicitly hit.
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return Math.Clamp(RotationTracker.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1);
|
return Math.Clamp(RotationTracker.RateAdjustedRotation / 360 / Spinner.SpinsRequired, 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +231,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
||||||
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||||
SpmCounter.SetRotation(RotationTracker.CumulativeRotation);
|
SpmCounter.SetRotation(RotationTracker.RateAdjustedRotation);
|
||||||
|
|
||||||
updateBonusScore();
|
updateBonusScore();
|
||||||
}
|
}
|
||||||
@ -244,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (ticks.Count == 0)
|
if (ticks.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int spins = (int)(RotationTracker.CumulativeRotation / 360);
|
int spins = (int)(RotationTracker.RateAdjustedRotation / 360);
|
||||||
|
|
||||||
if (spins < wholeSpins)
|
if (spins < wholeSpins)
|
||||||
{
|
{
|
||||||
|
@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
int rotations = (int)(drawableSpinner.RotationTracker.CumulativeRotation / 360);
|
int rotations = (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360);
|
||||||
|
|
||||||
if (wholeRotationCount == rotations) return false;
|
if (wholeRotationCount == rotations) return false;
|
||||||
|
|
||||||
|
@ -31,17 +31,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
public readonly BindableBool Complete = new BindableBool();
|
public readonly BindableBool Complete = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total rotation performed on the spinner disc, disregarding the spin direction.
|
/// The total rotation performed on the spinner disc, disregarding the spin direction,
|
||||||
|
/// adjusted for the track's playback rate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// This value is always non-negative and is monotonically increasing with time
|
/// This value is always non-negative and is monotonically increasing with time
|
||||||
/// (i.e. will only increase if time is passing forward, but can decrease during rewind).
|
/// (i.e. will only increase if time is passing forward, but can decrease during rewind).
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The rotation from each frame is multiplied by the clock's current playback rate.
|
||||||
|
/// The reason this is done is to ensure that spinners give the same score and require the same number of spins
|
||||||
|
/// regardless of whether speed-modifying mods are applied.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise,
|
/// Assuming no speed-modifying mods are active,
|
||||||
|
/// if the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise,
|
||||||
/// this property will return the value of 720 (as opposed to 0 for <see cref="Drawable.Rotation"/>).
|
/// this property will return the value of 720 (as opposed to 0 for <see cref="Drawable.Rotation"/>).
|
||||||
|
/// If Double Time is active instead (with a speed multiplier of 1.5x),
|
||||||
|
/// in the same scenario the property will return 720 * 1.5 = 1080.
|
||||||
/// </example>
|
/// </example>
|
||||||
public float CumulativeRotation { get; private set; }
|
public float RateAdjustedRotation { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the spinning is spinning at a reasonable speed to be considered visually spinning.
|
/// Whether the spinning is spinning at a reasonable speed to be considered visually spinning.
|
||||||
@ -113,7 +124,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentRotation += angle;
|
currentRotation += angle;
|
||||||
CumulativeRotation += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
|
// rate has to be applied each frame, because it's not guaranteed to be constant throughout playback
|
||||||
|
// (see: ModTimeRamp)
|
||||||
|
RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
var spinner = (Spinner)drawableSpinner.HitObject;
|
var spinner = (Spinner)drawableSpinner.HitObject;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
||||||
this.FadeInFromZero(spinner.TimePreempt / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||||
|
|
||||||
fixedMiddle.FadeColour(Color4.White);
|
fixedMiddle.FadeColour(Color4.White);
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime, true))
|
using (BeginAbsoluteSequence(spinner.StartTime, true))
|
||||||
|
@ -85,8 +85,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
var spinner = drawableSpinner.HitObject;
|
var spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
||||||
this.FadeInFromZero(spinner.TimePreempt / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -272,7 +272,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectionResetOnVisibilityChange()
|
||||||
|
{
|
||||||
|
showOverlay();
|
||||||
|
AddStep("Select last button", () => InputManager.Key(Key.Up));
|
||||||
|
|
||||||
|
hideOverlay();
|
||||||
|
showOverlay();
|
||||||
|
|
||||||
|
AddAssert("No button selected",
|
||||||
|
() => pauseOverlay.Buttons.All(button => !button.Selected.Value));
|
||||||
|
}
|
||||||
|
|
||||||
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
|
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
|
||||||
|
private void hideOverlay() => AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||||
|
|
||||||
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
|
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
|
||||||
|
|
||||||
|
@ -46,25 +46,37 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="interactive">If the test player should behave like the production one.</param>
|
/// <param name="interactive">If the test player should behave like the production one.</param>
|
||||||
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
|
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
|
||||||
/// <param name="afterLoadAction">An action to run after container load.</param>
|
public void ResetPlayer(bool interactive, Action beforeLoadAction = null)
|
||||||
public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null)
|
|
||||||
{
|
{
|
||||||
|
player = null;
|
||||||
|
|
||||||
audioManager.Volume.SetDefault();
|
audioManager.Volume.SetDefault();
|
||||||
|
|
||||||
InputManager.Clear();
|
InputManager.Clear();
|
||||||
|
|
||||||
|
container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||||
|
|
||||||
beforeLoadAction?.Invoke();
|
beforeLoadAction?.Invoke();
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||||
|
|
||||||
InputManager.Child = container = new TestPlayerLoaderContainer(
|
InputManager.Child = container;
|
||||||
loader = new TestPlayerLoader(() =>
|
}
|
||||||
{
|
|
||||||
afterLoadAction?.Invoke();
|
[Test]
|
||||||
return player = new TestPlayer(interactive, interactive);
|
public void TestEarlyExitBeforePlayerConstruction()
|
||||||
}));
|
{
|
||||||
|
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
||||||
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
|
||||||
|
AddStep("exit loader", () => loader.Exit());
|
||||||
|
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
|
||||||
|
AddAssert("player did not load", () => player == null);
|
||||||
|
AddUntilStep("player disposed", () => loader.DisposalTask == null);
|
||||||
|
AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -73,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
/// speed adjustments were undone too late, causing cross-screen pollution.
|
/// speed adjustments were undone too late, causing cross-screen pollution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEarlyExit()
|
public void TestEarlyExitAfterPlayerConstruction()
|
||||||
{
|
{
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
|
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
|
||||||
|
AddUntilStep("wait for non-null player", () => player != null);
|
||||||
AddStep("exit loader", () => loader.Exit());
|
AddStep("exit loader", () => loader.Exit());
|
||||||
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
|
||||||
AddAssert("player did not load", () => !player.IsLoaded);
|
AddAssert("player did not load", () => !player.IsLoaded);
|
||||||
@ -94,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for load ready", () =>
|
AddUntilStep("wait for load ready", () =>
|
||||||
{
|
{
|
||||||
moveMouse();
|
moveMouse();
|
||||||
return player.LoadState == LoadState.Ready;
|
return player?.LoadState == LoadState.Ready;
|
||||||
});
|
});
|
||||||
AddRepeatStep("move mouse", moveMouse, 20);
|
AddRepeatStep("move mouse", moveMouse, 20);
|
||||||
|
|
||||||
@ -195,19 +208,19 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMutedNotificationMasterVolume()
|
public void TestMutedNotificationMasterVolume()
|
||||||
{
|
{
|
||||||
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault);
|
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMutedNotificationTrackVolume()
|
public void TestMutedNotificationTrackVolume()
|
||||||
{
|
{
|
||||||
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault);
|
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMutedNotificationMuteButton()
|
public void TestMutedNotificationMuteButton()
|
||||||
{
|
{
|
||||||
addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value);
|
addVolumeSteps("mute button", () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@ -215,14 +228,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="volumeName">What part of the volume system is checked</param>
|
/// <param name="volumeName">What part of the volume system is checked</param>
|
||||||
/// <param name="beforeLoad">The action to be invoked to set the volume before loading</param>
|
/// <param name="beforeLoad">The action to be invoked to set the volume before loading</param>
|
||||||
/// <param name="afterLoad">The action to be invoked to set the volume after loading</param>
|
|
||||||
/// <param name="assert">The function to be invoked and checked</param>
|
/// <param name="assert">The function to be invoked and checked</param>
|
||||||
private void addVolumeSteps(string volumeName, Action beforeLoad, Action afterLoad, Func<bool> assert)
|
private void addVolumeSteps(string volumeName, Action beforeLoad, Func<bool> assert)
|
||||||
{
|
{
|
||||||
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
|
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
|
||||||
|
|
||||||
AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad));
|
AddStep("load player", () => ResetPlayer(false, beforeLoad));
|
||||||
AddUntilStep("wait for player", () => player.LoadState == LoadState.Ready);
|
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
|
||||||
|
|
||||||
AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1);
|
AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1);
|
||||||
AddStep("click notification", () =>
|
AddStep("click notification", () =>
|
||||||
|
@ -16,7 +16,9 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Multi.Components;
|
using osu.Game.Screens.Multi.Components;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
@ -145,6 +147,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2);
|
AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the same <see cref="Mod"/> instances are not shared between two playlist items.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestNewItemHasNewModInstances()
|
||||||
|
{
|
||||||
|
AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
||||||
|
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
|
||||||
|
AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2);
|
||||||
|
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
|
||||||
|
|
||||||
|
AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
|
||||||
|
AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
|
||||||
|
}
|
||||||
|
|
||||||
private class TestMatchSongSelect : MatchSongSelect
|
private class TestMatchSongSelect : MatchSongSelect
|
||||||
{
|
{
|
||||||
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
||||||
|
61
osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs
Normal file
61
osu.Game.Tests/Visual/Online/TestSceneHomeNewsPanel.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using System;
|
||||||
|
using osu.Game.Overlays.Dashboard.Home.News;
|
||||||
|
using osuTK;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneHomeNewsPanel : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
public TestSceneHomeNewsPanel()
|
||||||
|
{
|
||||||
|
Add(new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 500,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FeaturedNewsItemPanel(new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "This post has an image which starts with \"/\" and has many authors!",
|
||||||
|
Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||||
|
FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png",
|
||||||
|
PublishedAt = DateTimeOffset.Now,
|
||||||
|
Slug = "2020-07-16-summer-theme-park-2020-voting-open"
|
||||||
|
}),
|
||||||
|
new NewsItemGroupPanel(new List<APINewsPost>
|
||||||
|
{
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Title 1",
|
||||||
|
Slug = "2020-07-16-summer-theme-park-2020-voting-open",
|
||||||
|
PublishedAt = DateTimeOffset.Now,
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Title of this post is Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||||
|
Slug = "2020-07-16-summer-theme-park-2020-voting-open",
|
||||||
|
PublishedAt = DateTimeOffset.Now,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new ShowMoreNewsPanel()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -64,5 +64,77 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
}, 0, true);
|
}, 0, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClearButtonOnBindings()
|
||||||
|
{
|
||||||
|
KeyBindingRow backBindingRow = null;
|
||||||
|
|
||||||
|
AddStep("click back binding row", () =>
|
||||||
|
{
|
||||||
|
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||||
|
InputManager.MoveMouseTo(backBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
clickClearButton();
|
||||||
|
|
||||||
|
AddAssert("first binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text));
|
||||||
|
|
||||||
|
AddStep("click second binding", () =>
|
||||||
|
{
|
||||||
|
var target = backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(target);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
clickClearButton();
|
||||||
|
|
||||||
|
AddAssert("second binding cleared", () => string.IsNullOrEmpty(backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text));
|
||||||
|
|
||||||
|
void clickClearButton()
|
||||||
|
{
|
||||||
|
AddStep("click clear button", () =>
|
||||||
|
{
|
||||||
|
var clearButton = backBindingRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(clearButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClickRowSelectsFirstBinding()
|
||||||
|
{
|
||||||
|
KeyBindingRow backBindingRow = null;
|
||||||
|
|
||||||
|
AddStep("click back binding row", () =>
|
||||||
|
{
|
||||||
|
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||||
|
InputManager.MoveMouseTo(backBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first binding selected", () => backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||||
|
|
||||||
|
AddStep("click second binding", () =>
|
||||||
|
{
|
||||||
|
var target = backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1);
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(target);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("click back binding row", () =>
|
||||||
|
{
|
||||||
|
backBindingRow = panel.ChildrenOfType<KeyBindingRow>().ElementAt(10);
|
||||||
|
InputManager.MoveMouseTo(backBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first binding selected", () => backBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -15,6 +16,7 @@ using osu.Game.Overlays.Mods;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -75,6 +77,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.Alpha == 0);
|
AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModSettingsUnboundWhenCopied()
|
||||||
|
{
|
||||||
|
OsuModDoubleTime original = null;
|
||||||
|
OsuModDoubleTime copy = null;
|
||||||
|
|
||||||
|
AddStep("create mods", () =>
|
||||||
|
{
|
||||||
|
original = new OsuModDoubleTime();
|
||||||
|
copy = (OsuModDoubleTime)original.CreateCopy();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change property", () => original.SpeedChange.Value = 2);
|
||||||
|
|
||||||
|
AddAssert("original has new value", () => Precision.AlmostEquals(2.0, original.SpeedChange.Value));
|
||||||
|
AddAssert("copy has original value", () => Precision.AlmostEquals(1.5, copy.SpeedChange.Value));
|
||||||
|
}
|
||||||
|
|
||||||
private void createModSelect()
|
private void createModSelect()
|
||||||
{
|
{
|
||||||
AddStep("create mod select", () =>
|
AddStep("create mod select", () =>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -67,19 +67,18 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
if (beatmapSet != null)
|
if (beatmapSet != null)
|
||||||
{
|
{
|
||||||
BeatmapSetCover cover;
|
Add(displayedCover = new DelayedLoadUnloadWrapper(() =>
|
||||||
|
{
|
||||||
Add(displayedCover = new DelayedLoadWrapper(
|
var cover = new BeatmapSetCover(beatmapSet, coverType)
|
||||||
cover = new BeatmapSetCover(beatmapSet, coverType)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
})
|
};
|
||||||
);
|
cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out);
|
||||||
|
return cover;
|
||||||
cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out);
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore textures)
|
private void load(LargeTextureStore textures)
|
||||||
{
|
{
|
||||||
Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName);
|
Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName);
|
||||||
}
|
}
|
||||||
|
@ -683,9 +683,8 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
overlay.State.ValueChanged += state =>
|
overlay.State.ValueChanged += state =>
|
||||||
{
|
{
|
||||||
if (state.NewValue == Visibility.Hidden) return;
|
if (state.NewValue != Visibility.Hidden)
|
||||||
|
showOverlayAboveOthers(overlay, informationalOverlays);
|
||||||
informationalOverlays.Where(o => o != overlay).ForEach(o => o.Hide());
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,9 +698,8 @@ namespace osu.Game
|
|||||||
// informational overlays should be dismissed on a show or hide of a full overlay.
|
// informational overlays should be dismissed on a show or hide of a full overlay.
|
||||||
informationalOverlays.ForEach(o => o.Hide());
|
informationalOverlays.ForEach(o => o.Hide());
|
||||||
|
|
||||||
if (state.NewValue == Visibility.Hidden) return;
|
if (state.NewValue != Visibility.Hidden)
|
||||||
|
showOverlayAboveOthers(overlay, singleDisplayOverlays);
|
||||||
singleDisplayOverlays.Where(o => o != overlay).ForEach(o => o.Hide());
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,6 +724,15 @@ namespace osu.Game
|
|||||||
notifications.State.ValueChanged += _ => updateScreenOffset();
|
notifications.State.ValueChanged += _ => updateScreenOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
|
||||||
|
{
|
||||||
|
otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide());
|
||||||
|
|
||||||
|
// show above others if not visible at all, else leave at current depth.
|
||||||
|
if (!overlay.IsPresent)
|
||||||
|
overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
public class GameIdleTracker : IdleTracker
|
public class GameIdleTracker : IdleTracker
|
||||||
{
|
{
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
55
osu.Game/Overlays/Dashboard/Home/HomePanel.cs
Normal file
55
osu.Game/Overlays/Dashboard/Home/HomePanel.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Dashboard.Home
|
||||||
|
{
|
||||||
|
public class HomePanel : Container
|
||||||
|
{
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
private readonly Container content;
|
||||||
|
private readonly Box background;
|
||||||
|
|
||||||
|
public HomePanel()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Masking = true;
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Colour = Color4.Black.Opacity(0.25f),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 3,
|
||||||
|
Offset = new Vector2(0, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
background.Colour = colourProvider.Background4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
195
osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs
Normal file
195
osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// 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.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays.News;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Dashboard.Home.News
|
||||||
|
{
|
||||||
|
public class FeaturedNewsItemPanel : HomePanel
|
||||||
|
{
|
||||||
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
|
public FeaturedNewsItemPanel(APINewsPost post)
|
||||||
|
{
|
||||||
|
this.post = post;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ClickableNewsBackground(post),
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, size: 60),
|
||||||
|
new Dimension(GridSizeMode.Absolute, size: 20),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Date(post.PublishedAt),
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Vertical = 10 },
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Width = 1,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Colour = colourProvider.Light1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Margin = new MarginPadding { Top = 5, Bottom = 10 },
|
||||||
|
Padding = new MarginPadding { Right = 10 },
|
||||||
|
Spacing = new Vector2(0, 10),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new NewsTitleLink(post),
|
||||||
|
new TextFlowContainer(f =>
|
||||||
|
{
|
||||||
|
f.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = post.Preview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClickableNewsBackground : OsuHoverContainer
|
||||||
|
{
|
||||||
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
|
public ClickableNewsBackground(APINewsPost post)
|
||||||
|
{
|
||||||
|
this.post = post;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host)
|
||||||
|
{
|
||||||
|
NewsPostBackground bg;
|
||||||
|
|
||||||
|
Child = new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
|
||||||
|
bg.OnLoadComplete += d => d.FadeIn(250, Easing.In);
|
||||||
|
|
||||||
|
TooltipText = "view in browser";
|
||||||
|
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
||||||
|
|
||||||
|
HoverColour = Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Date : CompositeDrawable, IHasCustomTooltip
|
||||||
|
{
|
||||||
|
public ITooltip GetCustomTooltip() => new DateTooltip();
|
||||||
|
|
||||||
|
public object TooltipContent => date;
|
||||||
|
|
||||||
|
private readonly DateTimeOffset date;
|
||||||
|
|
||||||
|
public Date(DateTimeOffset date)
|
||||||
|
{
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.TopRight;
|
||||||
|
Origin = Anchor.TopRight;
|
||||||
|
Margin = new MarginPadding { Top = 10 };
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative
|
||||||
|
Colour = colourProvider.Light1,
|
||||||
|
Text = $"{date:dd}"
|
||||||
|
},
|
||||||
|
new TextFlowContainer(f =>
|
||||||
|
{
|
||||||
|
f.Font = OsuFont.GetFont(size: 11, weight: FontWeight.Regular);
|
||||||
|
f.Colour = colourProvider.Light1;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Text = $"{date:MMM yyyy}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs
Normal file
115
osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Dashboard.Home.News
|
||||||
|
{
|
||||||
|
public class NewsGroupItem : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
|
public NewsGroupItem(APINewsPost post)
|
||||||
|
{
|
||||||
|
this.post = post;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, size: 60),
|
||||||
|
new Dimension(GridSizeMode.Absolute, size: 20),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Date(post.PublishedAt),
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Width = 1,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Colour = colourProvider.Light1
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Padding = new MarginPadding { Right = 10 },
|
||||||
|
Child = new NewsTitleLink(post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Date : CompositeDrawable, IHasCustomTooltip
|
||||||
|
{
|
||||||
|
public ITooltip GetCustomTooltip() => new DateTooltip();
|
||||||
|
|
||||||
|
public object TooltipContent => date;
|
||||||
|
|
||||||
|
private readonly DateTimeOffset date;
|
||||||
|
|
||||||
|
public Date(DateTimeOffset date)
|
||||||
|
{
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
TextFlowContainer textFlow;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.TopRight;
|
||||||
|
Origin = Anchor.TopRight;
|
||||||
|
InternalChild = textFlow = new TextFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Colour = colourProvider.Light1;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Margin = new MarginPadding { Vertical = 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
textFlow.AddText($"{date:dd}", t =>
|
||||||
|
{
|
||||||
|
t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold);
|
||||||
|
});
|
||||||
|
|
||||||
|
textFlow.AddText($"{date: MMM}", t =>
|
||||||
|
{
|
||||||
|
t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
osu.Game/Overlays/Dashboard/Home/News/NewsItemGroupPanel.cs
Normal file
36
osu.Game/Overlays/Dashboard/Home/News/NewsItemGroupPanel.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Dashboard.Home.News
|
||||||
|
{
|
||||||
|
public class NewsItemGroupPanel : HomePanel
|
||||||
|
{
|
||||||
|
private readonly List<APINewsPost> posts;
|
||||||
|
|
||||||
|
public NewsItemGroupPanel(List<APINewsPost> posts)
|
||||||
|
{
|
||||||
|
this.posts = posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Content.Padding = new MarginPadding { Vertical = 5 };
|
||||||
|
|
||||||
|
Child = new FillFlowContainer<NewsGroupItem>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = posts.Select(p => new NewsGroupItem(p)).ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs
Normal file
45
osu.Game/Overlays/Dashboard/Home/News/NewsTitleLink.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Dashboard.Home.News
|
||||||
|
{
|
||||||
|
public class NewsTitleLink : OsuHoverContainer
|
||||||
|
{
|
||||||
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
|
public NewsTitleLink(APINewsPost post)
|
||||||
|
{
|
||||||
|
this.post = post;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Child = new TextFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Font = OsuFont.GetFont(weight: FontWeight.Bold);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = post.Title
|
||||||
|
};
|
||||||
|
|
||||||
|
HoverColour = colourProvider.Light1;
|
||||||
|
|
||||||
|
TooltipText = "view in browser";
|
||||||
|
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
osu.Game/Overlays/Dashboard/Home/News/ShowMoreNewsPanel.cs
Normal file
51
osu.Game/Overlays/Dashboard/Home/News/ShowMoreNewsPanel.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Dashboard.Home.News
|
||||||
|
{
|
||||||
|
public class ShowMoreNewsPanel : OsuHoverContainer
|
||||||
|
{
|
||||||
|
protected override IEnumerable<Drawable> EffectTargets => new[] { text };
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private NewsOverlay overlay { get; set; }
|
||||||
|
|
||||||
|
private OsuSpriteText text;
|
||||||
|
|
||||||
|
public ShowMoreNewsPanel()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Child = new HomePanel
|
||||||
|
{
|
||||||
|
Child = text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Margin = new MarginPadding { Vertical = 20 },
|
||||||
|
Text = "see more"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IdleColour = colourProvider.Light1;
|
||||||
|
HoverColour = Color4.White;
|
||||||
|
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
overlay?.ShowFrontPage();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,8 +48,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
public bool FilteringActive { get; set; }
|
public bool FilteringActive { get; set; }
|
||||||
|
|
||||||
private OsuSpriteText text;
|
private OsuSpriteText text;
|
||||||
private Drawable pressAKey;
|
private FillFlowContainer cancelAndClearButtons;
|
||||||
|
|
||||||
private FillFlowContainer<KeyButton> buttons;
|
private FillFlowContainer<KeyButton> buttons;
|
||||||
|
|
||||||
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text);
|
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text);
|
||||||
@ -80,7 +79,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
Hollow = true,
|
Hollow = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
Children = new[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
@ -99,7 +98,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight
|
Origin = Anchor.TopRight
|
||||||
},
|
},
|
||||||
pressAKey = new FillFlowContainer
|
cancelAndClearButtons = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding(padding) { Top = height + padding * 2 },
|
Padding = new MarginPadding(padding) { Top = height + padding * 2 },
|
||||||
@ -187,7 +186,8 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
|
|
||||||
if (bindTarget.IsHovered)
|
if (bindTarget.IsHovered)
|
||||||
finalise();
|
finalise();
|
||||||
else
|
// prevent updating bind target before clear button's action
|
||||||
|
else if (!cancelAndClearButtons.Any(b => b.IsHovered))
|
||||||
updateBindTarget();
|
updateBindTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,8 +298,8 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
if (HasFocus)
|
if (HasFocus)
|
||||||
GetContainingInputManager().ChangeFocus(null);
|
GetContainingInputManager().ChangeFocus(null);
|
||||||
|
|
||||||
pressAKey.FadeOut(300, Easing.OutQuint);
|
cancelAndClearButtons.FadeOut(300, Easing.OutQuint);
|
||||||
pressAKey.BypassAutoSizeAxes |= Axes.Y;
|
cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFocus(FocusEvent e)
|
protected override void OnFocus(FocusEvent e)
|
||||||
@ -307,8 +307,8 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
AutoSizeDuration = 500;
|
AutoSizeDuration = 500;
|
||||||
AutoSizeEasing = Easing.OutQuint;
|
AutoSizeEasing = Easing.OutQuint;
|
||||||
|
|
||||||
pressAKey.FadeIn(300, Easing.OutQuint);
|
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
|
||||||
pressAKey.BypassAutoSizeAxes &= ~Axes.Y;
|
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
|
||||||
|
|
||||||
updateBindTarget();
|
updateBindTarget();
|
||||||
base.OnFocus(e);
|
base.OnFocus(e);
|
||||||
@ -320,6 +320,9 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
base.OnFocusLost(e);
|
base.OnFocusLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the bind target to the currently hovered key button or the first if clicked anywhere else.
|
||||||
|
/// </summary>
|
||||||
private void updateBindTarget()
|
private void updateBindTarget()
|
||||||
{
|
{
|
||||||
if (bindTarget != null) bindTarget.IsBinding = false;
|
if (bindTarget != null) bindTarget.IsBinding = false;
|
||||||
@ -354,7 +357,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class KeyButton : Container
|
public class KeyButton : Container
|
||||||
{
|
{
|
||||||
public readonly Framework.Input.Bindings.KeyBinding KeyBinding;
|
public readonly Framework.Input.Bindings.KeyBinding KeyBinding;
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -48,7 +46,7 @@ namespace osu.Game.Overlays.News
|
|||||||
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
NewsBackground bg;
|
NewsPostBackground bg;
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
background = new Box
|
background = new Box
|
||||||
@ -70,7 +68,7 @@ namespace osu.Game.Overlays.News
|
|||||||
CornerRadius = 6,
|
CornerRadius = 6,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new DelayedLoadWrapper(bg = new NewsBackground(post.FirstImage)
|
new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
@ -123,34 +121,6 @@ namespace osu.Game.Overlays.News
|
|||||||
main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold));
|
main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold));
|
||||||
}
|
}
|
||||||
|
|
||||||
[LongRunningLoad]
|
|
||||||
private class NewsBackground : Sprite
|
|
||||||
{
|
|
||||||
private readonly string sourceUrl;
|
|
||||||
|
|
||||||
public NewsBackground(string sourceUrl)
|
|
||||||
{
|
|
||||||
this.sourceUrl = sourceUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(LargeTextureStore store)
|
|
||||||
{
|
|
||||||
Texture = store.Get(createUrl(sourceUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
private string createUrl(string source)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(source))
|
|
||||||
return "Headers/news";
|
|
||||||
|
|
||||||
if (source.StartsWith('/'))
|
|
||||||
return "https://osu.ppy.sh" + source;
|
|
||||||
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DateContainer : CircularContainer, IHasCustomTooltip
|
private class DateContainer : CircularContainer, IHasCustomTooltip
|
||||||
{
|
{
|
||||||
public ITooltip GetCustomTooltip() => new DateTooltip();
|
public ITooltip GetCustomTooltip() => new DateTooltip();
|
||||||
|
37
osu.Game/Overlays/News/NewsPostBackground.cs
Normal file
37
osu.Game/Overlays/News/NewsPostBackground.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News
|
||||||
|
{
|
||||||
|
[LongRunningLoad]
|
||||||
|
public class NewsPostBackground : Sprite
|
||||||
|
{
|
||||||
|
private readonly string sourceUrl;
|
||||||
|
|
||||||
|
public NewsPostBackground(string sourceUrl)
|
||||||
|
{
|
||||||
|
this.sourceUrl = sourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(LargeTextureStore store)
|
||||||
|
{
|
||||||
|
Texture = store.Get(createUrl(sourceUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string createUrl(string source)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(source))
|
||||||
|
return "Headers/news";
|
||||||
|
|
||||||
|
if (source.StartsWith('/'))
|
||||||
|
return "https://osu.ppy.sh" + source;
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -126,7 +127,25 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
|
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Mod CreateCopy() => (Mod)MemberwiseClone();
|
public virtual Mod CreateCopy()
|
||||||
|
{
|
||||||
|
var copy = (Mod)Activator.CreateInstance(GetType());
|
||||||
|
|
||||||
|
// Copy bindable values across
|
||||||
|
foreach (var (_, prop) in this.GetSettingsSourceProperties())
|
||||||
|
{
|
||||||
|
var origBindable = prop.GetValue(this);
|
||||||
|
var copyBindable = prop.GetValue(copy);
|
||||||
|
|
||||||
|
// The bindables themselves are readonly, so the value must be transferred through the Bindable<T>.Value property.
|
||||||
|
var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable<object>.Value), BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
Debug.Assert(valueProperty != null);
|
||||||
|
|
||||||
|
valueProperty.SetValue(copyBindable, valueProperty.GetValue(origBindable));
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(IMod other) => GetType() == other?.GetType();
|
public bool Equals(IMod other) => GetType() == other?.GetType();
|
||||||
}
|
}
|
||||||
|
@ -191,6 +191,11 @@ namespace osu.Game.Rulesets
|
|||||||
if (loadedAssemblies.ContainsKey(assembly))
|
if (loadedAssemblies.ContainsKey(assembly))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799).
|
||||||
|
// as a failsafe, also compare by FullName.
|
||||||
|
if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName))
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
|
loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Screens.Multi.Play
|
|||||||
{
|
{
|
||||||
failed = true;
|
failed = true;
|
||||||
|
|
||||||
Logger.Error(e, "Failed to retrieve a score submission token.");
|
Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling).");
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
|
|
||||||
protected internal FillFlowContainer<DialogButton> InternalButtons;
|
protected ButtonContainer InternalButtons;
|
||||||
public IReadOnlyList<DialogButton> Buttons => InternalButtons;
|
public IReadOnlyList<DialogButton> Buttons => InternalButtons;
|
||||||
|
|
||||||
private FillFlowContainer retryCounterContainer;
|
private FillFlowContainer retryCounterContainer;
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
State.ValueChanged += s => selectionIndex = -1;
|
State.ValueChanged += s => InternalButtons.Deselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
InternalButtons = new FillFlowContainer<DialogButton>
|
InternalButtons = new ButtonContainer
|
||||||
{
|
{
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -186,40 +186,16 @@ namespace osu.Game.Screens.Play
|
|||||||
InternalButtons.Add(button);
|
InternalButtons.Add(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int selectionIndex = -1;
|
|
||||||
|
|
||||||
private void setSelected(int value)
|
|
||||||
{
|
|
||||||
if (selectionIndex == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Deselect the previously-selected button
|
|
||||||
if (selectionIndex != -1)
|
|
||||||
InternalButtons[selectionIndex].Selected.Value = false;
|
|
||||||
|
|
||||||
selectionIndex = value;
|
|
||||||
|
|
||||||
// Select the newly-selected button
|
|
||||||
if (selectionIndex != -1)
|
|
||||||
InternalButtons[selectionIndex].Selected.Value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case GlobalAction.SelectPrevious:
|
case GlobalAction.SelectPrevious:
|
||||||
if (selectionIndex == -1 || selectionIndex == 0)
|
InternalButtons.SelectPrevious();
|
||||||
setSelected(InternalButtons.Count - 1);
|
|
||||||
else
|
|
||||||
setSelected(selectionIndex - 1);
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.SelectNext:
|
case GlobalAction.SelectNext:
|
||||||
if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1)
|
InternalButtons.SelectNext();
|
||||||
setSelected(0);
|
|
||||||
else
|
|
||||||
setSelected(selectionIndex + 1);
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.Back:
|
case GlobalAction.Back:
|
||||||
@ -241,9 +217,9 @@ namespace osu.Game.Screens.Play
|
|||||||
private void buttonSelectionChanged(DialogButton button, bool isSelected)
|
private void buttonSelectionChanged(DialogButton button, bool isSelected)
|
||||||
{
|
{
|
||||||
if (!isSelected)
|
if (!isSelected)
|
||||||
setSelected(-1);
|
InternalButtons.Deselect();
|
||||||
else
|
else
|
||||||
setSelected(InternalButtons.IndexOf(button));
|
InternalButtons.Select(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRetryCount()
|
private void updateRetryCount()
|
||||||
@ -277,6 +253,46 @@ namespace osu.Game.Screens.Play
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected class ButtonContainer : FillFlowContainer<DialogButton>
|
||||||
|
{
|
||||||
|
private int selectedIndex = -1;
|
||||||
|
|
||||||
|
private void setSelected(int value)
|
||||||
|
{
|
||||||
|
if (selectedIndex == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Deselect the previously-selected button
|
||||||
|
if (selectedIndex != -1)
|
||||||
|
this[selectedIndex].Selected.Value = false;
|
||||||
|
|
||||||
|
selectedIndex = value;
|
||||||
|
|
||||||
|
// Select the newly-selected button
|
||||||
|
if (selectedIndex != -1)
|
||||||
|
this[selectedIndex].Selected.Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectNext()
|
||||||
|
{
|
||||||
|
if (selectedIndex == -1 || selectedIndex == Count - 1)
|
||||||
|
setSelected(0);
|
||||||
|
else
|
||||||
|
setSelected(selectedIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectPrevious()
|
||||||
|
{
|
||||||
|
if (selectedIndex == -1 || selectedIndex == 0)
|
||||||
|
setSelected(Count - 1);
|
||||||
|
else
|
||||||
|
setSelected(selectedIndex - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deselect() => setSelected(-1);
|
||||||
|
public void Select(DialogButton button) => setSelected(IndexOf(button));
|
||||||
|
}
|
||||||
|
|
||||||
private class Button : DialogButton
|
private class Button : DialogButton
|
||||||
{
|
{
|
||||||
// required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved)
|
// required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -25,8 +26,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected override Action BackAction => () => InternalButtons.Children.First().Click();
|
protected override Action BackAction => () => InternalButtons.Children.First().Click();
|
||||||
|
|
||||||
private const float minimum_volume = 0.0001f;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -37,10 +36,8 @@ namespace osu.Game.Screens.Play
|
|||||||
AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop"))
|
AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop"))
|
||||||
{
|
{
|
||||||
Looping = true,
|
Looping = true,
|
||||||
|
Volume = { Value = 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
// SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it
|
|
||||||
pauseLoop.VolumeTo(minimum_volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
@ -55,7 +52,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
base.PopOut();
|
base.PopOut();
|
||||||
|
|
||||||
pauseLoop.VolumeTo(minimum_volume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
|
pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private bool readyForPush =>
|
private bool readyForPush =>
|
||||||
// don't push unless the player is completely loaded
|
// don't push unless the player is completely loaded
|
||||||
player.LoadState == LoadState.Ready
|
player?.LoadState == LoadState.Ready
|
||||||
// don't push if the user is hovering one of the panes, unless they are idle.
|
// don't push if the user is hovering one of the panes, unless they are idle.
|
||||||
&& (IsHovered || idleTracker.IsIdle.Value)
|
&& (IsHovered || idleTracker.IsIdle.Value)
|
||||||
// don't push if the user is dragging a slider or otherwise.
|
// don't push if the user is dragging a slider or otherwise.
|
||||||
@ -153,8 +153,6 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
base.OnEntering(last);
|
base.OnEntering(last);
|
||||||
|
|
||||||
prepareNewPlayer();
|
|
||||||
|
|
||||||
content.ScaleTo(0.7f);
|
content.ScaleTo(0.7f);
|
||||||
Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
|
Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
|
||||||
|
|
||||||
@ -172,11 +170,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
contentIn();
|
contentIn();
|
||||||
|
|
||||||
MetadataInfo.Loading = true;
|
|
||||||
|
|
||||||
// we will only be resumed if the player has requested a re-run (see restartRequested).
|
|
||||||
prepareNewPlayer();
|
|
||||||
|
|
||||||
this.Delay(400).Schedule(pushWhenLoaded);
|
this.Delay(400).Schedule(pushWhenLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,6 +250,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void prepareNewPlayer()
|
private void prepareNewPlayer()
|
||||||
{
|
{
|
||||||
|
if (!this.IsCurrentScreen())
|
||||||
|
return;
|
||||||
|
|
||||||
var restartCount = player?.RestartCount + 1 ?? 0;
|
var restartCount = player?.RestartCount + 1 ?? 0;
|
||||||
|
|
||||||
player = createPlayer();
|
player = createPlayer();
|
||||||
@ -274,8 +270,10 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void contentIn()
|
private void contentIn()
|
||||||
{
|
{
|
||||||
content.ScaleTo(1, 650, Easing.OutQuint);
|
MetadataInfo.Loading = true;
|
||||||
|
|
||||||
content.FadeInFromZero(400);
|
content.FadeInFromZero(400);
|
||||||
|
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void contentOut()
|
private void contentOut()
|
||||||
|
@ -77,6 +77,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
item.RequiredMods.Clear();
|
item.RequiredMods.Clear();
|
||||||
item.RequiredMods.AddRange(Mods.Value);
|
item.RequiredMods.AddRange(Mods.Value);
|
||||||
|
|
||||||
|
Mods.Value = Mods.Value.Select(m => m.CreateCopy()).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,14 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Transforms;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class SkinnableSound : SkinReloadableDrawable
|
public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent
|
||||||
{
|
{
|
||||||
private readonly ISampleInfo[] hitSamples;
|
private readonly ISampleInfo[] hitSamples;
|
||||||
|
|
||||||
@ -29,6 +27,16 @@ namespace osu.Game.Skinning
|
|||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to play the underlying sample when aggregate volume is zero.
|
||||||
|
/// Note that this is checked at the point of calling <see cref="Play"/>; changing the volume post-play will not begin playback.
|
||||||
|
/// Defaults to false unless <see cref="Looping"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Can serve as an optimisation if it is known ahead-of-time that this behaviour is allowed in a given use case.
|
||||||
|
/// </remarks>
|
||||||
|
protected bool PlayWhenZeroVolume => Looping;
|
||||||
|
|
||||||
private readonly AudioContainer<DrawableSample> samplesContainer;
|
private readonly AudioContainer<DrawableSample> samplesContainer;
|
||||||
|
|
||||||
public SkinnableSound(ISampleInfo hitSamples)
|
public SkinnableSound(ISampleInfo hitSamples)
|
||||||
@ -88,7 +96,7 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
samplesContainer.ForEach(c =>
|
samplesContainer.ForEach(c =>
|
||||||
{
|
{
|
||||||
if (c.AggregateVolume.Value > 0)
|
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
|
||||||
c.Play();
|
c.Play();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -144,36 +152,17 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
||||||
|
|
||||||
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||||
|
=> samplesContainer.AddAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||||
|
=> samplesContainer.RemoveAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
|
public void RemoveAllAdjustments(AdjustableProperty type)
|
||||||
|
=> samplesContainer.RemoveAllAdjustments(type);
|
||||||
|
|
||||||
public bool IsPlaying => samplesContainer.Any(s => s.Playing);
|
public bool IsPlaying => samplesContainer.Any(s => s.Playing);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Smoothly adjusts <see cref="Volume"/> over time.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
|
||||||
public TransformSequence<AudioContainer<DrawableSample>> VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) =>
|
|
||||||
samplesContainer.VolumeTo(newVolume, duration, easing);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Smoothly adjusts <see cref="Balance"/> over time.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
|
||||||
public TransformSequence<AudioContainer<DrawableSample>> BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) =>
|
|
||||||
samplesContainer.BalanceTo(newBalance, duration, easing);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Smoothly adjusts <see cref="Frequency"/> over time.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
|
||||||
public TransformSequence<AudioContainer<DrawableSample>> FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) =>
|
|
||||||
samplesContainer.FrequencyTo(newFrequency, duration, easing);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Smoothly adjusts <see cref="Tempo"/> over time.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
|
||||||
public TransformSequence<AudioContainer<DrawableSample>> TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) =>
|
|
||||||
samplesContainer.TempoTo(newTempo, duration, easing);
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,8 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
var mods = new List<Mod>(SelectedMods.Value);
|
var mods = new List<Mod>(SelectedMods.Value);
|
||||||
|
|
||||||
if (currentTestData.Mod != null)
|
if (currentTestData.Mods != null)
|
||||||
mods.Add(currentTestData.Mod);
|
mods.AddRange(currentTestData.Mods);
|
||||||
if (currentTestData.Autoplay)
|
if (currentTestData.Autoplay)
|
||||||
mods.Add(ruleset.GetAutoplayMod());
|
mods.Add(ruleset.GetAutoplayMod());
|
||||||
|
|
||||||
@ -85,9 +85,18 @@ namespace osu.Game.Tests.Visual
|
|||||||
public Func<bool> PassCondition;
|
public Func<bool> PassCondition;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="Mod"/> this test case tests.
|
/// The <see cref="Mod"/>s this test case tests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Mod Mod;
|
public IReadOnlyList<Mod> Mods;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience property for setting <see cref="Mods"/> if only
|
||||||
|
/// a single mod is to be tested.
|
||||||
|
/// </summary>
|
||||||
|
public Mod Mod
|
||||||
|
{
|
||||||
|
set => Mods = new[] { value };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.806.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.814.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.731.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.812.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.5" />
|
<PackageReference Include="Sentry" Version="2.1.5" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
@ -70,8 +70,8 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.806.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.814.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.731.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.812.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
@ -80,7 +80,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.806.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.814.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user