mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:37:28 +08:00
Merge branch 'master' into display-performance-attributes
This commit is contained in:
commit
f29301cd1e
1
.gitignore
vendored
1
.gitignore
vendored
@ -339,3 +339,4 @@ inspectcode
|
|||||||
|
|
||||||
# Fody (pulled in by Realm) - schema file
|
# Fody (pulled in by Realm) - schema file
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
|
**/FodyWeavers.xml
|
||||||
|
147
Gemfile.lock
147
Gemfile.lock
@ -1,53 +1,75 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.3)
|
CFPropertyList (3.0.5)
|
||||||
addressable (2.7.0)
|
rexml
|
||||||
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.413.0)
|
aws-partitions (1.551.0)
|
||||||
aws-sdk-core (3.110.0)
|
aws-sdk-core (3.125.5)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.40.0)
|
aws-sdk-kms (1.53.0)
|
||||||
aws-sdk-core (~> 3, >= 3.109.0)
|
aws-sdk-core (~> 3, >= 3.125.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.87.0)
|
aws-sdk-s3 (1.111.3)
|
||||||
aws-sdk-core (~> 3, >= 3.109.0)
|
aws-sdk-core (~> 3, >= 3.125.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.2.2)
|
aws-sigv4 (1.4.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
claide (1.0.3)
|
claide (1.1.0)
|
||||||
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.20)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
digest-crc (0.6.4)
|
||||||
digest-crc (0.6.3)
|
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.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.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.2.1)
|
emoji_regex (3.2.3)
|
||||||
excon (0.78.1)
|
excon (0.90.0)
|
||||||
faraday (1.2.0)
|
faraday (1.9.3)
|
||||||
multipart-post (>= 1.2, < 3)
|
faraday-em_http (~> 1.0)
|
||||||
ruby2_keywords
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
faraday-cookie_jar (0.0.7)
|
faraday-cookie_jar (0.0.7)
|
||||||
faraday (>= 0.8.0)
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (1.0.0)
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.0.3)
|
||||||
|
multipart-post (>= 1.2, < 3)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.1)
|
fastimage (2.2.6)
|
||||||
fastlane (2.170.0)
|
fastlane (2.181.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)
|
||||||
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
@ -68,6 +90,7 @@ GEM
|
|||||||
jwt (>= 2.1.0, < 3)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
@ -94,65 +117,80 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
signet (~> 0.12)
|
signet (~> 0.12)
|
||||||
google-cloud-core (1.5.0)
|
google-apis-core (0.4.2)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
rexml
|
||||||
|
webrick
|
||||||
|
google-apis-iamcredentials_v1 (0.10.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.11.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-cloud-core (1.6.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.4.0)
|
google-cloud-env (1.5.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.2.0)
|
||||||
google-cloud-storage (1.29.2)
|
google-cloud-storage (1.36.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
google-cloud-core (~> 1.2)
|
google-apis-storage_v1 (~> 0.1)
|
||||||
googleauth (~> 0.9)
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.14.0)
|
googleauth (0.17.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)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.14)
|
signet (~> 0.15)
|
||||||
highline (1.7.10)
|
highline (1.7.10)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.4)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.5.0)
|
||||||
json (2.5.1)
|
json (2.6.1)
|
||||||
jwt (2.2.2)
|
jwt (2.3.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.0)
|
naturally (2.2.1)
|
||||||
nokogiri (1.10.10)
|
nokogiri (1.10.10)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
os (1.1.1)
|
os (1.1.4)
|
||||||
plist (3.5.0)
|
plist (3.6.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.6)
|
||||||
rake (13.0.3)
|
rake (13.0.6)
|
||||||
representable (3.0.4)
|
representable (3.1.1)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
|
rexml (3.2.5)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby2_keywords (0.0.2)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.16.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.8)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
slack-notifier (2.4.0)
|
||||||
souyuz (0.9.1)
|
souyuz (0.9.1)
|
||||||
fastlane (>= 1.103.0)
|
fastlane (>= 1.103.0)
|
||||||
highline (~> 1.7)
|
highline (~> 1.7)
|
||||||
@ -160,6 +198,7 @@ GEM
|
|||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
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)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.1)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
@ -167,18 +206,20 @@ GEM
|
|||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.8)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.8.0)
|
||||||
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.19.0)
|
xcodeproj (1.21.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.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
|
rexml (~> 3.2.4)
|
||||||
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.1)
|
||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(PippidonReplayFrame frame) => true;
|
protected override bool IsImportant(PippidonReplayFrame frame) => true;
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
inputs.Add(new ReplayState<EmptyScrollingAction>
|
inputs.Add(new ReplayState<EmptyScrollingAction>
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
inputs.Add(new ReplayState<PippidonAction>
|
inputs.Add(new ReplayState<PippidonAction>
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@ Install _fastlane_ using
|
|||||||
```
|
```
|
||||||
[sudo] gem install fastlane -NV
|
[sudo] gem install fastlane -NV
|
||||||
```
|
```
|
||||||
or alternatively using `brew cask install fastlane`
|
or alternatively using `brew install fastlane`
|
||||||
|
|
||||||
# Available Actions
|
# Available Actions
|
||||||
## Android
|
## Android
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.115.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.204.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.118.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.204.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
gameActivity.RunOnUiThread(() =>
|
gameActivity.RunOnUiThread(() =>
|
||||||
{
|
{
|
||||||
gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser;
|
gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -8,16 +9,18 @@ using System.Threading.Tasks;
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.Net;
|
using Android.Graphics;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Provider;
|
using Android.Provider;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using osu.Framework.Android;
|
using osu.Framework.Android;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using Debug = System.Diagnostics.Debug;
|
||||||
|
using Uri = Android.Net.Uri;
|
||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser)]
|
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
|
||||||
@ -41,6 +44,12 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
private static readonly string[] osu_url_schemes = { "osu", "osump" };
|
private static readonly string[] osu_url_schemes = { "osu", "osump" };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default screen orientation.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Adjusted on startup to match expected UX for the current device type (phone/tablet).</remarks>
|
||||||
|
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
|
||||||
|
|
||||||
private OsuGameAndroid game;
|
private OsuGameAndroid game;
|
||||||
|
|
||||||
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
||||||
@ -54,8 +63,20 @@ namespace osu.Android
|
|||||||
// reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
|
// reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
|
||||||
handleIntent(Intent);
|
handleIntent(Intent);
|
||||||
|
|
||||||
|
Debug.Assert(Window != null);
|
||||||
|
|
||||||
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
||||||
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
|
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
|
||||||
|
|
||||||
|
Debug.Assert(WindowManager?.DefaultDisplay != null);
|
||||||
|
Debug.Assert(Resources?.DisplayMetrics != null);
|
||||||
|
|
||||||
|
Point displaySize = new Point();
|
||||||
|
WindowManager.DefaultDisplay.GetSize(displaySize);
|
||||||
|
float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
|
||||||
|
bool isTablet = smallestWidthDp >= 600f;
|
||||||
|
|
||||||
|
RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent) => handleIntent(intent);
|
protected override void OnNewIntent(Intent intent) => handleIntent(intent);
|
||||||
@ -104,7 +125,7 @@ namespace osu.Android
|
|||||||
|
|
||||||
cursor.MoveToFirst();
|
cursor.MoveToFirst();
|
||||||
|
|
||||||
var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
|
int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
|
||||||
string filename = cursor.GetString(filenameColumn);
|
string filename = cursor.GetString(filenameColumn);
|
||||||
|
|
||||||
// SharpCompress requires archive streams to be seekable, which the stream opened by
|
// SharpCompress requires archive streams to be seekable, which the stream opened by
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (DesktopGameHost host = Host.GetSuitableHost(gameName, true))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true }))
|
||||||
{
|
{
|
||||||
host.ExceptionThrown += handleException;
|
host.ExceptionThrown += handleException;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Benchmarks
|
|||||||
public class BenchmarkRealmReads : BenchmarkTest
|
public class BenchmarkRealmReads : BenchmarkTest
|
||||||
{
|
{
|
||||||
private TemporaryNativeStorage storage;
|
private TemporaryNativeStorage storage;
|
||||||
private RealmContextFactory realmFactory;
|
private RealmAccess realm;
|
||||||
private UpdateThread updateThread;
|
private UpdateThread updateThread;
|
||||||
|
|
||||||
[Params(1, 100, 1000)]
|
[Params(1, 100, 1000)]
|
||||||
@ -27,9 +27,9 @@ namespace osu.Game.Benchmarks
|
|||||||
storage = new TemporaryNativeStorage("realm-benchmark");
|
storage = new TemporaryNativeStorage("realm-benchmark");
|
||||||
storage.DeleteDirectory(string.Empty);
|
storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
realmFactory = new RealmContextFactory(storage, "client");
|
realm = new RealmAccess(storage, "client");
|
||||||
|
|
||||||
realmFactory.Run(realm =>
|
realm.Run(r =>
|
||||||
{
|
{
|
||||||
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
|
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
|
||||||
});
|
});
|
||||||
@ -41,9 +41,9 @@ namespace osu.Game.Benchmarks
|
|||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void BenchmarkDirectPropertyRead()
|
public void BenchmarkDirectPropertyRead()
|
||||||
{
|
{
|
||||||
realmFactory.Run(realm =>
|
realm.Run(r =>
|
||||||
{
|
{
|
||||||
var beatmapSet = realm.All<BeatmapSetInfo>().First();
|
var beatmapSet = r.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
for (int i = 0; i < ReadsPerFetch; i++)
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
{
|
{
|
||||||
@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var beatmapSet = realmFactory.Context.All<BeatmapSetInfo>().First();
|
var beatmapSet = realm.Realm.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
for (int i = 0; i < ReadsPerFetch; i++)
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
{
|
{
|
||||||
@ -80,9 +80,9 @@ namespace osu.Game.Benchmarks
|
|||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void BenchmarkRealmLivePropertyRead()
|
public void BenchmarkRealmLivePropertyRead()
|
||||||
{
|
{
|
||||||
realmFactory.Run(realm =>
|
realm.Run(r =>
|
||||||
{
|
{
|
||||||
var beatmapSet = realm.All<BeatmapSetInfo>().First().ToLive(realmFactory);
|
var beatmapSet = r.All<BeatmapSetInfo>().First().ToLive(realm);
|
||||||
|
|
||||||
for (int i = 0; i < ReadsPerFetch; i++)
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
{
|
{
|
||||||
@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var beatmapSet = realmFactory.Context.All<BeatmapSetInfo>().First().ToLive(realmFactory);
|
var beatmapSet = realm.Realm.All<BeatmapSetInfo>().First().ToLive(realm);
|
||||||
|
|
||||||
for (int i = 0; i < ReadsPerFetch; i++)
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
{
|
{
|
||||||
@ -119,9 +119,9 @@ namespace osu.Game.Benchmarks
|
|||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void BenchmarkDetachedPropertyRead()
|
public void BenchmarkDetachedPropertyRead()
|
||||||
{
|
{
|
||||||
realmFactory.Run(realm =>
|
realm.Run(r =>
|
||||||
{
|
{
|
||||||
var beatmapSet = realm.All<BeatmapSetInfo>().First().Detach();
|
var beatmapSet = r.All<BeatmapSetInfo>().First().Detach();
|
||||||
|
|
||||||
for (int i = 0; i < ReadsPerFetch; i++)
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
{
|
{
|
||||||
@ -133,7 +133,7 @@ namespace osu.Game.Benchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
realmFactory?.Dispose();
|
realm?.Dispose();
|
||||||
storage?.Dispose();
|
storage?.Dispose();
|
||||||
updateThread?.Exit();
|
updateThread?.Exit();
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,16 @@
|
|||||||
<string>armv7</string>
|
<string>armv7</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -15,9 +16,26 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
|
|
||||||
private const float default_flashlight_size = 350;
|
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||||
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0.5f,
|
||||||
|
MaxValue = 1.5f,
|
||||||
|
Default = 1f,
|
||||||
|
Value = 1f,
|
||||||
|
Precision = 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield);
|
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
||||||
|
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
||||||
|
{
|
||||||
|
Default = true,
|
||||||
|
Value = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public override float DefaultFlashlightSize => 350;
|
||||||
|
|
||||||
|
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
|
||||||
|
|
||||||
private CatchPlayfield playfield;
|
private CatchPlayfield playfield;
|
||||||
|
|
||||||
@ -31,10 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
private readonly CatchPlayfield playfield;
|
private readonly CatchPlayfield playfield;
|
||||||
|
|
||||||
public CatchFlashlight(CatchPlayfield playfield)
|
public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield)
|
||||||
|
: base(modFlashlight)
|
||||||
{
|
{
|
||||||
this.playfield = playfield;
|
this.playfield = playfield;
|
||||||
FlashlightSize = new Vector2(0, getSizeFor(0));
|
FlashlightSize = new Vector2(0, GetSizeFor(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -44,19 +63,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
|
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getSizeFor(int combo)
|
|
||||||
{
|
|
||||||
if (combo > 200)
|
|
||||||
return default_flashlight_size * 0.8f;
|
|
||||||
else if (combo > 100)
|
|
||||||
return default_flashlight_size * 0.9f;
|
|
||||||
else
|
|
||||||
return default_flashlight_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "CircularFlashlight";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
case CatchSkinComponents.CatchComboCounter:
|
case CatchSkinComponents.CatchComboCounter:
|
||||||
if (providesComboCounter)
|
if (providesComboCounter)
|
||||||
return new LegacyCatchComboCounter(Skin);
|
return new LegacyCatchComboCounter();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
private readonly LegacyRollingCounter explosion;
|
private readonly LegacyRollingCounter explosion;
|
||||||
|
|
||||||
public LegacyCatchComboCounter(ISkin skin)
|
public LegacyCatchComboCounter()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
@ -24,11 +24,16 @@
|
|||||||
<string>armv7</string>
|
<string>armv7</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||||
|
103
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
Normal file
103
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneManiaModHoldOff : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMapHasNoHoldNotes()
|
||||||
|
{
|
||||||
|
var testBeatmap = createModdedBeatmap();
|
||||||
|
Assert.False(testBeatmap.HitObjects.OfType<HoldNote>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCorrectNoteValues()
|
||||||
|
{
|
||||||
|
var testBeatmap = createRawBeatmap();
|
||||||
|
var noteValues = new List<double>(testBeatmap.HitObjects.OfType<HoldNote>().Count());
|
||||||
|
|
||||||
|
foreach (HoldNote h in testBeatmap.HitObjects.OfType<HoldNote>())
|
||||||
|
{
|
||||||
|
noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap));
|
||||||
|
}
|
||||||
|
|
||||||
|
noteValues.Sort();
|
||||||
|
Assert.AreEqual(noteValues, new List<double> { 0.125, 0.250, 0.500, 1.000, 2.000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCorrectObjectCount()
|
||||||
|
{
|
||||||
|
// Ensure that the mod produces the expected number of objects when applied.
|
||||||
|
|
||||||
|
var rawBeatmap = createRawBeatmap();
|
||||||
|
var testBeatmap = createModdedBeatmap();
|
||||||
|
|
||||||
|
// Calculate expected number of objects
|
||||||
|
int expectedObjectCount = 0;
|
||||||
|
|
||||||
|
foreach (ManiaHitObject h in rawBeatmap.HitObjects)
|
||||||
|
{
|
||||||
|
// Both notes and hold notes account for at least one object
|
||||||
|
expectedObjectCount++;
|
||||||
|
|
||||||
|
if (h.GetType() == typeof(HoldNote))
|
||||||
|
{
|
||||||
|
double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap);
|
||||||
|
|
||||||
|
if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD)
|
||||||
|
{
|
||||||
|
// Should generate an end note if it's longer than the minimum note value
|
||||||
|
expectedObjectCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ManiaBeatmap createModdedBeatmap()
|
||||||
|
{
|
||||||
|
var beatmap = createRawBeatmap();
|
||||||
|
var holdOffMod = new ManiaModHoldOff();
|
||||||
|
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty());
|
||||||
|
|
||||||
|
holdOffMod.ApplyToBeatmap(beatmap);
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ManiaBeatmap createRawBeatmap()
|
||||||
|
{
|
||||||
|
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
|
||||||
|
beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60
|
||||||
|
|
||||||
|
// Add test hit objects
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 4000 });
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 4500 });
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new ColumnHitObjectArea(0, new HitObjectContainer())
|
Child = new ColumnHitObjectArea(new HitObjectContainer())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new ColumnHitObjectArea(1, new HitObjectContainer())
|
Child = new ColumnHitObjectArea(new HitObjectContainer())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public bool Matches(BeatmapInfo beatmapInfo)
|
public bool Matches(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
|
return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
|
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
|
[Cached] // Used for touch input, see ColumnTouchInputArea.
|
||||||
public class ManiaInputManager : RulesetInputManager<ManiaAction>
|
public class ManiaInputManager : RulesetInputManager<ManiaAction>
|
||||||
{
|
{
|
||||||
public ManiaInputManager(RulesetInfo ruleset, int variant)
|
public ManiaInputManager(RulesetInfo ruleset, int variant)
|
||||||
|
@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new ManiaModDifficultyAdjust(),
|
new ManiaModDifficultyAdjust(),
|
||||||
new ManiaModClassic(),
|
new ManiaModClassic(),
|
||||||
new ManiaModInvert(),
|
new ManiaModInvert(),
|
||||||
new ManiaModConstantSpeed()
|
new ManiaModConstantSpeed(),
|
||||||
|
new ManiaModHoldOff()
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -369,21 +370,21 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
Columns = new[]
|
Columns = new[]
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents)
|
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 250
|
Height = 250
|
||||||
}),
|
}, true),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new StatisticRow
|
new StatisticRow
|
||||||
{
|
{
|
||||||
Columns = new[]
|
Columns = new[]
|
||||||
{
|
{
|
||||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
{
|
{
|
||||||
new UnstableRate(score.HitEvents)
|
new UnstableRate(score.HitEvents)
|
||||||
}))
|
}), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -16,17 +17,35 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
||||||
|
|
||||||
private const float default_flashlight_size = 180;
|
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||||
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0.5f,
|
||||||
|
MaxValue = 3f,
|
||||||
|
Default = 1f,
|
||||||
|
Value = 1f,
|
||||||
|
Precision = 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
public override Flashlight CreateFlashlight() => new ManiaFlashlight();
|
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
||||||
|
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
||||||
|
{
|
||||||
|
Default = false,
|
||||||
|
Value = false
|
||||||
|
};
|
||||||
|
|
||||||
|
public override float DefaultFlashlightSize => 50;
|
||||||
|
|
||||||
|
protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this);
|
||||||
|
|
||||||
private class ManiaFlashlight : Flashlight
|
private class ManiaFlashlight : Flashlight
|
||||||
{
|
{
|
||||||
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
|
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
|
||||||
|
|
||||||
public ManiaFlashlight()
|
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
|
||||||
|
: base(modFlashlight)
|
||||||
{
|
{
|
||||||
FlashlightSize = new Vector2(0, default_flashlight_size);
|
FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
|
||||||
|
|
||||||
AddLayout(flashlightProperties);
|
AddLayout(flashlightProperties);
|
||||||
}
|
}
|
||||||
@ -46,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||||
{
|
{
|
||||||
|
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "RectangularFlashlight";
|
protected override string FragmentShader => "RectangularFlashlight";
|
||||||
|
72
osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
Normal file
72
osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion
|
||||||
|
{
|
||||||
|
public override string Name => "Hold Off";
|
||||||
|
|
||||||
|
public override string Acronym => "HO";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override string Description => @"Replaces all hold notes with normal notes.";
|
||||||
|
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
||||||
|
|
||||||
|
public const double END_NOTE_ALLOW_THRESHOLD = 0.5;
|
||||||
|
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
|
||||||
|
var newObjects = new List<ManiaHitObject>();
|
||||||
|
|
||||||
|
foreach (var h in beatmap.HitObjects.OfType<HoldNote>())
|
||||||
|
{
|
||||||
|
// Add a note for the beginning of the hold note
|
||||||
|
newObjects.Add(new Note
|
||||||
|
{
|
||||||
|
Column = h.Column,
|
||||||
|
StartTime = h.StartTime,
|
||||||
|
Samples = h.GetNodeSamples(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't add an end note if the duration is shorter than the threshold
|
||||||
|
double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc.
|
||||||
|
|
||||||
|
if (noteValue >= END_NOTE_ALLOW_THRESHOLD)
|
||||||
|
{
|
||||||
|
newObjects.Add(new Note
|
||||||
|
{
|
||||||
|
Column = h.Column,
|
||||||
|
StartTime = h.EndTime,
|
||||||
|
Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType<Note>().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap)
|
||||||
|
{
|
||||||
|
double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength;
|
||||||
|
return holdNote.Duration / beatLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
inputs.Add(new ReplayState<ManiaAction> { PressedActions = CurrentFrame?.Actions ?? new List<ManiaAction>() });
|
inputs.Add(new ReplayState<ManiaAction> { PressedActions = CurrentFrame?.Actions ?? new List<ManiaAction>() });
|
||||||
}
|
}
|
||||||
|
@ -62,13 +62,14 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
|
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
|
||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
background,
|
background,
|
||||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
|
new ColumnTouchInputArea(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
@ -139,5 +140,50 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||||
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
||||||
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
||||||
|
|
||||||
|
public class ColumnTouchInputArea : Drawable
|
||||||
|
{
|
||||||
|
private readonly Column column;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private ManiaInputManager maniaInputManager { get; set; }
|
||||||
|
|
||||||
|
private KeyBindingContainer<ManiaAction> keyBindingContainer;
|
||||||
|
|
||||||
|
public ColumnTouchInputArea(Column column)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
keyBindingContainer = maniaInputManager?.KeyBindingContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
keyBindingContainer?.TriggerPressed(column.Action.Value);
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
keyBindingContainer?.TriggerReleased(column.Action.Value);
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnTouchDown(TouchDownEvent e)
|
||||||
|
{
|
||||||
|
keyBindingContainer?.TriggerPressed(column.Action.Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchUp(TouchUpEvent e)
|
||||||
|
{
|
||||||
|
keyBindingContainer?.TriggerReleased(column.Action.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
|
|
||||||
private readonly Drawable hitTarget;
|
private readonly Drawable hitTarget;
|
||||||
|
|
||||||
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
|
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
|
||||||
: base(hitObjectContainer)
|
: base(hitObjectContainer)
|
||||||
{
|
{
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
AlwaysPresent = true
|
AlwaysPresent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,11 +24,16 @@
|
|||||||
<string>armv7</string>
|
<string>armv7</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneSliderVelocityAdjust : OsuGameTestScene
|
||||||
|
{
|
||||||
|
private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor;
|
||||||
|
|
||||||
|
private EditorBeatmap editorBeatmap => editor.ChildrenOfType<EditorBeatmap>().FirstOrDefault();
|
||||||
|
|
||||||
|
private EditorClock editorClock => editor.ChildrenOfType<EditorClock>().FirstOrDefault();
|
||||||
|
|
||||||
|
private Slider slider => editorBeatmap.HitObjects.OfType<Slider>().FirstOrDefault();
|
||||||
|
|
||||||
|
private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType<TimelineHitObjectBlueprint>().FirstOrDefault();
|
||||||
|
|
||||||
|
private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType<DifficultyPointPiece>().First();
|
||||||
|
|
||||||
|
private IndeterminateSliderWithTextBoxInput<double> velocityTextBox => Game.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().First().ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().First();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
private bool editorComponentsReady => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
||||||
|
&& editor.ChildrenOfType<TimelineArea>().FirstOrDefault()?.IsLoaded == true
|
||||||
|
&& editor?.ChildrenOfType<Playfield>().FirstOrDefault()?.IsLoaded == true;
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
|
||||||
|
{
|
||||||
|
double? velocity = null;
|
||||||
|
|
||||||
|
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
|
AddUntilStep("wait for editor load", () => editorComponentsReady);
|
||||||
|
|
||||||
|
AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||||
|
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
|
||||||
|
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().First().ScreenSpaceDrawQuad.Centre));
|
||||||
|
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
|
||||||
|
AddStep("end placement", () => InputManager.Click(MouseButton.Right));
|
||||||
|
|
||||||
|
AddStep("exit placement mode", () => InputManager.Key(Key.Number1));
|
||||||
|
|
||||||
|
AddAssert("slider placed", () => slider != null);
|
||||||
|
|
||||||
|
AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
|
||||||
|
AddAssert("ensure one slider placed", () => slider != null);
|
||||||
|
|
||||||
|
AddStep("store velocity", () => velocity = slider.Velocity);
|
||||||
|
|
||||||
|
if (adjustVelocity)
|
||||||
|
{
|
||||||
|
AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick());
|
||||||
|
AddStep("change velocity", () => velocityTextBox.Current.Value = 2);
|
||||||
|
|
||||||
|
AddAssert("velocity adjusted", () =>
|
||||||
|
{
|
||||||
|
Debug.Assert(velocity != null);
|
||||||
|
return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("store velocity", () => velocity = slider.Velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddStep("save", () => InputManager.Keys(PlatformAction.Save));
|
||||||
|
AddStep("exit", () => InputManager.Key(Key.Escape));
|
||||||
|
|
||||||
|
AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
|
AddUntilStep("wait for editor load", () => editorComponentsReady);
|
||||||
|
|
||||||
|
AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
|
||||||
|
AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs
Normal file
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModAimAssist : OsuModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(0.1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1)]
|
||||||
|
public void TestAimAssist(float strength)
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModAimAssist
|
||||||
|
{
|
||||||
|
AssistStrength = { Value = strength },
|
||||||
|
},
|
||||||
|
PassCondition = () => true,
|
||||||
|
Autoplay = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
Normal file
154
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModAlternate : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestInputAtIntro() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModAlternate(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(200)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternating() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModAlternate(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(200, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1500,
|
||||||
|
Position = new Vector2(300, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 2000,
|
||||||
|
Position = new Vector2(400, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(1001, new Vector2(200, 100)),
|
||||||
|
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1501, new Vector2(300, 100)),
|
||||||
|
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(2001, new Vector2(400, 100)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputSingular() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModAlternate(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(200, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModAlternate(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
Breaks = new List<BreakPeriod>
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 2250),
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 2500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2501, new Vector2(100)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
|
|
||||||
private bool isBreak() => Player.IsBreakTime.Value;
|
private bool isBreak() => Player.IsBreakTime.Value;
|
||||||
|
|
||||||
private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha);
|
private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
|
|
||||||
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
|
83
osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs
Normal file
83
osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => "Aim Assist";
|
||||||
|
public override string Acronym => "AA";
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.MousePointer;
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override string Description => "No need to chase the circle – the circle chases you!";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) };
|
||||||
|
|
||||||
|
private IFrameStableClock gameplayClock;
|
||||||
|
|
||||||
|
[SettingSource("Assist strength", "How much this mod will assist you.", 0)]
|
||||||
|
public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f)
|
||||||
|
{
|
||||||
|
Precision = 0.05f,
|
||||||
|
MinValue = 0.05f,
|
||||||
|
MaxValue = 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
|
||||||
|
// Hide judgment displays and follow points as they won't make any sense.
|
||||||
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
|
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
||||||
|
|
||||||
|
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||||
|
{
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableHitCircle circle:
|
||||||
|
easeTo(circle, cursorPos);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSlider slider:
|
||||||
|
|
||||||
|
if (!slider.HeadCircle.Result.HasResult)
|
||||||
|
easeTo(slider, cursorPos);
|
||||||
|
else
|
||||||
|
easeTo(slider, cursorPos - slider.Ball.DrawPosition);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void easeTo(DrawableHitObject hitObject, Vector2 destination)
|
||||||
|
{
|
||||||
|
double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value);
|
||||||
|
|
||||||
|
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
|
||||||
|
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
|
||||||
|
|
||||||
|
hitObject.Position = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
106
osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
Normal file
106
osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||||
|
{
|
||||||
|
public override string Name => @"Alternate";
|
||||||
|
public override string Acronym => @"AL";
|
||||||
|
public override string Description => @"Don't use the same key twice in a row!";
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) };
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||||
|
|
||||||
|
private double firstObjectValidJudgementTime;
|
||||||
|
private IBindable<bool> isBreakTime;
|
||||||
|
private const double flash_duration = 1000;
|
||||||
|
private OsuAction? lastActionPressed;
|
||||||
|
private DrawableRuleset<OsuHitObject> ruleset;
|
||||||
|
|
||||||
|
private IFrameStableClock gameplayClock;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
ruleset = drawableRuleset;
|
||||||
|
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||||
|
|
||||||
|
var firstHitObject = ruleset.Objects.FirstOrDefault();
|
||||||
|
firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0);
|
||||||
|
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToPlayer(Player player)
|
||||||
|
{
|
||||||
|
isBreakTime = player.IsBreakTime.GetBoundCopy();
|
||||||
|
isBreakTime.ValueChanged += e =>
|
||||||
|
{
|
||||||
|
if (e.NewValue)
|
||||||
|
lastActionPressed = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkCorrectAction(OsuAction action)
|
||||||
|
{
|
||||||
|
if (isBreakTime.Value)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (gameplayClock.CurrentTime < firstObjectValidJudgementTime)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case OsuAction.LeftButton:
|
||||||
|
case OsuAction.RightButton:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Any action which is not left or right button should be ignored.
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastActionPressed != action)
|
||||||
|
{
|
||||||
|
// User alternated correctly.
|
||||||
|
lastActionPressed = action;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private readonly OsuModAlternate mod;
|
||||||
|
|
||||||
|
public InputInterceptor(OsuModAlternate mod)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||||
|
=> !mod.checkCorrectAction(e.Action);
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) };
|
||||||
|
|
||||||
public bool PerformFail() => false;
|
public bool PerformFail() => false;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModAutoplay : ModAutoplay
|
public class OsuModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
||||||
|
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModCinema : ModCinema<OsuHitObject>
|
public class OsuModCinema : ModCinema<OsuHitObject>
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
||||||
|
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -21,27 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
|
|
||||||
private const float default_flashlight_size = 180;
|
|
||||||
|
|
||||||
private const double default_follow_delay = 120;
|
private const double default_follow_delay = 120;
|
||||||
|
|
||||||
private OsuFlashlight flashlight;
|
|
||||||
|
|
||||||
public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
|
|
||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
|
||||||
{
|
|
||||||
if (drawable is DrawableSlider s)
|
|
||||||
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
|
||||||
{
|
|
||||||
base.ApplyToDrawableRuleset(drawableRuleset);
|
|
||||||
|
|
||||||
flashlight.FollowDelay = FollowDelay.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
|
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
|
||||||
public BindableNumber<double> FollowDelay { get; } = new BindableDouble(default_follow_delay)
|
public BindableNumber<double> FollowDelay { get; } = new BindableDouble(default_follow_delay)
|
||||||
{
|
{
|
||||||
@ -50,13 +30,45 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
Precision = default_follow_delay,
|
Precision = default_follow_delay,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||||
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0.5f,
|
||||||
|
MaxValue = 2f,
|
||||||
|
Default = 1f,
|
||||||
|
Value = 1f,
|
||||||
|
Precision = 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
|
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
||||||
|
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
||||||
|
{
|
||||||
|
Default = true,
|
||||||
|
Value = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public override float DefaultFlashlightSize => 180;
|
||||||
|
|
||||||
|
private OsuFlashlight flashlight;
|
||||||
|
|
||||||
|
protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
if (drawable is DrawableSlider s)
|
||||||
|
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
|
||||||
|
}
|
||||||
|
|
||||||
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
|
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
|
||||||
{
|
{
|
||||||
public double FollowDelay { private get; set; }
|
private readonly double followDelay;
|
||||||
|
|
||||||
public OsuFlashlight()
|
public OsuFlashlight(OsuModFlashlight modFlashlight)
|
||||||
|
: base(modFlashlight)
|
||||||
{
|
{
|
||||||
FlashlightSize = new Vector2(0, getSizeFor(0));
|
followDelay = modFlashlight.FollowDelay.Value;
|
||||||
|
|
||||||
|
FlashlightSize = new Vector2(0, GetSizeFor(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
|
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
|
||||||
@ -71,24 +83,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var destination = e.MousePosition;
|
var destination = e.MousePosition;
|
||||||
|
|
||||||
FlashlightPosition = Interpolation.ValueAt(
|
FlashlightPosition = Interpolation.ValueAt(
|
||||||
Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out);
|
Math.Min(Math.Abs(Clock.ElapsedFrameTime), followDelay), position, destination, 0, followDelay, Easing.Out);
|
||||||
|
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getSizeFor(int combo)
|
|
||||||
{
|
|
||||||
if (combo > 200)
|
|
||||||
return default_flashlight_size * 0.8f;
|
|
||||||
else if (combo > 100)
|
|
||||||
return default_flashlight_size * 0.9f;
|
|
||||||
else
|
|
||||||
return default_flashlight_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||||
{
|
{
|
||||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string FragmentShader => "CircularFlashlight";
|
protected override string FragmentShader => "CircularFlashlight";
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Everything rotates. EVERYTHING.";
|
public override string Description => "Everything rotates. EVERYTHING.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) };
|
||||||
|
|
||||||
private float theta;
|
private float theta;
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "They just won't stay still...";
|
public override string Description => "They just won't stay still...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) };
|
||||||
|
|
||||||
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
||||||
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
||||||
|
@ -169,6 +169,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModClassic(),
|
new OsuModClassic(),
|
||||||
new OsuModRandom(),
|
new OsuModRandom(),
|
||||||
new OsuModMirror(),
|
new OsuModMirror(),
|
||||||
|
new OsuModAlternate(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -193,6 +194,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
new OsuModNoScope(),
|
new OsuModNoScope(),
|
||||||
|
new OsuModAimAssist(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
@ -277,33 +279,32 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
Columns = new[]
|
Columns = new[]
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution",
|
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
new HitEventTimingDistributionGraph(timedHitEvents)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 250
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
|
||||||
Columns = new[]
|
|
||||||
{
|
|
||||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 250
|
Height = 250
|
||||||
}),
|
}, true),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new StatisticRow
|
new StatisticRow
|
||||||
{
|
{
|
||||||
Columns = new[]
|
Columns = new[]
|
||||||
{
|
{
|
||||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250
|
||||||
|
}, true),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
{
|
{
|
||||||
new UnstableRate(timedHitEvents)
|
new UnstableRate(timedHitEvents)
|
||||||
}))
|
}), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
private readonly ProxyContainer approachCircles;
|
private readonly ProxyContainer approachCircles;
|
||||||
private readonly ProxyContainer spinnerProxies;
|
private readonly ProxyContainer spinnerProxies;
|
||||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||||
private readonly FollowPointRenderer followPoints;
|
|
||||||
|
public FollowPointRenderer FollowPoints { get; }
|
||||||
|
|
||||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||||
followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||||
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
|
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
protected override void OnHitObjectAdded(HitObject hitObject)
|
protected override void OnHitObjectAdded(HitObject hitObject)
|
||||||
{
|
{
|
||||||
base.OnHitObjectAdded(hitObject);
|
base.OnHitObjectAdded(hitObject);
|
||||||
followPoints.AddFollowPoints((OsuHitObject)hitObject);
|
FollowPoints.AddFollowPoints((OsuHitObject)hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHitObjectRemoved(HitObject hitObject)
|
protected override void OnHitObjectRemoved(HitObject hitObject)
|
||||||
{
|
{
|
||||||
base.OnHitObjectRemoved(hitObject);
|
base.OnHitObjectRemoved(hitObject);
|
||||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
@ -24,11 +24,16 @@
|
|||||||
<string>armv7</string>
|
<string>armv7</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||||
|
@ -1,91 +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.Input;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
|
||||||
using osu.Game.Screens.Edit;
|
|
||||||
using osu.Game.Screens.Edit.Setup;
|
|
||||||
using osu.Game.Screens.Menu;
|
|
||||||
using osu.Game.Screens.Select;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
|
||||||
{
|
|
||||||
public class TestSceneEditorSaving : OsuGameTestScene
|
|
||||||
{
|
|
||||||
private Screens.Edit.Editor editor => Game.ChildrenOfType<Screens.Edit.Editor>().FirstOrDefault();
|
|
||||||
|
|
||||||
private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
|
|
||||||
/// Emphasis is placed on <see cref="BeatmapDifficulty.SliderMultiplier"/>, since taiko has special handling for it to keep compatibility with stable.
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void TestNewBeatmapSaveThenLoad()
|
|
||||||
{
|
|
||||||
AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
|
|
||||||
AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
|
|
||||||
|
|
||||||
PushAndConfirm(() => new EditorLoader());
|
|
||||||
|
|
||||||
AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
|
|
||||||
|
|
||||||
AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
|
||||||
|
|
||||||
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
|
||||||
|
|
||||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
|
||||||
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
|
||||||
|
|
||||||
AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2);
|
|
||||||
AddStep("Set artist and title", () =>
|
|
||||||
{
|
|
||||||
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
|
||||||
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
|
||||||
});
|
|
||||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
|
||||||
|
|
||||||
checkMutations();
|
|
||||||
|
|
||||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
|
||||||
|
|
||||||
checkMutations();
|
|
||||||
|
|
||||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
|
||||||
|
|
||||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
|
||||||
|
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
|
||||||
|
|
||||||
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
|
||||||
AddStep("Open options", () => InputManager.Key(Key.F3));
|
|
||||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
|
||||||
|
|
||||||
AddUntilStep("Wait for editor load", () => editor != null);
|
|
||||||
|
|
||||||
checkMutations();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkMutations()
|
|
||||||
{
|
|
||||||
AddAssert("Beatmap has correct slider multiplier", () =>
|
|
||||||
{
|
|
||||||
// we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
|
|
||||||
// therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
|
|
||||||
var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
|
|
||||||
taikoDifficulty.CopyFrom(editorBeatmap.Difficulty);
|
|
||||||
return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
|
|
||||||
});
|
|
||||||
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
|
||||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneTaikoEditorSaving : EditorSavingTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTaikoSliderMultiplier()
|
||||||
|
{
|
||||||
|
AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2);
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
|
||||||
|
AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier);
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
|
AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier);
|
||||||
|
|
||||||
|
bool assertTaikoSliderMulitplier()
|
||||||
|
{
|
||||||
|
// we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
|
||||||
|
// therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
|
||||||
|
var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
|
||||||
|
taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty);
|
||||||
|
return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
||||||
|
|
||||||
if (original.BeatmapInfo.RulesetID == 3)
|
if (original.BeatmapInfo.Ruleset.OnlineID == 3)
|
||||||
{
|
{
|
||||||
// Post processing step to transform mania hit objects with the same start time into strong hits
|
// Post processing step to transform mania hit objects with the same start time into strong hits
|
||||||
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
|
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
@ -16,9 +17,26 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
|
|
||||||
private const float default_flashlight_size = 250;
|
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||||
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0.5f,
|
||||||
|
MaxValue = 1.5f,
|
||||||
|
Default = 1f,
|
||||||
|
Value = 1f,
|
||||||
|
Precision = 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield);
|
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
||||||
|
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
||||||
|
{
|
||||||
|
Default = true,
|
||||||
|
Value = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public override float DefaultFlashlightSize => 250;
|
||||||
|
|
||||||
|
protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
|
||||||
|
|
||||||
private TaikoPlayfield playfield;
|
private TaikoPlayfield playfield;
|
||||||
|
|
||||||
@ -33,7 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
|
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
|
||||||
private readonly TaikoPlayfield taikoPlayfield;
|
private readonly TaikoPlayfield taikoPlayfield;
|
||||||
|
|
||||||
public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
|
public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
|
||||||
|
: base(modFlashlight)
|
||||||
{
|
{
|
||||||
this.taikoPlayfield = taikoPlayfield;
|
this.taikoPlayfield = taikoPlayfield;
|
||||||
FlashlightSize = getSizeFor(0);
|
FlashlightSize = getSizeFor(0);
|
||||||
@ -43,15 +62,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
|
|
||||||
private Vector2 getSizeFor(int combo)
|
private Vector2 getSizeFor(int combo)
|
||||||
{
|
{
|
||||||
float size = default_flashlight_size;
|
|
||||||
|
|
||||||
if (combo > 200)
|
|
||||||
size *= 0.8f;
|
|
||||||
else if (combo > 100)
|
|
||||||
size *= 0.9f;
|
|
||||||
|
|
||||||
// Preserve flashlight size through the playfield's aspect adjustment.
|
// Preserve flashlight size through the playfield's aspect adjustment.
|
||||||
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
protected override void CollectReplayInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
inputs.Add(new ReplayState<TaikoAction> { PressedActions = CurrentFrame?.Actions ?? new List<TaikoAction>() });
|
inputs.Add(new ReplayState<TaikoAction> { PressedActions = CurrentFrame?.Actions ?? new List<TaikoAction>() });
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
if (!effectPoint.KiaiMode)
|
if (!effectPoint.KiaiMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (beatIndex % (int)timingPoint.TimeSignature != 0)
|
if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
double duration = timingPoint.BeatLength * 2;
|
double duration = timingPoint.BeatLength * 2;
|
||||||
|
@ -213,21 +213,21 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
{
|
{
|
||||||
Columns = new[]
|
Columns = new[]
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
|
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 250
|
Height = 250
|
||||||
}),
|
}, true),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new StatisticRow
|
new StatisticRow
|
||||||
{
|
{
|
||||||
Columns = new[]
|
Columns = new[]
|
||||||
{
|
{
|
||||||
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
{
|
{
|
||||||
new UnstableRate(timedHitEvents)
|
new UnstableRate(timedHitEvents)
|
||||||
}))
|
}), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,11 +24,16 @@
|
|||||||
<string>armv7</string>
|
<string>armv7</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||||
Assert.AreEqual(164471, metadata.PreviewTime);
|
Assert.AreEqual(164471, metadata.PreviewTime);
|
||||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||||
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
|
||||||
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||||
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||||
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||||
@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var timingPoint = controlPoints.TimingPointAt(0);
|
var timingPoint = controlPoints.TimingPointAt(0);
|
||||||
Assert.AreEqual(956, timingPoint.Time);
|
Assert.AreEqual(956, timingPoint.Time);
|
||||||
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
||||||
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
|
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
|
||||||
timingPoint = controlPoints.TimingPointAt(48428);
|
timingPoint = controlPoints.TimingPointAt(48428);
|
||||||
Assert.AreEqual(956, timingPoint.Time);
|
Assert.AreEqual(956, timingPoint.Time);
|
||||||
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
||||||
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
|
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
|
||||||
timingPoint = controlPoints.TimingPointAt(119637);
|
timingPoint = controlPoints.TimingPointAt(119637);
|
||||||
Assert.AreEqual(119637, timingPoint.Time);
|
Assert.AreEqual(119637, timingPoint.Time);
|
||||||
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
||||||
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
|
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
|
||||||
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
||||||
Assert.AreEqual(0, difficultyPoint.Time);
|
Assert.AreEqual(0, difficultyPoint.Time);
|
||||||
@ -794,5 +794,74 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.That(path.Distance, Is.EqualTo(1));
|
Assert.That(path.Distance, Is.EqualTo(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLegacyDefaultsPreserved()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
using (var stream = new LineBufferedReader(memoryStream))
|
||||||
|
{
|
||||||
|
var decoded = decoder.Decode(stream);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0));
|
||||||
|
Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f));
|
||||||
|
Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False);
|
||||||
|
Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False);
|
||||||
|
Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False);
|
||||||
|
Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False);
|
||||||
|
Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False);
|
||||||
|
Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal));
|
||||||
|
Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0));
|
||||||
|
Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
|
||||||
|
Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUndefinedApproachRateInheritsOverallDifficulty()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var decoded = decoder.Decode(stream);
|
||||||
|
Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1));
|
||||||
|
Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApproachRateDefinedBeforeOverallDifficulty()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var decoded = decoder.Decode(stream);
|
||||||
|
Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
|
||||||
|
Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApproachRateDefinedAfterOverallDifficulty()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var decoded = decoder.Decode(stream);
|
||||||
|
Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
|
||||||
|
Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
private IBeatmap convert(IBeatmap beatmap)
|
private IBeatmap convert(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
switch (beatmap.BeatmapInfo.RulesetID)
|
switch (beatmap.BeatmapInfo.Ruleset.OnlineID)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
|
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||||
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
|
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
|
||||||
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
|
||||||
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
|
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
|
||||||
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
|
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
|
||||||
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||||
|
@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
|
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
|
||||||
{
|
{
|
||||||
var realmContextFactory = osu.Dependencies.Get<RealmContextFactory>();
|
var realm = osu.Dependencies.Get<RealmAccess>();
|
||||||
|
|
||||||
realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout));
|
realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout));
|
||||||
|
|
||||||
// TODO: add back some extra checks outside of the realm ones?
|
// TODO: add back some extra checks outside of the realm ones?
|
||||||
// var set = queryBeatmapSets().First();
|
// var set = queryBeatmapSets().First();
|
||||||
|
@ -155,7 +155,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
|
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
|
||||||
using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
|
using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -38,12 +38,12 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDetachBeatmapSet()
|
public void TestDetachBeatmapSet()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapModelManager(realmFactory, storage))
|
using (var importer = new BeatmapModelManager(realm, storage))
|
||||||
using (new RulesetStore(realmFactory, storage))
|
using (new RulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
ILive<BeatmapSetInfo>? beatmapSet;
|
Live<BeatmapSetInfo>? beatmapSet;
|
||||||
|
|
||||||
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
||||||
beatmapSet = await importer.Import(reader);
|
beatmapSet = await importer.Import(reader);
|
||||||
@ -82,12 +82,12 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestUpdateDetachedBeatmapSet()
|
public void TestUpdateDetachedBeatmapSet()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapModelManager(realmFactory, storage))
|
using (var importer = new BeatmapModelManager(realm, storage))
|
||||||
using (new RulesetStore(realmFactory, storage))
|
using (new RulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
ILive<BeatmapSetInfo>? beatmapSet;
|
Live<BeatmapSetInfo>? beatmapSet;
|
||||||
|
|
||||||
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
||||||
beatmapSet = await importer.Import(reader);
|
beatmapSet = await importer.Import(reader);
|
||||||
@ -139,53 +139,53 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportBeatmapThenCleanup()
|
public void TestImportBeatmapThenCleanup()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapModelManager(realmFactory, storage))
|
using (var importer = new BeatmapModelManager(realm, storage))
|
||||||
using (new RulesetStore(realmFactory, storage))
|
using (new RulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
ILive<BeatmapSetInfo>? imported;
|
Live<BeatmapSetInfo>? imported;
|
||||||
|
|
||||||
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
||||||
imported = await importer.Import(reader);
|
imported = await importer.Import(reader);
|
||||||
|
|
||||||
Assert.AreEqual(1, realmFactory.Context.All<BeatmapSetInfo>().Count());
|
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count());
|
||||||
|
|
||||||
Assert.NotNull(imported);
|
Assert.NotNull(imported);
|
||||||
Debug.Assert(imported != null);
|
Debug.Assert(imported != null);
|
||||||
|
|
||||||
imported.PerformWrite(s => s.DeletePending = true);
|
imported.PerformWrite(s => s.DeletePending = true);
|
||||||
|
|
||||||
Assert.AreEqual(1, realmFactory.Context.All<BeatmapSetInfo>().Count(s => s.DeletePending));
|
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count(s => s.DeletePending));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Logger.Log("Running with no work to purge pending deletions");
|
Logger.Log("Running with no work to purge pending deletions");
|
||||||
|
|
||||||
RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All<BeatmapSetInfo>().Count()); });
|
RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All<BeatmapSetInfo>().Count()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportWhenClosed()
|
public void TestImportWhenClosed()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
await LoadOszIntoStore(importer, realmFactory.Context);
|
await LoadOszIntoStore(importer, realm.Realm);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAccessFileAfterImport()
|
public void TestAccessFileAfterImport()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
var beatmap = imported.Beatmaps.First();
|
var beatmap = imported.Beatmaps.First();
|
||||||
var file = beatmap.File;
|
var file = beatmap.File;
|
||||||
@ -198,33 +198,33 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDelete()
|
public void TestImportThenDelete()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
deleteBeatmapSet(imported, realmFactory.Context);
|
deleteBeatmapSet(imported, realm.Realm);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDeleteFromStream()
|
public void TestImportThenDeleteFromStream()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? tempPath = TestResources.GetTestBeatmapForImport();
|
string? tempPath = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
ILive<BeatmapSetInfo>? importedSet;
|
Live<BeatmapSetInfo>? importedSet;
|
||||||
|
|
||||||
using (var stream = File.OpenRead(tempPath))
|
using (var stream = File.OpenRead(tempPath))
|
||||||
{
|
{
|
||||||
importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
|
importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.NotNull(importedSet);
|
Assert.NotNull(importedSet);
|
||||||
@ -233,39 +233,39 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
|
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
|
||||||
File.Delete(tempPath);
|
File.Delete(tempPath);
|
||||||
|
|
||||||
var imported = realmFactory.Context.All<BeatmapSetInfo>().First(beatmapSet => beatmapSet.ID == importedSet.ID);
|
var imported = realm.Realm.All<BeatmapSetInfo>().First(beatmapSet => beatmapSet.ID == importedSet.ID);
|
||||||
|
|
||||||
deleteBeatmapSet(imported, realmFactory.Context);
|
deleteBeatmapSet(imported, realm.Realm);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImport()
|
public void TestImportThenImport()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
|
||||||
checkBeatmapSetCount(realmFactory.Context, 1);
|
checkBeatmapSetCount(realm.Realm, 1);
|
||||||
checkSingleReferencedFileCount(realmFactory.Context, 18);
|
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImportWithReZip()
|
public void TestImportThenImportWithReZip()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
string hashBefore = hashFile(temp);
|
string hashBefore = hashFile(temp);
|
||||||
|
|
||||||
@ -292,7 +292,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
Assert.NotNull(importedSecondTime);
|
Assert.NotNull(importedSecondTime);
|
||||||
Debug.Assert(importedSecondTime != null);
|
Debug.Assert(importedSecondTime != null);
|
||||||
@ -311,10 +311,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImportWithChangedHashedFile()
|
public void TestImportThenImportWithChangedHashedFile()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -323,9 +323,9 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First());
|
await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
|
||||||
|
|
||||||
using (var zip = ZipArchive.Open(temp))
|
using (var zip = ZipArchive.Open(temp))
|
||||||
zip.WriteToDirectory(extractedFolder);
|
zip.WriteToDirectory(extractedFolder);
|
||||||
@ -343,7 +343,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is not the original.
|
// check the newly "imported" beatmap is not the original.
|
||||||
Assert.NotNull(importedSecondTime);
|
Assert.NotNull(importedSecondTime);
|
||||||
@ -363,10 +363,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Ignore("intentionally broken by import optimisations")]
|
[Ignore("intentionally broken by import optimisations")]
|
||||||
public void TestImportThenImportWithChangedFile()
|
public void TestImportThenImportWithChangedFile()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -375,7 +375,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
using (var zip = ZipArchive.Open(temp))
|
using (var zip = ZipArchive.Open(temp))
|
||||||
zip.WriteToDirectory(extractedFolder);
|
zip.WriteToDirectory(extractedFolder);
|
||||||
@ -392,7 +392,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
Assert.NotNull(importedSecondTime);
|
Assert.NotNull(importedSecondTime);
|
||||||
Debug.Assert(importedSecondTime != null);
|
Debug.Assert(importedSecondTime != null);
|
||||||
@ -411,10 +411,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImportWithDifferentFilename()
|
public void TestImportThenImportWithDifferentFilename()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -423,7 +423,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
using (var zip = ZipArchive.Open(temp))
|
using (var zip = ZipArchive.Open(temp))
|
||||||
zip.WriteToDirectory(extractedFolder);
|
zip.WriteToDirectory(extractedFolder);
|
||||||
@ -440,7 +440,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
Assert.NotNull(importedSecondTime);
|
Assert.NotNull(importedSecondTime);
|
||||||
Debug.Assert(importedSecondTime != null);
|
Debug.Assert(importedSecondTime != null);
|
||||||
@ -460,12 +460,12 @@ namespace osu.Game.Tests.Database
|
|||||||
[Ignore("intentionally broken by import optimisations")]
|
[Ignore("intentionally broken by import optimisations")]
|
||||||
public void TestImportCorruptThenImport()
|
public void TestImportCorruptThenImport()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
var firstFile = imported.Files.First();
|
var firstFile = imported.Files.First();
|
||||||
|
|
||||||
@ -476,7 +476,7 @@ namespace osu.Game.Tests.Database
|
|||||||
using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create))
|
using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create))
|
||||||
stream.WriteByte(0);
|
stream.WriteByte(0);
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
|
using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
|
||||||
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
|
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
|
||||||
@ -485,18 +485,18 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
|
||||||
checkBeatmapSetCount(realmFactory.Context, 1);
|
checkBeatmapSetCount(realm.Realm, 1);
|
||||||
checkSingleReferencedFileCount(realmFactory.Context, 18);
|
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestModelCreationFailureDoesntReturn()
|
public void TestModelCreationFailureDoesntReturn()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var progressNotification = new ImportProgressNotification();
|
var progressNotification = new ImportProgressNotification();
|
||||||
|
|
||||||
@ -510,8 +510,8 @@ namespace osu.Game.Tests.Database
|
|||||||
new ImportTask(zipStream, string.Empty)
|
new ImportTask(zipStream, string.Empty)
|
||||||
);
|
);
|
||||||
|
|
||||||
checkBeatmapSetCount(realmFactory.Context, 0);
|
checkBeatmapSetCount(realm.Realm, 0);
|
||||||
checkBeatmapCount(realmFactory.Context, 0);
|
checkBeatmapCount(realm.Realm, 0);
|
||||||
|
|
||||||
Assert.IsEmpty(imported);
|
Assert.IsEmpty(imported);
|
||||||
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
|
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
|
||||||
@ -521,7 +521,7 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRollbackOnFailure()
|
public void TestRollbackOnFailure()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
int loggedExceptionCount = 0;
|
int loggedExceptionCount = 0;
|
||||||
|
|
||||||
@ -531,16 +531,16 @@ namespace osu.Game.Tests.Database
|
|||||||
Interlocked.Increment(ref loggedExceptionCount);
|
Interlocked.Increment(ref loggedExceptionCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
realmFactory.Context.Write(() => imported.Hash += "-changed");
|
realm.Realm.Write(() => imported.Hash += "-changed");
|
||||||
|
|
||||||
checkBeatmapSetCount(realmFactory.Context, 1);
|
checkBeatmapSetCount(realm.Realm, 1);
|
||||||
checkBeatmapCount(realmFactory.Context, 12);
|
checkBeatmapCount(realm.Realm, 12);
|
||||||
checkSingleReferencedFileCount(realmFactory.Context, 18);
|
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||||
|
|
||||||
string? brokenTempFilename = TestResources.GetTestBeatmapForImport();
|
string? brokenTempFilename = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -565,10 +565,10 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
checkBeatmapSetCount(realmFactory.Context, 1);
|
checkBeatmapSetCount(realm.Realm, 1);
|
||||||
checkBeatmapCount(realmFactory.Context, 12);
|
checkBeatmapCount(realm.Realm, 12);
|
||||||
|
|
||||||
checkSingleReferencedFileCount(realmFactory.Context, 18);
|
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||||
|
|
||||||
Assert.AreEqual(1, loggedExceptionCount);
|
Assert.AreEqual(1, loggedExceptionCount);
|
||||||
|
|
||||||
@ -579,18 +579,18 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDeleteThenImportOptimisedPath()
|
public void TestImportThenDeleteThenImportOptimisedPath()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
deleteBeatmapSet(imported, realmFactory.Context);
|
deleteBeatmapSet(imported, realm.Realm);
|
||||||
|
|
||||||
Assert.IsTrue(imported.DeletePending);
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
@ -601,20 +601,52 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDeleteThenImportNonOptimisedPath()
|
public void TestImportThenReimportAfterMissingFiles()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realmFactory, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||||
|
|
||||||
deleteBeatmapSet(imported, realmFactory.Context);
|
deleteBeatmapSet(imported, realmFactory.Realm);
|
||||||
|
|
||||||
Assert.IsTrue(imported.DeletePending);
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
// intentionally nuke all files
|
||||||
|
storage.DeleteDirectory("files");
|
||||||
|
|
||||||
|
Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
|
||||||
|
|
||||||
|
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
Assert.IsFalse(imported.DeletePending);
|
||||||
|
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||||
|
|
||||||
|
// check that the files now exist, even though they were deleted above.
|
||||||
|
Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestImportThenDeleteThenImportNonOptimisedPath()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
|
{
|
||||||
|
using var importer = new NonOptimisedBeatmapImporter(realm, storage);
|
||||||
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
|
deleteBeatmapSet(imported, realm.Realm);
|
||||||
|
|
||||||
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
@ -627,22 +659,22 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDeleteThenImportWithOnlineIDsMissing()
|
public void TestImportThenDeleteThenImportWithOnlineIDsMissing()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
realmFactory.Context.Write(() =>
|
realm.Realm.Write(() =>
|
||||||
{
|
{
|
||||||
foreach (var b in imported.Beatmaps)
|
foreach (var b in imported.Beatmaps)
|
||||||
b.OnlineID = -1;
|
b.OnlineID = -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
deleteBeatmapSet(imported, realmFactory.Context);
|
deleteBeatmapSet(imported, realm.Realm);
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
|
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
|
||||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||||
@ -653,10 +685,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportWithDuplicateBeatmapIDs()
|
public void TestImportWithDuplicateBeatmapIDs()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
var metadata = new BeatmapMetadata
|
var metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
@ -667,7 +699,7 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var ruleset = realmFactory.Context.All<RulesetInfo>().First();
|
var ruleset = realm.Realm.All<RulesetInfo>().First();
|
||||||
|
|
||||||
var toImport = new BeatmapSetInfo
|
var toImport = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
@ -686,7 +718,7 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = await importer.Import(toImport);
|
var imported = importer.Import(toImport);
|
||||||
|
|
||||||
Assert.NotNull(imported);
|
Assert.NotNull(imported);
|
||||||
Debug.Assert(imported != null);
|
Debug.Assert(imported != null);
|
||||||
@ -699,15 +731,15 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportWhenFileOpen()
|
public void TestImportWhenFileOpen()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
using (File.OpenRead(temp))
|
using (File.OpenRead(temp))
|
||||||
await importer.Import(temp);
|
await importer.Import(temp);
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
File.Delete(temp);
|
File.Delete(temp);
|
||||||
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
||||||
});
|
});
|
||||||
@ -716,10 +748,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportWithDuplicateHashes()
|
public void TestImportWithDuplicateHashes()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -740,7 +772,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
await importer.Import(temp);
|
await importer.Import(temp);
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -752,10 +784,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportNestedStructure()
|
public void TestImportNestedStructure()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -780,7 +812,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.NotNull(imported);
|
Assert.NotNull(imported);
|
||||||
Debug.Assert(imported != null);
|
Debug.Assert(imported != null);
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
|
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
|
||||||
}
|
}
|
||||||
@ -794,10 +826,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportWithIgnoredDirectoryInArchive()
|
public void TestImportWithIgnoredDirectoryInArchive()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
@ -830,7 +862,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.NotNull(imported);
|
Assert.NotNull(imported);
|
||||||
Debug.Assert(imported != null);
|
Debug.Assert(imported != null);
|
||||||
|
|
||||||
EnsureLoaded(realmFactory.Context);
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
|
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
|
||||||
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
|
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
|
||||||
@ -845,22 +877,22 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestUpdateBeatmapInfo()
|
public void TestUpdateBeatmapInfo()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
using var importer = new BeatmapModelManager(realm, storage);
|
||||||
using var store = new RulesetStore(realmFactory, storage);
|
using var store = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
await importer.Import(temp);
|
await importer.Import(temp);
|
||||||
|
|
||||||
// Update via the beatmap, not the beatmap info, to ensure correct linking
|
// Update via the beatmap, not the beatmap info, to ensure correct linking
|
||||||
BeatmapSetInfo setToUpdate = realmFactory.Context.All<BeatmapSetInfo>().First();
|
BeatmapSetInfo setToUpdate = realm.Realm.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
var beatmapToUpdate = setToUpdate.Beatmaps.First();
|
var beatmapToUpdate = setToUpdate.Beatmaps.First();
|
||||||
|
|
||||||
realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated");
|
realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated");
|
||||||
|
|
||||||
BeatmapInfo updatedInfo = realmFactory.Context.All<BeatmapInfo>().First(b => b.ID == beatmapToUpdate.ID);
|
BeatmapInfo updatedInfo = realm.Realm.All<BeatmapInfo>().First(b => b.ID == beatmapToUpdate.ID);
|
||||||
Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
|
Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1004,8 +1036,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
public class NonOptimisedBeatmapImporter : BeatmapImporter
|
public class NonOptimisedBeatmapImporter : BeatmapImporter
|
||||||
{
|
{
|
||||||
public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage)
|
public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage)
|
||||||
: base(realmFactory, storage)
|
: base(realm, storage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportFile()
|
public void TestImportFile()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realmAccess, storage) =>
|
||||||
{
|
{
|
||||||
var realm = realmFactory.Context;
|
var realm = realmAccess.Realm;
|
||||||
var files = new RealmFileStore(realmFactory, storage);
|
var files = new RealmFileStore(realmAccess, storage);
|
||||||
|
|
||||||
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
|
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
|
||||||
|
|
||||||
@ -36,10 +36,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportSameFileTwice()
|
public void TestImportSameFileTwice()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realmAccess, storage) =>
|
||||||
{
|
{
|
||||||
var realm = realmFactory.Context;
|
var realm = realmAccess.Realm;
|
||||||
var files = new RealmFileStore(realmFactory, storage);
|
var files = new RealmFileStore(realmAccess, storage);
|
||||||
|
|
||||||
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
|
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
|
||||||
|
|
||||||
@ -53,10 +53,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDontPurgeReferenced()
|
public void TestDontPurgeReferenced()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realmAccess, storage) =>
|
||||||
{
|
{
|
||||||
var realm = realmFactory.Context;
|
var realm = realmAccess.Realm;
|
||||||
var files = new RealmFileStore(realmFactory, storage);
|
var files = new RealmFileStore(realmAccess, storage);
|
||||||
|
|
||||||
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
|
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
|
||||||
|
|
||||||
@ -92,10 +92,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPurgeUnreferenced()
|
public void TestPurgeUnreferenced()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realmAccess, storage) =>
|
||||||
{
|
{
|
||||||
var realm = realmFactory.Context;
|
var realm = realmAccess.Realm;
|
||||||
var files = new RealmFileStore(realmFactory, storage);
|
var files = new RealmFileStore(realmAccess, storage);
|
||||||
|
|
||||||
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
|
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
|
||||||
|
|
||||||
|
@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestConstructRealm()
|
public void TestConstructRealm()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); });
|
RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBlockOperations()
|
public void TestBlockOperations()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
using (realmFactory.BlockAllOperations())
|
using (realm.BlockAllOperations())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -42,24 +42,25 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestNestedContextCreationWithSubscription()
|
public void TestNestedContextCreationWithSubscription()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
bool callbackRan = false;
|
bool callbackRan = false;
|
||||||
|
|
||||||
realmFactory.Run(realm =>
|
realm.RegisterCustomSubscription(r =>
|
||||||
{
|
{
|
||||||
var subscription = realm.All<BeatmapInfo>().QueryAsyncWithNotifications((sender, changes, error) =>
|
var subscription = r.All<BeatmapInfo>().QueryAsyncWithNotifications((sender, changes, error) =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(_ =>
|
realm.Run(_ =>
|
||||||
{
|
{
|
||||||
callbackRan = true;
|
callbackRan = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Force the callback above to run.
|
// Force the callback above to run.
|
||||||
realmFactory.Run(r => r.Refresh());
|
realm.Run(rr => rr.Refresh());
|
||||||
|
|
||||||
subscription?.Dispose();
|
subscription?.Dispose();
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.IsTrue(callbackRan);
|
Assert.IsTrue(callbackRan);
|
||||||
@ -69,14 +70,14 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBlockOperationsWithContention()
|
public void TestBlockOperationsWithContention()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
|
ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
|
||||||
ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
|
ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(_ =>
|
realm.Run(_ =>
|
||||||
{
|
{
|
||||||
hasThreadedUsage.Set();
|
hasThreadedUsage.Set();
|
||||||
|
|
||||||
@ -88,12 +89,17 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.Throws<TimeoutException>(() =>
|
Assert.Throws<TimeoutException>(() =>
|
||||||
{
|
{
|
||||||
using (realmFactory.BlockAllOperations())
|
using (realm.BlockAllOperations())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stopThreadedUsage.Set();
|
stopThreadedUsage.Set();
|
||||||
|
|
||||||
|
// Ensure we can block a second time after the usage has ended.
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
{
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLiveEquality()
|
public void TestLiveEquality()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
ILive<BeatmapInfo> beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory));
|
Live<BeatmapInfo> beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm));
|
||||||
|
|
||||||
ILive<BeatmapInfo> beatmap2 = realmFactory.Run(realm => realm.All<BeatmapInfo>().First().ToLive(realmFactory));
|
Live<BeatmapInfo> beatmap2 = realm.Run(r => r.All<BeatmapInfo>().First().ToLive(realm));
|
||||||
|
|
||||||
Assert.AreEqual(beatmap, beatmap2);
|
Assert.AreEqual(beatmap, beatmap2);
|
||||||
});
|
});
|
||||||
@ -34,29 +34,27 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAccessAfterStorageMigrate()
|
public void TestAccessAfterStorageMigrate()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
|
|
||||||
realmFactory.Run(realm =>
|
realm.Run(r =>
|
||||||
{
|
{
|
||||||
realm.Write(r => r.Add(beatmap));
|
r.Write(_ => r.Add(beatmap));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
});
|
});
|
||||||
|
|
||||||
using (realmFactory.BlockAllOperations())
|
|
||||||
{
|
|
||||||
// recycle realm before migrating
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
||||||
{
|
{
|
||||||
migratedStorage.DeleteDirectory(string.Empty);
|
migratedStorage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
storage.Migrate(migratedStorage);
|
using (realm.BlockAllOperations())
|
||||||
|
{
|
||||||
|
storage.Migrate(migratedStorage);
|
||||||
|
}
|
||||||
|
|
||||||
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
||||||
}
|
}
|
||||||
@ -66,13 +64,13 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAccessAfterAttach()
|
public void TestAccessAfterAttach()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
var liveBeatmap = beatmap.ToLive(realmFactory);
|
var liveBeatmap = beatmap.ToLive(realm);
|
||||||
|
|
||||||
realmFactory.Run(realm => realm.Write(r => r.Add(beatmap)));
|
realm.Run(r => r.Write(_ => r.Add(beatmap)));
|
||||||
|
|
||||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
});
|
});
|
||||||
@ -98,16 +96,16 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestScopedReadWithoutContext()
|
public void TestScopedReadWithoutContext()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(threadContext =>
|
realm.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
});
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
@ -127,16 +125,16 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestScopedWriteWithoutContext()
|
public void TestScopedWriteWithoutContext()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(threadContext =>
|
realm.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
});
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
@ -153,10 +151,10 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestValueAccessNonManaged()
|
public void TestValueAccessNonManaged()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
var liveBeatmap = beatmap.ToLive(realmFactory);
|
var liveBeatmap = beatmap.ToLive(realm);
|
||||||
|
|
||||||
Assert.DoesNotThrow(() =>
|
Assert.DoesNotThrow(() =>
|
||||||
{
|
{
|
||||||
@ -168,17 +166,17 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestValueAccessWithOpenContextFails()
|
public void TestValueAccessWithOpenContextFails()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(threadContext =>
|
realm.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
});
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
@ -193,7 +191,7 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Can't be used, even from within a valid context.
|
// Can't be used, even from within a valid context.
|
||||||
realmFactory.Run(threadContext =>
|
realm.Run(threadContext =>
|
||||||
{
|
{
|
||||||
Assert.Throws<InvalidOperationException>(() =>
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
{
|
{
|
||||||
@ -207,16 +205,16 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestValueAccessWithoutOpenContextFails()
|
public void TestValueAccessWithoutOpenContextFails()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(threadContext =>
|
realm.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
});
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
@ -235,18 +233,18 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLiveAssumptions()
|
public void TestLiveAssumptions()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realm, _) =>
|
||||||
{
|
{
|
||||||
int changesTriggered = 0;
|
int changesTriggered = 0;
|
||||||
|
|
||||||
realmFactory.Run(outerRealm =>
|
realm.RegisterCustomSubscription(outerRealm =>
|
||||||
{
|
{
|
||||||
outerRealm.All<BeatmapInfo>().QueryAsyncWithNotifications(gotChange);
|
outerRealm.All<BeatmapInfo>().QueryAsyncWithNotifications(gotChange);
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
Live<BeatmapInfo>? liveBeatmap = null;
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
realmFactory.Run(innerRealm =>
|
realm.Run(innerRealm =>
|
||||||
{
|
{
|
||||||
var ruleset = CreateRuleset();
|
var ruleset = CreateRuleset();
|
||||||
var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
@ -255,7 +253,7 @@ namespace osu.Game.Tests.Database
|
|||||||
// not just a refresh from the resolved Live.
|
// not just a refresh from the resolved Live.
|
||||||
innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realm);
|
||||||
});
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
@ -282,6 +280,8 @@ namespace osu.Game.Tests.Database
|
|||||||
r.Remove(resolved);
|
r.Remove(resolved);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
void gotChange(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
void gotChange(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
||||||
|
138
osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
Normal file
138
osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class RealmSubscriptionRegistrationTests : RealmTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSubscriptionWithContextLoss()
|
||||||
|
{
|
||||||
|
IEnumerable<BeatmapSetInfo>? resolvedItems = null;
|
||||||
|
ChangeSet? lastChanges = null;
|
||||||
|
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), onChanged);
|
||||||
|
|
||||||
|
testEventsArriving(true);
|
||||||
|
|
||||||
|
// All normal until here.
|
||||||
|
// Now let's yank the main realm context.
|
||||||
|
resolvedItems = null;
|
||||||
|
lastChanges = null;
|
||||||
|
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
Assert.That(resolvedItems, Is.Empty);
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
testEventsArriving(true);
|
||||||
|
|
||||||
|
// Now let's try unsubscribing.
|
||||||
|
resolvedItems = null;
|
||||||
|
lastChanges = null;
|
||||||
|
|
||||||
|
registration.Dispose();
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
testEventsArriving(false);
|
||||||
|
|
||||||
|
// And make sure even after another context loss we don't get firings.
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
Assert.That(resolvedItems, Is.Null);
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
testEventsArriving(false);
|
||||||
|
|
||||||
|
void testEventsArriving(bool shouldArrive)
|
||||||
|
{
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
if (shouldArrive)
|
||||||
|
Assert.That(resolvedItems, Has.One.Items);
|
||||||
|
else
|
||||||
|
Assert.That(resolvedItems, Is.Null);
|
||||||
|
|
||||||
|
realm.Write(r =>
|
||||||
|
{
|
||||||
|
r.RemoveAll<BeatmapSetInfo>();
|
||||||
|
r.RemoveAll<RulesetInfo>();
|
||||||
|
});
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
if (shouldArrive)
|
||||||
|
Assert.That(lastChanges?.DeletedIndices, Has.One.Items);
|
||||||
|
else
|
||||||
|
Assert.That(lastChanges, Is.Null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error)
|
||||||
|
{
|
||||||
|
if (changes == null)
|
||||||
|
resolvedItems = sender;
|
||||||
|
|
||||||
|
lastChanges = changes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomRegisterWithContextLoss()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
BeatmapSetInfo? beatmapSetInfo = null;
|
||||||
|
|
||||||
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||||
|
|
||||||
|
var subscription = realm.RegisterCustomSubscription(r =>
|
||||||
|
{
|
||||||
|
beatmapSetInfo = r.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
|
return new InvokeOnDisposal(() => beatmapSetInfo = null);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(beatmapSetInfo, Is.Not.Null);
|
||||||
|
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
{
|
||||||
|
// custom disposal action fired when context lost.
|
||||||
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-registration after context restore.
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
Assert.That(beatmapSetInfo, Is.Not.Null);
|
||||||
|
|
||||||
|
subscription.Dispose();
|
||||||
|
|
||||||
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
|
|
||||||
|
using (realm.BlockAllOperations())
|
||||||
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
Assert.That(beatmapSetInfo, Is.Null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database
|
|||||||
storage.DeleteDirectory(string.Empty);
|
storage.DeleteDirectory(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void RunTestWithRealm(Action<RealmContextFactory, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
protected void RunTestWithRealm(Action<RealmAccess, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
||||||
{
|
{
|
||||||
@ -39,22 +39,22 @@ namespace osu.Game.Tests.Database
|
|||||||
// ReSharper disable once AccessToDisposedClosure
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
|
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
|
||||||
|
|
||||||
using (var realmFactory = new RealmContextFactory(testStorage, "client"))
|
using (var realm = new RealmAccess(testStorage, "client"))
|
||||||
{
|
{
|
||||||
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
|
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
|
||||||
testAction(realmFactory, testStorage);
|
testAction(realm, testStorage);
|
||||||
|
|
||||||
realmFactory.Dispose();
|
realm.Dispose();
|
||||||
|
|
||||||
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
|
||||||
realmFactory.Compact();
|
realm.Compact();
|
||||||
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
|
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void RunTestWithRealmAsync(Func<RealmContextFactory, Storage, Task> testAction, [CallerMemberName] string caller = "")
|
protected void RunTestWithRealmAsync(Func<RealmAccess, Storage, Task> testAction, [CallerMemberName] string caller = "")
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
|
||||||
{
|
{
|
||||||
@ -62,15 +62,15 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
var testStorage = storage.GetStorageForDirectory(caller);
|
||||||
|
|
||||||
using (var realmFactory = new RealmContextFactory(testStorage, "client"))
|
using (var realm = new RealmAccess(testStorage, "client"))
|
||||||
{
|
{
|
||||||
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
|
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
|
||||||
await testAction(realmFactory, testStorage);
|
await testAction(realm, testStorage);
|
||||||
|
|
||||||
realmFactory.Dispose();
|
realm.Dispose();
|
||||||
|
|
||||||
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
|
||||||
realmFactory.Compact();
|
realm.Compact();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static RulesetInfo CreateRuleset() =>
|
protected static RulesetInfo CreateRuleset() =>
|
||||||
new RulesetInfo(0, "osu!", "osu", true);
|
new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true };
|
||||||
|
|
||||||
private class RealmTestGame : Framework.Game
|
private class RealmTestGame : Framework.Game
|
||||||
{
|
{
|
||||||
@ -138,11 +138,11 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
|
private static long getFileSize(Storage testStorage, RealmAccess realm)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var stream = testStorage.GetStream(realmFactory.Filename))
|
using (var stream = testStorage.GetStream(realm.Filename))
|
||||||
return stream?.Length ?? 0;
|
return stream?.Length ?? 0;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -12,37 +12,37 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCreateStore()
|
public void TestCreateStore()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
var rulesets = new RulesetStore(realmFactory, storage);
|
var rulesets = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
Assert.AreEqual(4, realmFactory.Context.All<RulesetInfo>().Count());
|
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
|
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
var rulesets = new RulesetStore(realmFactory, storage);
|
var rulesets = new RulesetStore(realm, storage);
|
||||||
var rulesets2 = new RulesetStore(realmFactory, storage);
|
var rulesets2 = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||||
|
|
||||||
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
||||||
Assert.AreEqual(4, realmFactory.Context.All<RulesetInfo>().Count());
|
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRetrievedRulesetsAreDetached()
|
public void TestRetrievedRulesetsAreDetached()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
var rulesets = new RulesetStore(realmFactory, storage);
|
var rulesets = new RulesetStore(realm, storage);
|
||||||
|
|
||||||
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
|
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
|
||||||
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
|
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
private RealmKeyBindingStore keyBindingStore;
|
private RealmKeyBindingStore keyBindingStore;
|
||||||
|
|
||||||
private RealmContextFactory realmContextFactory;
|
private RealmAccess realm;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
storage = new NativeStorage(directory.FullName);
|
storage = new NativeStorage(directory.FullName);
|
||||||
|
|
||||||
realmContextFactory = new RealmContextFactory(storage, "test");
|
realm = new RealmAccess(storage, "test");
|
||||||
keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider());
|
keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -60,11 +60,11 @@ namespace osu.Game.Tests.Database
|
|||||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||||
|
|
||||||
// Add some excess bindings for an action which only supports 1.
|
// Add some excess bindings for an action which only supports 1.
|
||||||
realmContextFactory.Write(realm =>
|
realm.Write(r =>
|
||||||
{
|
{
|
||||||
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
|
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
|
||||||
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
|
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
|
||||||
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
|
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
||||||
@ -76,9 +76,9 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
private int queryCount(GlobalAction? match = null)
|
private int queryCount(GlobalAction? match = null)
|
||||||
{
|
{
|
||||||
return realmContextFactory.Run(realm =>
|
return realm.Run(r =>
|
||||||
{
|
{
|
||||||
var results = realm.All<RealmKeyBinding>();
|
var results = r.All<RealmKeyBinding>();
|
||||||
if (match.HasValue)
|
if (match.HasValue)
|
||||||
results = results.Where(k => k.ActionInt == (int)match.Value);
|
results = results.Where(k => k.ActionInt == (int)match.Value);
|
||||||
return results.Count();
|
return results.Count();
|
||||||
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||||
|
|
||||||
realmContextFactory.Run(outerRealm =>
|
realm.Run(outerRealm =>
|
||||||
{
|
{
|
||||||
var backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
var backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var tsr = ThreadSafeReference.Create(backBinding);
|
var tsr = ThreadSafeReference.Create(backBinding);
|
||||||
|
|
||||||
realmContextFactory.Run(innerRealm =>
|
realm.Run(innerRealm =>
|
||||||
{
|
{
|
||||||
var binding = innerRealm.ResolveReference(tsr);
|
var binding = innerRealm.ResolveReference(tsr);
|
||||||
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
|
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
|
||||||
@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database
|
|||||||
[TearDown]
|
[TearDown]
|
||||||
public void TearDown()
|
public void TearDown()
|
||||||
{
|
{
|
||||||
realmContextFactory.Dispose();
|
realm.Dispose();
|
||||||
storage.DeleteDirectory(string.Empty);
|
storage.DeleteDirectory(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
|
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
|
||||||
|
|
||||||
// Should fail to load, but not produce an error due to the extension not being expected to load.
|
// Should fail to load, but not produce an error due to the extension not being expected to load.
|
||||||
Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
|
Assert.IsEmpty(check.Run(getContext(null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
|
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
|
||||||
{
|
{
|
||||||
Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true)));
|
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false)
|
private BeatmapVerifierContext getContext(Stream resourceStream)
|
||||||
{
|
{
|
||||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||||
|
@ -131,8 +131,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||||
|
|
||||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
|
|
||||||
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -12,47 +18,93 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
[HeadlessTest]
|
[HeadlessTest]
|
||||||
public class TestSceneMasterGameplayClockContainer : OsuTestScene
|
public class TestSceneMasterGameplayClockContainer : OsuTestScene
|
||||||
{
|
{
|
||||||
|
private OsuConfigManager localConfig;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestStartThenElapsedTime()
|
public void TestStartThenElapsedTime()
|
||||||
{
|
{
|
||||||
GameplayClockContainer gcc = null;
|
GameplayClockContainer gameplayClockContainer = null;
|
||||||
|
|
||||||
AddStep("create container", () =>
|
AddStep("create container", () =>
|
||||||
{
|
{
|
||||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
working.LoadTrack();
|
working.LoadTrack();
|
||||||
|
|
||||||
Add(gcc = new MasterGameplayClockContainer(working, 0));
|
Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start clock", () => gcc.Start());
|
AddStep("start clock", () => gameplayClockContainer.Start());
|
||||||
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
|
AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestElapseThenReset()
|
public void TestElapseThenReset()
|
||||||
{
|
{
|
||||||
GameplayClockContainer gcc = null;
|
GameplayClockContainer gameplayClockContainer = null;
|
||||||
|
|
||||||
AddStep("create container", () =>
|
AddStep("create container", () =>
|
||||||
{
|
{
|
||||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
working.LoadTrack();
|
working.LoadTrack();
|
||||||
|
|
||||||
Add(gcc = new MasterGameplayClockContainer(working, 0));
|
Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start clock", () => gcc.Start());
|
AddStep("start clock", () => gameplayClockContainer.Start());
|
||||||
AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000);
|
AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000);
|
||||||
|
|
||||||
double timeAtReset = 0;
|
double timeAtReset = 0;
|
||||||
AddStep("reset clock", () =>
|
AddStep("reset clock", () =>
|
||||||
{
|
{
|
||||||
timeAtReset = gcc.GameplayClock.CurrentTime;
|
timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime;
|
||||||
gcc.Reset();
|
gameplayClockContainer.Reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset);
|
AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSeekPerformsInGameplayTime(
|
||||||
|
[Values(1.0, 0.5, 2.0)] double clockRate,
|
||||||
|
[Values(0.0, 200.0, -200.0)] double userOffset,
|
||||||
|
[Values(false, true)] bool whileStopped)
|
||||||
|
{
|
||||||
|
ClockBackedTestWorkingBeatmap working = null;
|
||||||
|
GameplayClockContainer gameplayClockContainer = null;
|
||||||
|
|
||||||
|
AddStep("create container", () =>
|
||||||
|
{
|
||||||
|
working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio);
|
||||||
|
working.LoadTrack();
|
||||||
|
|
||||||
|
Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
|
||||||
|
|
||||||
|
if (whileStopped)
|
||||||
|
gameplayClockContainer.Stop();
|
||||||
|
|
||||||
|
gameplayClockContainer.Reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate)));
|
||||||
|
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
|
||||||
|
|
||||||
|
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
|
||||||
|
AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f));
|
||||||
|
|
||||||
|
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
|
||||||
|
AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
localConfig?.Dispose();
|
||||||
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
@ -42,6 +48,43 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResetFromReplayFrame()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject> { HitObjects = { new HitCircle() } };
|
||||||
|
|
||||||
|
var scoreProcessor = new ScoreProcessor();
|
||||||
|
scoreProcessor.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
|
||||||
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||||
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
|
||||||
|
// No header shouldn't cause any change
|
||||||
|
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame());
|
||||||
|
|
||||||
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||||
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
|
||||||
|
// Reset with a miss instead.
|
||||||
|
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
||||||
|
{
|
||||||
|
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||||
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
|
||||||
|
// Reset with no judged hit.
|
||||||
|
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
||||||
|
{
|
||||||
|
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||||
|
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestJudgement : Judgement
|
private class TestJudgement : Judgement
|
||||||
{
|
{
|
||||||
public override HitResult MaxResult { get; }
|
public override HitResult MaxResult { get; }
|
||||||
|
@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
public AudioManager AudioManager => Audio;
|
public AudioManager AudioManager => Audio;
|
||||||
public IResourceStore<byte[]> Files => null;
|
public IResourceStore<byte[]> Files => null;
|
||||||
public new IResourceStore<byte[]> Resources => base.Resources;
|
public new IResourceStore<byte[]> Resources => base.Resources;
|
||||||
public RealmContextFactory RealmContextFactory => null;
|
public RealmAccess RealmAccess => null;
|
||||||
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
const int beat_length_numerator = 2000;
|
const int beat_length_numerator = 2000;
|
||||||
const int beat_length_denominator = 7;
|
const int beat_length_denominator = 7;
|
||||||
const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
|
TimeSignature signature = TimeSignature.SimpleQuadruple;
|
||||||
|
|
||||||
var beatmap = new Beatmap
|
var beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
|
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
|
||||||
{
|
{
|
||||||
var barLine = barLines[i * beat_length_denominator];
|
var barLine = barLines[i * beat_length_denominator];
|
||||||
int expectedTime = beat_length_numerator * (int)signature * i;
|
int expectedTime = beat_length_numerator * signature.Numerator * i;
|
||||||
|
|
||||||
// every seventh bar's start time should be at least greater than the whole number we expect.
|
// every seventh bar's start time should be at least greater than the whole number we expect.
|
||||||
// It cannot be less, as that can affect overlapping scroll algorithms
|
// It cannot be less, as that can affect overlapping scroll algorithms
|
||||||
@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
|
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
|
||||||
|
|
||||||
// check major/minor lines for good measure too
|
// check major/minor lines for good measure too
|
||||||
Assert.AreEqual(i % (int)signature == 0, barLine.Major);
|
Assert.AreEqual(i % signature.Numerator == 0, barLine.Major);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,19 +142,28 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
Assert.That(osuStorage, Is.Not.Null);
|
Assert.That(osuStorage, Is.Not.Null);
|
||||||
|
|
||||||
|
// In the following tests, realm files are ignored as
|
||||||
|
// - in the case of checking the source, interacting with the pipe files (client.realm.note) may
|
||||||
|
// lead to unexpected behaviour.
|
||||||
|
// - in the case of checking the destination, the files may have already been recreated by the game
|
||||||
|
// as part of the standard migration flow.
|
||||||
|
|
||||||
foreach (string file in osuStorage.IgnoreFiles)
|
foreach (string file in osuStorage.IgnoreFiles)
|
||||||
{
|
{
|
||||||
// avoid touching realm files which may be a pipe and break everything.
|
if (!file.Contains("realm", StringComparison.Ordinal))
|
||||||
// this is also done locally inside OsuStorage via the IgnoreFiles list.
|
{
|
||||||
if (file.EndsWith(".ini", StringComparison.Ordinal))
|
|
||||||
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
|
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
|
||||||
Assert.That(storage.Exists(file), Is.False);
|
Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string dir in osuStorage.IgnoreDirectories)
|
foreach (string dir in osuStorage.IgnoreDirectories)
|
||||||
{
|
{
|
||||||
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
|
if (!dir.Contains("realm", StringComparison.Ordinal))
|
||||||
Assert.That(storage.ExistsDirectory(dir), Is.False);
|
{
|
||||||
|
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
|
||||||
|
Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}"));
|
Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}"));
|
||||||
|
@ -16,7 +16,11 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
{
|
{
|
||||||
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
|
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
|
||||||
{
|
{
|
||||||
Ruleset = new RulesetInfo { OnlineID = 0 },
|
Ruleset = new RulesetInfo
|
||||||
|
{
|
||||||
|
ShortName = "osu",
|
||||||
|
OnlineID = 0
|
||||||
|
},
|
||||||
StarRating = 4.0d,
|
StarRating = 4.0d,
|
||||||
Difficulty = new BeatmapDifficulty
|
Difficulty = new BeatmapDifficulty
|
||||||
{
|
{
|
||||||
@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
var exampleBeatmapInfo = getExampleBeatmap();
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
var criteria = new FilterCriteria
|
var criteria = new FilterCriteria
|
||||||
{
|
{
|
||||||
Ruleset = new RulesetInfo { OnlineID = 6 }
|
Ruleset = new RulesetInfo { ShortName = "catch" }
|
||||||
};
|
};
|
||||||
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
carouselItem.Filter(criteria);
|
carouselItem.Filter(criteria);
|
||||||
@ -78,6 +82,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.IsFalse(carouselItem.Filtered.Value);
|
Assert.IsFalse(carouselItem.Filtered.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets()
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { OnlineID = -1 },
|
||||||
|
AllowConvertedBeatmaps = true
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.IsFalse(carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
|
38
osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs
Normal file
38
osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.NonVisual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class RulesetInfoOrderingTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestOrdering()
|
||||||
|
{
|
||||||
|
var rulesets = new[]
|
||||||
|
{
|
||||||
|
new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
|
||||||
|
new OsuRuleset().RulesetInfo,
|
||||||
|
new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
|
||||||
|
new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
|
||||||
|
new CatchRuleset().RulesetInfo,
|
||||||
|
new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
|
||||||
|
};
|
||||||
|
|
||||||
|
var orderedRulesets = rulesets.OrderBy(r => r);
|
||||||
|
|
||||||
|
// Ensure all customs are after official.
|
||||||
|
Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 }));
|
||||||
|
|
||||||
|
// Ensure customs are grouped next to each other (ie. stably sorted).
|
||||||
|
Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2"));
|
||||||
|
Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
score.Statistics[HitResult.Good]++;
|
score.Statistics[HitResult.Good]++;
|
||||||
score.Rank = ScoreRank.X;
|
score.Rank = ScoreRank.X;
|
||||||
|
score.RealmUser.Username = "test";
|
||||||
|
|
||||||
Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10));
|
Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10));
|
||||||
Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11));
|
Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11));
|
||||||
|
|
||||||
Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B));
|
Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B));
|
||||||
Assert.That(score.Rank, Is.EqualTo(ScoreRank.X));
|
Assert.That(score.Rank, Is.EqualTo(ScoreRank.X));
|
||||||
|
|
||||||
|
Assert.That(scoreCopy.RealmUser.Username, Is.Empty);
|
||||||
|
Assert.That(score.RealmUser.Username, Is.EqualTo("test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -61,7 +61,6 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
|
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
|
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotSupportedException();
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotSupportedException();
|
||||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestAnimationTimeReference : IAnimationTimeReference
|
private class TestAnimationTimeReference : IAnimationTimeReference
|
||||||
|
@ -45,9 +45,9 @@ namespace osu.Game.Tests.Online
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, GameHost host)
|
private void load(AudioManager audio, GameHost host)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
|
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host));
|
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online
|
|||||||
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
||||||
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
ContextFactory.Write(r => r.RemoveAll<BeatmapSetInfo>());
|
Realm.Write(r => r.RemoveAll<BeatmapSetInfo>());
|
||||||
ContextFactory.Write(r => r.RemoveAll<BeatmapInfo>());
|
Realm.Write(r => r.RemoveAll<BeatmapInfo>());
|
||||||
|
|
||||||
selectedItem.Value = new PlaylistItem
|
selectedItem.Value = new PlaylistItem
|
||||||
{
|
{
|
||||||
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Online
|
|||||||
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
||||||
|
|
||||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
||||||
AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true);
|
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
|
||||||
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,39 +164,39 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
|
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
public Task<ILive<BeatmapSetInfo>> CurrentImportTask { get; private set; }
|
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
|
||||||
|
|
||||||
public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
|
public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
|
||||||
: base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
|
: base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
||||||
{
|
{
|
||||||
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue);
|
return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TestBeatmapModelManager : BeatmapModelManager
|
internal class TestBeatmapModelManager : BeatmapModelManager
|
||||||
{
|
{
|
||||||
private readonly TestBeatmapManager testBeatmapManager;
|
private readonly TestBeatmapManager testBeatmapManager;
|
||||||
|
|
||||||
public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
||||||
: base(databaseContextFactory, storage, beatmapOnlineLookupQueue)
|
: base(databaseAccess, storage, beatmapOnlineLookupQueue)
|
||||||
{
|
{
|
||||||
this.testBeatmapManager = testBeatmapManager;
|
this.testBeatmapManager = testBeatmapManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<ILive<BeatmapSetInfo>> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
public override Live<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await testBeatmapManager.AllowImport.Task.ConfigureAwait(false);
|
testBeatmapManager.AllowImport.Task.WaitSafely();
|
||||||
return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
|
return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
|
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
|
||||||
{
|
{
|
||||||
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider, GameHost gameHost)
|
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider)
|
||||||
: base(importer, apiProvider)
|
: base(importer, apiProvider)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,10 @@ namespace osu.Game.Tests.Resources
|
|||||||
public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null)
|
public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null)
|
||||||
{
|
{
|
||||||
int j = 0;
|
int j = 0;
|
||||||
RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo;
|
|
||||||
|
rulesets ??= new[] { new OsuRuleset().RulesetInfo };
|
||||||
|
|
||||||
|
RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
|
||||||
|
|
||||||
int setId = Interlocked.Increment(ref importId);
|
int setId = Interlocked.Increment(ref importId);
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
[Difficulty]
|
||||||
|
OverallDifficulty:1
|
||||||
|
ApproachRate:9
|
@ -0,0 +1,3 @@
|
|||||||
|
[Difficulty]
|
||||||
|
ApproachRate:9
|
||||||
|
OverallDifficulty:1
|
2
osu.Game.Tests/Resources/undefined-approach-rate.osu
Normal file
2
osu.Game.Tests/Resources/undefined-approach-rate.osu
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[Difficulty]
|
||||||
|
OverallDifficulty:1
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -25,7 +24,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
public class ImportScoreTest : ImportTest
|
public class ImportScoreTest : ImportTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestBasicImport()
|
public void TestBasicImport()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
@ -49,7 +48,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
BeatmapInfo = beatmap.Beatmaps.First()
|
BeatmapInfo = beatmap.Beatmaps.First()
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = await LoadScoreIntoOsu(osu, toImport);
|
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||||
|
|
||||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||||
@ -67,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportMods()
|
public void TestImportMods()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
@ -85,7 +84,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = await LoadScoreIntoOsu(osu, toImport);
|
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||||
|
|
||||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
|
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
|
||||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
|
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
|
||||||
@ -98,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportStatistics()
|
public void TestImportStatistics()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
@ -120,7 +119,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var imported = await LoadScoreIntoOsu(osu, toImport);
|
var imported = LoadScoreIntoOsu(osu, toImport);
|
||||||
|
|
||||||
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
|
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
|
||||||
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
|
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
|
||||||
@ -133,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestOnlineScoreIsAvailableLocally()
|
public void TestOnlineScoreIsAvailableLocally()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
@ -143,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
|
|
||||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||||
|
|
||||||
await LoadScoreIntoOsu(osu, new ScoreInfo
|
LoadScoreIntoOsu(osu, new ScoreInfo
|
||||||
{
|
{
|
||||||
User = new APIUser { Username = "Test user" },
|
User = new APIUser { Username = "Test user" },
|
||||||
BeatmapInfo = beatmap.Beatmaps.First(),
|
BeatmapInfo = beatmap.Beatmaps.First(),
|
||||||
@ -168,13 +167,14 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<ScoreInfo> LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
|
public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
|
||||||
{
|
{
|
||||||
// clone to avoid attaching the input score to realm.
|
// clone to avoid attaching the input score to realm.
|
||||||
score = score.DeepClone();
|
score = score.DeepClone();
|
||||||
|
|
||||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||||
await scoreManager.Import(score, archive);
|
|
||||||
|
scoreManager.Import(score, archive);
|
||||||
|
|
||||||
return scoreManager.Query(_ => true);
|
return scoreManager.Query(_ => true);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void assertCorrectMetadata(ILive<SkinInfo> import1, string name, string creator, OsuGameBase osu)
|
private void assertCorrectMetadata(Live<SkinInfo> import1, string name, string creator, OsuGameBase osu)
|
||||||
{
|
{
|
||||||
import1.PerformRead(i =>
|
import1.PerformRead(i =>
|
||||||
{
|
{
|
||||||
@ -250,7 +250,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertImportedBoth(ILive<SkinInfo> import1, ILive<SkinInfo> import2)
|
private void assertImportedBoth(Live<SkinInfo> import1, Live<SkinInfo> import2)
|
||||||
{
|
{
|
||||||
import1.PerformRead(i1 => import2.PerformRead(i2 =>
|
import1.PerformRead(i1 => import2.PerformRead(i2 =>
|
||||||
{
|
{
|
||||||
@ -260,7 +260,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertImportedOnce(ILive<SkinInfo> import1, ILive<SkinInfo> import2)
|
private void assertImportedOnce(Live<SkinInfo> import1, Live<SkinInfo> import2)
|
||||||
{
|
{
|
||||||
import1.PerformRead(i1 => import2.PerformRead(i2 =>
|
import1.PerformRead(i1 => import2.PerformRead(i2 =>
|
||||||
{
|
{
|
||||||
@ -334,7 +334,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ILive<SkinInfo>> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
|
private async Task<Live<SkinInfo>> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
|
||||||
{
|
{
|
||||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||||
return await skinManager.Import(archive);
|
return await skinManager.Import(archive);
|
||||||
|
@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||||
Dependencies.Cache(ContextFactory);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host)
|
private void load(GameHost host)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.Cache(ContextFactory);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
@ -37,11 +38,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadEditor()
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
{
|
=> beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
|
|
||||||
base.LoadEditor();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicSwitch()
|
public void TestBasicSwitch()
|
||||||
@ -84,8 +82,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("set target difficulty", () =>
|
AddStep("set target difficulty", () =>
|
||||||
{
|
{
|
||||||
targetDifficulty = sameRuleset
|
targetDifficulty = sameRuleset
|
||||||
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
|
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName)
|
||||||
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
|
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName);
|
||||||
});
|
});
|
||||||
switchToDifficulty(() => targetDifficulty);
|
switchToDifficulty(() => targetDifficulty);
|
||||||
confirmEditingBeatmap(() => targetDifficulty);
|
confirmEditingBeatmap(() => targetDifficulty);
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
@ -39,11 +40,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadEditor()
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null);
|
||||||
{
|
|
||||||
Beatmap.Value = new DummyWorkingBeatmap(Audio, null);
|
|
||||||
base.LoadEditor();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCreateNewBeatmap()
|
public void TestCreateNewBeatmap()
|
||||||
@ -93,5 +90,100 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateNewDifficulty()
|
||||||
|
{
|
||||||
|
string firstDifficultyName = Guid.NewGuid().ToString();
|
||||||
|
string secondDifficultyName = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
|
||||||
|
AddStep("save beatmap", () => Editor.Save());
|
||||||
|
AddAssert("new beatmap persisted", () =>
|
||||||
|
{
|
||||||
|
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName);
|
||||||
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
|
||||||
|
|
||||||
|
return beatmap != null
|
||||||
|
&& beatmap.DifficultyName == firstDifficultyName
|
||||||
|
&& set != null
|
||||||
|
&& set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID);
|
||||||
|
});
|
||||||
|
AddAssert("can save again", () => Editor.Save());
|
||||||
|
|
||||||
|
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||||
|
AddUntilStep("wait for created", () =>
|
||||||
|
{
|
||||||
|
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||||
|
return difficultyName != null && difficultyName != firstDifficultyName;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName);
|
||||||
|
AddStep("save beatmap", () => Editor.Save());
|
||||||
|
AddAssert("new beatmap persisted", () =>
|
||||||
|
{
|
||||||
|
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName);
|
||||||
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
|
||||||
|
|
||||||
|
return beatmap != null
|
||||||
|
&& beatmap.DifficultyName == secondDifficultyName
|
||||||
|
&& set != null
|
||||||
|
&& set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties()
|
||||||
|
{
|
||||||
|
Guid setId = Guid.Empty;
|
||||||
|
|
||||||
|
AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
|
||||||
|
AddStep("save beatmap", () => Editor.Save());
|
||||||
|
AddAssert("new beatmap persisted", () =>
|
||||||
|
{
|
||||||
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
|
||||||
|
return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||||
|
AddAssert("beatmap set unchanged", () =>
|
||||||
|
{
|
||||||
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
|
||||||
|
return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateNewBeatmapFailsWithSameNamedDifficulties()
|
||||||
|
{
|
||||||
|
Guid setId = Guid.Empty;
|
||||||
|
const string duplicate_difficulty_name = "duplicate";
|
||||||
|
|
||||||
|
AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
|
||||||
|
AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name);
|
||||||
|
AddStep("save beatmap", () => Editor.Save());
|
||||||
|
AddAssert("new beatmap persisted", () =>
|
||||||
|
{
|
||||||
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
|
||||||
|
return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||||
|
AddUntilStep("wait for created", () =>
|
||||||
|
{
|
||||||
|
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||||
|
return difficultyName != null && difficultyName != duplicate_difficulty_name;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name);
|
||||||
|
AddStep("try to save beatmap", () => Editor.Save());
|
||||||
|
AddAssert("beatmap set not corrupted", () =>
|
||||||
|
{
|
||||||
|
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
|
||||||
|
// the difficulty was already created at the point of the switch.
|
||||||
|
// what we want to check is that both difficulties do not use the same file.
|
||||||
|
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,92 +3,139 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osu.Game.Screens.Menu;
|
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
public class TestSceneEditorSaving : OsuGameTestScene
|
public class TestSceneEditorSaving : EditorSavingTestScene
|
||||||
{
|
{
|
||||||
private Editor editor => Game.ChildrenOfType<Editor>().FirstOrDefault();
|
|
||||||
|
|
||||||
private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNewBeatmapSaveThenLoad()
|
public void TestMetadata()
|
||||||
{
|
{
|
||||||
AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
|
|
||||||
|
|
||||||
PushAndConfirm(() => new EditorLoader());
|
|
||||||
|
|
||||||
AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
|
|
||||||
|
|
||||||
AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
|
||||||
|
|
||||||
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
|
||||||
|
|
||||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
|
||||||
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
|
||||||
|
|
||||||
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
|
|
||||||
AddStep("Set artist and title", () =>
|
AddStep("Set artist and title", () =>
|
||||||
{
|
{
|
||||||
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
EditorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||||
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
EditorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||||
});
|
});
|
||||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
AddStep("Set author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username = "author");
|
||||||
|
AddStep("Set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
||||||
|
|
||||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
SaveEditor();
|
||||||
|
|
||||||
|
AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||||
|
AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
||||||
|
AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||||
|
AddAssert("Beatmap has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
|
AddAssert("Beatmap still has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||||
|
AddAssert("Beatmap still has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
|
||||||
|
AddAssert("Beatmap still has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||||
|
AddAssert("Beatmap still has correct .osu file path", () => EditorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestConfiguration()
|
||||||
|
{
|
||||||
|
double originalTimelineZoom = 0;
|
||||||
|
double changedTimelineZoom = 0;
|
||||||
|
|
||||||
|
AddStep("Set beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().Value = 16);
|
||||||
|
AddStep("Set timeline zoom", () =>
|
||||||
|
{
|
||||||
|
originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
|
||||||
|
|
||||||
|
var timeline = Editor.ChildrenOfType<Timeline>().Single();
|
||||||
|
InputManager.MoveMouseTo(timeline);
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.ScrollVerticalBy(15f);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Ensure timeline zoom changed", () =>
|
||||||
|
{
|
||||||
|
changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
|
||||||
|
return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom);
|
||||||
|
});
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
|
||||||
|
AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||||
|
AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
|
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||||
|
AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifficulty()
|
||||||
|
{
|
||||||
|
AddStep("Set overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty = 7);
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
|
||||||
|
AddAssert("Beatmap has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
|
AddAssert("Beatmap still has correct overall difficulty", () => EditorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitObjectPlacement()
|
||||||
|
{
|
||||||
|
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
|
||||||
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||||
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
checkMutations();
|
SaveEditor();
|
||||||
|
|
||||||
|
AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
||||||
|
|
||||||
// After placement these must be non-default as defaults are read-only.
|
// After placement these must be non-default as defaults are read-only.
|
||||||
AddAssert("Placed object has non-default control points", () =>
|
AddAssert("Placed object has non-default control points", () =>
|
||||||
editorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||||
editorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||||
|
|
||||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
checkMutations();
|
AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
||||||
|
|
||||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
// After placement these must be non-default as defaults are read-only.
|
||||||
|
AddAssert("Placed object still has non-default control points", () =>
|
||||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||||
|
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||||
Screens.Select.SongSelect songSelect = null;
|
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new PlaySongSelect());
|
|
||||||
AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded);
|
|
||||||
|
|
||||||
AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
|
|
||||||
AddStep("Open options", () => InputManager.Key(Key.F3));
|
|
||||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
|
||||||
|
|
||||||
AddUntilStep("Wait for editor load", () => editor != null);
|
|
||||||
|
|
||||||
checkMutations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkMutations()
|
[Test]
|
||||||
|
public void TestExitWithoutSaveFromExistingBeatmap()
|
||||||
{
|
{
|
||||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
const string tags_to_save = "these tags will be saved";
|
||||||
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
|
const string tags_to_discard = "these tags should be discarded";
|
||||||
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
|
||||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save);
|
||||||
|
SaveEditor();
|
||||||
|
AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
|
||||||
|
AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard);
|
||||||
|
|
||||||
|
AddStep("Exit editor", () => Editor.Exit());
|
||||||
|
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||||
|
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Screens.Edit;
|
|||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
using osu.Game.Screens.Edit.GameplayTest;
|
using osu.Game.Screens.Edit.GameplayTest;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -43,9 +44,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
=> beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0));
|
||||||
|
|
||||||
protected override void LoadEditor()
|
protected override void LoadEditor()
|
||||||
{
|
{
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
|
|
||||||
SelectedMods.Value = new[] { new ModCinema() };
|
SelectedMods.Value = new[] { new ModCinema() };
|
||||||
base.LoadEditor();
|
base.LoadEditor();
|
||||||
}
|
}
|
||||||
@ -67,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
AddUntilStep("background has correct params", () =>
|
AddUntilStep("background has correct params", () =>
|
||||||
{
|
{
|
||||||
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
|
// the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
|
||||||
|
// due to the beatmap refetch logic ran on editor suspend.
|
||||||
|
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
|
||||||
|
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
|
||||||
|
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
|
||||||
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
||||||
});
|
});
|
||||||
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
|
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
|
||||||
@ -96,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
AddUntilStep("background has correct params", () =>
|
AddUntilStep("background has correct params", () =>
|
||||||
{
|
{
|
||||||
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
|
// the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
|
||||||
|
// due to the beatmap refetch logic ran on editor suspend.
|
||||||
|
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
|
||||||
|
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
|
||||||
|
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
|
||||||
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private LabelledTimeSignature timeSignature;
|
||||||
|
|
||||||
|
private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () =>
|
||||||
|
{
|
||||||
|
Child = timeSignature = new LabelledTimeSignature
|
||||||
|
{
|
||||||
|
Label = "Time Signature",
|
||||||
|
RelativeSizeAxes = Axes.None,
|
||||||
|
Width = 400,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Current = { Value = initial }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType<OsuTextBox>().Single();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInitialValue()
|
||||||
|
{
|
||||||
|
createLabelledTimeSignature(TimeSignature.SimpleTriple);
|
||||||
|
AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeViaCurrent()
|
||||||
|
{
|
||||||
|
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
||||||
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
|
AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5));
|
||||||
|
|
||||||
|
AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5)));
|
||||||
|
AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5");
|
||||||
|
|
||||||
|
AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple);
|
||||||
|
|
||||||
|
AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
|
||||||
|
AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeNumerator()
|
||||||
|
{
|
||||||
|
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
||||||
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
|
AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
|
||||||
|
|
||||||
|
AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7");
|
||||||
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
|
AddStep("drop focus", () => InputManager.ChangeFocus(null));
|
||||||
|
AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInvalidChangeRollbackOnCommit()
|
||||||
|
{
|
||||||
|
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
||||||
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
|
AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
|
||||||
|
|
||||||
|
AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0");
|
||||||
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
|
AddStep("drop focus", () => InputManager.ChangeFocus(null));
|
||||||
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -47,25 +47,25 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
||||||
{
|
{
|
||||||
new HitCircle { StartTime = 100 },
|
new HitCircle { StartTime = 500 },
|
||||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
new HitCircle { StartTime = 1000, Position = new Vector2(100) },
|
||||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
new HitCircle { StartTime = 1500, Position = new Vector2(200) },
|
||||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
new HitCircle { StartTime = 2000, Position = new Vector2(300) },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
|
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
|
||||||
|
|
||||||
AddStep("nudge forwards", () => InputManager.Key(Key.K));
|
AddStep("nudge forwards", () => InputManager.Key(Key.K));
|
||||||
AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100);
|
AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 500);
|
||||||
|
|
||||||
AddStep("nudge backwards", () => InputManager.Key(Key.J));
|
AddStep("nudge backwards", () => InputManager.Key(Key.J));
|
||||||
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
|
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicSelect()
|
public void TestBasicSelect()
|
||||||
{
|
{
|
||||||
var addedObject = new HitCircle { StartTime = 100 };
|
var addedObject = new HitCircle { StartTime = 500 };
|
||||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
|
||||||
moveMouseToObject(() => addedObject);
|
moveMouseToObject(() => addedObject);
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
var addedObject2 = new HitCircle
|
var addedObject2 = new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = 200,
|
StartTime = 1000,
|
||||||
Position = new Vector2(100),
|
Position = new Vector2(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,10 +92,10 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
var addedObjects = new[]
|
var addedObjects = new[]
|
||||||
{
|
{
|
||||||
new HitCircle { StartTime = 100 },
|
new HitCircle { StartTime = 500 },
|
||||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
new HitCircle { StartTime = 1000, Position = new Vector2(100) },
|
||||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
new HitCircle { StartTime = 1500, Position = new Vector2(200) },
|
||||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
new HitCircle { StartTime = 2000, Position = new Vector2(300) },
|
||||||
};
|
};
|
||||||
|
|
||||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBasicDeselect()
|
public void TestBasicDeselect()
|
||||||
{
|
{
|
||||||
var addedObject = new HitCircle { StartTime = 100 };
|
var addedObject = new HitCircle { StartTime = 500 };
|
||||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
|
|
||||||
moveMouseToObject(() => addedObject);
|
moveMouseToObject(() => addedObject);
|
||||||
@ -166,11 +166,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
var addedObjects = new[]
|
var addedObjects = new[]
|
||||||
{
|
{
|
||||||
new HitCircle { StartTime = 100 },
|
new HitCircle { StartTime = 500 },
|
||||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
new HitCircle { StartTime = 1000, Position = new Vector2(100) },
|
||||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
new HitCircle { StartTime = 1500, Position = new Vector2(200) },
|
||||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
new HitCircle { StartTime = 2000, Position = new Vector2(300) },
|
||||||
new HitCircle { StartTime = 500, Position = new Vector2(400) },
|
new HitCircle { StartTime = 2500, Position = new Vector2(400) },
|
||||||
};
|
};
|
||||||
|
|
||||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
@ -236,10 +236,10 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
var addedObjects = new[]
|
var addedObjects = new[]
|
||||||
{
|
{
|
||||||
new HitCircle { StartTime = 100 },
|
new HitCircle { StartTime = 500 },
|
||||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
new HitCircle { StartTime = 1000, Position = new Vector2(100) },
|
||||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
new HitCircle { StartTime = 1500, Position = new Vector2(200) },
|
||||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
new HitCircle { StartTime = 2000, Position = new Vector2(300) },
|
||||||
};
|
};
|
||||||
|
|
||||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -19,28 +19,35 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
|
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
private HoldForMenuButton holdForMenuButton;
|
||||||
private void load()
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
HoldForMenuButton holdForMenuButton;
|
AddStep("create button", () =>
|
||||||
|
|
||||||
Add(holdForMenuButton = new HoldForMenuButton
|
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomRight,
|
exitAction = false;
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Action = () => exitAction = true
|
Child = holdForMenuButton = new HoldForMenuButton
|
||||||
|
{
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Action = () => exitAction = true
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var text = holdForMenuButton.Children.OfType<SpriteText>().First();
|
[Test]
|
||||||
|
public void TestMovementAndTrigger()
|
||||||
|
{
|
||||||
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton));
|
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton));
|
||||||
AddUntilStep("Text visible", () => text.IsPresent && !exitAction);
|
AddUntilStep("Text visible", () => getSpriteText().IsPresent && !exitAction);
|
||||||
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
|
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
|
||||||
AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction);
|
AddUntilStep("Text is not visible", () => !getSpriteText().IsPresent && !exitAction);
|
||||||
|
|
||||||
AddStep("Trigger exit action", () =>
|
AddStep("Trigger exit action", () =>
|
||||||
{
|
{
|
||||||
exitAction = false;
|
|
||||||
InputManager.MoveMouseTo(holdForMenuButton);
|
InputManager.MoveMouseTo(holdForMenuButton);
|
||||||
InputManager.PressButton(MouseButton.Left);
|
InputManager.PressButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
@ -50,6 +57,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
|
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
|
||||||
AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction);
|
AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction);
|
||||||
|
AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFadeOnNoInput()
|
||||||
|
{
|
||||||
|
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||||
|
AddUntilStep("wait for text fade out", () => !getSpriteText().IsPresent);
|
||||||
|
AddUntilStep("wait for button fade out", () => holdForMenuButton.Alpha < 0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpriteText getSpriteText() => holdForMenuButton.Children.OfType<SpriteText>().First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user