1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-18 02:43:22 +08:00

Merge master

This commit is contained in:
apollo-dw 2021-12-29 18:12:30 +00:00
commit d51456741c
281 changed files with 5586 additions and 2804 deletions

View File

@ -1,6 +1,6 @@
# Contributing Guidelines
Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
Thank you for showing interest in the development of osu!. We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner.
@ -32,7 +32,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in
* **Provide more information when asked to do so.**
Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local osu! database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
* **When submitting a feature proposal, please describe it in the most understandable way you can.**
@ -54,7 +54,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in
We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label.
However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
However, do keep in mind that the core team is committed to bringing osu!(lazer) up to par with osu!(stable) first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
Here are some key things to note before jumping in:
@ -128,7 +128,7 @@ Here are some key things to note before jumping in:
* **Don't mistake criticism of code for criticism of your person.**
As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
As mentioned before, we are highly committed to quality when it comes to the osu! project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
* **Feel free to reach out for help.**

View File

@ -11,7 +11,7 @@
A free-to-win rhythm game. Rhythm is just a *click* away!
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge.
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
## Status
@ -48,7 +48,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir
Please make sure you have the following prerequisites:
- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed.
- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) installed.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.

View File

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

View File

@ -17,7 +17,7 @@ using osu.Game.Database;
namespace osu.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser)]
[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 = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
@ -47,11 +47,6 @@ namespace osu.Android
protected override void OnCreate(Bundle savedInstanceState)
{
// The default current directory on android is '/'.
// On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
// In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
base.OnCreate(savedInstanceState);
// OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack.

View File

@ -1,11 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" package="sh.ppy.osulazer" android:installLocation="auto" android:versionName="0.1.0">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
</manifest>

View File

@ -0,0 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Android;
using Android.App;
// used for AndroidBatteryInfo
[assembly: UsesPermission(Manifest.Permission.BatteryStats)]

View File

@ -29,6 +29,7 @@
<Compile Include="GameplayScreenRotationLocker.cs" />
<Compile Include="OsuGameActivity.cs" />
<Compile Include="OsuGameAndroid.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />

View File

@ -90,7 +90,6 @@ namespace osu.Desktop
Logger.Log("Starting legacy IPC provider...");
legacyIpc = new LegacyTcpIpcProvider();
legacyIpc.Bind();
legacyIpc.StartAsync();
}
catch (Exception ex)
{

View File

@ -76,7 +76,7 @@ namespace osu.Desktop.Security
private void load(OsuColour colours, NotificationOverlay notificationOverlay)
{
Icon = FontAwesome.Solid.ShieldAlt;
IconBackgound.Colour = colours.YellowDark;
IconBackground.Colour = colours.YellowDark;
}
}
}

View File

@ -14,6 +14,7 @@ namespace osu.Desktop.Windows
{
private Bindable<bool> disableWinKey;
private IBindable<bool> localUserPlaying;
private IBindable<bool> isActive;
[Resolved]
private GameHost host { get; set; }
@ -24,13 +25,16 @@ namespace osu.Desktop.Windows
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking());
isActive = host.IsActive.GetBoundCopy();
isActive.BindValueChanged(_ => updateBlocking());
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
}
private void updateBlocking()
{
bool shouldDisable = disableWinKey.Value && localUserPlaying.Value;
bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value;
if (shouldDisable)
host.InputThread.Scheduler.Add(WindowsKey.Disable);

View File

@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Catch.Tests.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();

View File

@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
[TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })]
[TestCase("basic-hyperdash")]
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
@ -70,6 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests
HitObject = hitObject;
startTime = 0;
position = 0;
hyperDash = false;
}
private double startTime;
@ -88,8 +90,17 @@ namespace osu.Game.Rulesets.Catch.Tests
set => position = value;
}
private bool hyperDash;
public bool HyperDash
{
get => (HitObject as PalpableCatchHitObject)?.HyperDash ?? hyperDash;
set => hyperDash = value;
}
public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(Position, other.Position, conversion_lenience);
&& Precision.AlmostEquals(Position, other.Position, conversion_lenience)
&& HyperDash == other.HyperDash;
}
}

View File

@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Tests
}
[Test]
public void TestJuicestream()
public void TestJuiceStream()
{
AddStep("hit juicestream", () => spawnJuiceStream(true));
AddUntilStep("wait for completion", () => playfieldIsEmpty);

View File

@ -37,20 +37,20 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("show hyperdash droplet", () => SetContents(_ => createDrawableDroplet(true)));
}
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperDash = false) =>
new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
{
IndexInBeatmap = indexInBeatmap,
HyperDashBindable = { Value = hyperdash }
HyperDashBindable = { Value = hyperDash }
}));
private Drawable createDrawableBanana() =>
new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
private Drawable createDrawableDroplet(bool hyperdash = false) =>
private Drawable createDrawableDroplet(bool hyperDash = false) =>
new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
{
HyperDashBindable = { Value = hyperdash }
HyperDashBindable = { Value = hyperDash }
}));
private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));

View File

@ -0,0 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchPerformanceAttributes : PerformanceAttributes
{
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
@ -44,15 +44,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
int numTotalHits = totalComboHits();
// Longer maps are worth more
double lengthBonus =
0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
// Longer maps are worth more
value *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
value *= Math.Pow(0.97, misses);
// Combo scaling
@ -80,17 +76,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty
}
if (mods.Any(m => m is ModFlashlight))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
value *= 1.35 * lengthBonus;
// Scale the aim value with accuracy _slightly_
value *= Math.Pow(accuracy(), 5.5);
// Custom multipliers for NoFail. SpunOut is not applicable.
if (mods.Any(m => m is ModNoFail))
value *= 0.90;
return value;
return new CatchPerformanceAttributes
{
Total = value
};
}
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);

View File

@ -0,0 +1,19 @@
{
"Mappings": [{
"StartTime": 369,
"Objects": [{
"StartTime": 369,
"Position": 0,
"HyperDash": true
}]
},
{
"StartTime": 450,
"Objects": [{
"StartTime": 450,
"Position": 512,
"HyperDash": false
}]
}
]
}

View File

@ -0,0 +1,21 @@
osu file format v14
[General]
StackLeniency: 0.7
Mode: 2
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:9.6
ApproachRate:9.6
SliderMultiplier:1.9
SliderTickRate:1
[TimingPoints]
2169,266.666666666667,4,2,1,70,1,0
[HitObjects]
0,192,369,1,0,0:0:0:0:
512,192,450,1,0,0:0:0:0:

View File

@ -3,147 +3,183 @@
"StartTime": 369,
"Objects": [{
"StartTime": 369,
"Position": 177
"Position": 177,
"HyperDash": false
},
{
"StartTime": 450,
"Position": 216.539276
"Position": 216.539276,
"HyperDash": false
},
{
"StartTime": 532,
"Position": 256.5667
"Position": 256.5667,
"HyperDash": false
},
{
"StartTime": 614,
"Position": 296.594116
"Position": 296.594116,
"HyperDash": false
},
{
"StartTime": 696,
"Position": 336.621521
"Position": 336.621521,
"HyperDash": false
},
{
"StartTime": 778,
"Position": 376.99762
"Position": 376.99762,
"HyperDash": false
},
{
"StartTime": 860,
"Position": 337.318878
"Position": 337.318878,
"HyperDash": false
},
{
"StartTime": 942,
"Position": 297.291443
"Position": 297.291443,
"HyperDash": false
},
{
"StartTime": 1024,
"Position": 257.264038
"Position": 257.264038,
"HyperDash": false
},
{
"StartTime": 1106,
"Position": 217.2366
"Position": 217.2366,
"HyperDash": false
},
{
"StartTime": 1188,
"Position": 177
"Position": 177,
"HyperDash": false
},
{
"StartTime": 1270,
"Position": 216.818192
"Position": 216.818192,
"HyperDash": false
},
{
"StartTime": 1352,
"Position": 256.8456
"Position": 256.8456,
"HyperDash": false
},
{
"StartTime": 1434,
"Position": 296.873047
"Position": 296.873047,
"HyperDash": false
},
{
"StartTime": 1516,
"Position": 336.900452
"Position": 336.900452,
"HyperDash": false
},
{
"StartTime": 1598,
"Position": 376.99762
"Position": 376.99762,
"HyperDash": false
},
{
"StartTime": 1680,
"Position": 337.039948
"Position": 337.039948,
"HyperDash": false
},
{
"StartTime": 1762,
"Position": 297.0125
"Position": 297.0125,
"HyperDash": false
},
{
"StartTime": 1844,
"Position": 256.9851
"Position": 256.9851,
"HyperDash": false
},
{
"StartTime": 1926,
"Position": 216.957672
"Position": 216.957672,
"HyperDash": false
},
{
"StartTime": 2008,
"Position": 177
"Position": 177,
"HyperDash": false
},
{
"StartTime": 2090,
"Position": 217.097137
"Position": 217.097137,
"HyperDash": false
},
{
"StartTime": 2172,
"Position": 257.124573
"Position": 257.124573,
"HyperDash": false
},
{
"StartTime": 2254,
"Position": 297.152
"Position": 297.152,
"HyperDash": false
},
{
"StartTime": 2336,
"Position": 337.179443
"Position": 337.179443,
"HyperDash": false
},
{
"StartTime": 2418,
"Position": 376.99762
"Position": 376.99762,
"HyperDash": false
},
{
"StartTime": 2500,
"Position": 336.760956
"Position": 336.760956,
"HyperDash": false
},
{
"StartTime": 2582,
"Position": 296.733643
"Position": 296.733643,
"HyperDash": false
},
{
"StartTime": 2664,
"Position": 256.7062
"Position": 256.7062,
"HyperDash": false
},
{
"StartTime": 2746,
"Position": 216.678772
"Position": 216.678772,
"HyperDash": false
},
{
"StartTime": 2828,
"Position": 177
"Position": 177,
"HyperDash": false
},
{
"StartTime": 2909,
"Position": 216.887909
"Position": 216.887909,
"HyperDash": false
},
{
"StartTime": 2991,
"Position": 256.915344
"Position": 256.915344,
"HyperDash": false
},
{
"StartTime": 3073,
"Position": 296.942749
"Position": 296.942749,
"HyperDash": false
},
{
"StartTime": 3155,
"Position": 336.970184
"Position": 336.970184,
"HyperDash": false
},
{
"StartTime": 3237,
"Position": 376.99762
"Position": 376.99762,
"HyperDash": false
}
]
}]

View File

@ -3,71 +3,88 @@
"StartTime": 369,
"Objects": [{
"StartTime": 369,
"Position": 65
"Position": 65,
"HyperDash": false
},
{
"StartTime": 450,
"Position": 482
"Position": 482,
"HyperDash": false
},
{
"StartTime": 532,
"Position": 164
"Position": 164,
"HyperDash": false
},
{
"StartTime": 614,
"Position": 315
"Position": 315,
"HyperDash": false
},
{
"StartTime": 696,
"Position": 145
"Position": 145,
"HyperDash": false
},
{
"StartTime": 778,
"Position": 159
"Position": 159,
"HyperDash": false
},
{
"StartTime": 860,
"Position": 310
"Position": 310,
"HyperDash": false
},
{
"StartTime": 942,
"Position": 441
"Position": 441,
"HyperDash": false
},
{
"StartTime": 1024,
"Position": 428
"Position": 428,
"HyperDash": false
},
{
"StartTime": 1106,
"Position": 243
"Position": 243,
"HyperDash": false
},
{
"StartTime": 1188,
"Position": 422
"Position": 422,
"HyperDash": false
},
{
"StartTime": 1270,
"Position": 481
"Position": 481,
"HyperDash": false
},
{
"StartTime": 1352,
"Position": 104
"Position": 104,
"HyperDash": false
},
{
"StartTime": 1434,
"Position": 473
"Position": 473,
"HyperDash": false
},
{
"StartTime": 1516,
"Position": 135
"Position": 135,
"HyperDash": false
},
{
"StartTime": 1598,
"Position": 360
"Position": 360,
"HyperDash": false
},
{
"StartTime": 1680,
"Position": 123
"Position": 123,
"HyperDash": false
}
]
}]

View File

@ -3,231 +3,264 @@
"StartTime": 369,
"Objects": [{
"StartTime": 369,
"Position": 258
"Position": 258,
"HyperDash": false
}]
},
{
"StartTime": 450,
"Objects": [{
"StartTime": 450,
"Position": 254
"Position": 254,
"HyperDash": false
}]
},
{
"StartTime": 532,
"Objects": [{
"StartTime": 532,
"Position": 241
"Position": 241,
"HyperDash": false
}]
},
{
"StartTime": 614,
"Objects": [{
"StartTime": 614,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 696,
"Objects": [{
"StartTime": 696,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 778,
"Objects": [{
"StartTime": 778,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 860,
"Objects": [{
"StartTime": 860,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 942,
"Objects": [{
"StartTime": 942,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 1024,
"Objects": [{
"StartTime": 1024,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 1106,
"Objects": [{
"StartTime": 1106,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 1188,
"Objects": [{
"StartTime": 1188,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 1270,
"Objects": [{
"StartTime": 1270,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 1352,
"Objects": [{
"StartTime": 1352,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 1434,
"Objects": [{
"StartTime": 1434,
"Position": 258
"Position": 258,
"HyperDash": false
}]
},
{
"StartTime": 1516,
"Objects": [{
"StartTime": 1516,
"Position": 253
"Position": 253,
"HyperDash": false
}]
},
{
"StartTime": 1598,
"Objects": [{
"StartTime": 1598,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 1680,
"Objects": [{
"StartTime": 1680,
"Position": 260
"Position": 260,
"HyperDash": false
}]
},
{
"StartTime": 1762,
"Objects": [{
"StartTime": 1762,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 1844,
"Objects": [{
"StartTime": 1844,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 1926,
"Objects": [{
"StartTime": 1926,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 2008,
"Objects": [{
"StartTime": 2008,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 2090,
"Objects": [{
"StartTime": 2090,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 2172,
"Objects": [{
"StartTime": 2172,
"Position": 243
"Position": 243,
"HyperDash": false
}]
},
{
"StartTime": 2254,
"Objects": [{
"StartTime": 2254,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 2336,
"Objects": [{
"StartTime": 2336,
"Position": 278
"Position": 278,
"HyperDash": false
}]
},
{
"StartTime": 2418,
"Objects": [{
"StartTime": 2418,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 2500,
"Objects": [{
"StartTime": 2500,
"Position": 258
"Position": 258,
"HyperDash": false
}]
},
{
"StartTime": 2582,
"Objects": [{
"StartTime": 2582,
"Position": 256
"Position": 256,
"HyperDash": false
}]
},
{
"StartTime": 2664,
"Objects": [{
"StartTime": 2664,
"Position": 242
"Position": 242,
"HyperDash": false
}]
},
{
"StartTime": 2746,
"Objects": [{
"StartTime": 2746,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 2828,
"Objects": [{
"StartTime": 2828,
"Position": 238
"Position": 238,
"HyperDash": false
}]
},
{
"StartTime": 2909,
"Objects": [{
"StartTime": 2909,
"Position": 271
"Position": 271,
"HyperDash": false
}]
},
{
"StartTime": 2991,
"Objects": [{
"StartTime": 2991,
"Position": 254
"Position": 254,
"HyperDash": false
}]
}
]

View File

@ -3,14 +3,16 @@
"StartTime": 3368,
"Objects": [{
"StartTime": 3368,
"Position": 374
"Position": 374,
"HyperDash": false
}]
},
{
"StartTime": 3501,
"Objects": [{
"StartTime": 3501,
"Position": 446
"Position": 446,
"HyperDash": false
}]
}
]

View File

@ -1 +1,71 @@
{"Mappings":[{"StartTime":19184.0,"Objects":[{"StartTime":19184.0,"Position":320.0},{"StartTime":19263.0,"Position":311.730255},{"StartTime":19343.0,"Position":324.6205},{"StartTime":19423.0,"Position":343.0907},{"StartTime":19503.0,"Position":372.2917},{"StartTime":19582.0,"Position":385.194733},{"StartTime":19662.0,"Position":379.0426},{"StartTime":19742.0,"Position":385.1066},{"StartTime":19822.0,"Position":391.624664},{"StartTime":19919.0,"Position":386.27832},{"StartTime":20016.0,"Position":380.117035},{"StartTime":20113.0,"Position":381.664154},{"StartTime":20247.0,"Position":370.872864}]}]}
{
"Mappings": [{
"StartTime": 19184,
"Objects": [{
"StartTime": 19184,
"Position": 320,
"HyperDash": false
},
{
"StartTime": 19263,
"Position": 311.730255,
"HyperDash": false
},
{
"StartTime": 19343,
"Position": 324.6205,
"HyperDash": false
},
{
"StartTime": 19423,
"Position": 343.0907,
"HyperDash": false
},
{
"StartTime": 19503,
"Position": 372.2917,
"HyperDash": false
},
{
"StartTime": 19582,
"Position": 385.194733,
"HyperDash": false
},
{
"StartTime": 19662,
"Position": 379.0426,
"HyperDash": false
},
{
"StartTime": 19742,
"Position": 385.1066,
"HyperDash": false
},
{
"StartTime": 19822,
"Position": 391.624664,
"HyperDash": false
},
{
"StartTime": 19919,
"Position": 386.27832,
"HyperDash": false
},
{
"StartTime": 20016,
"Position": 380.117035,
"HyperDash": false
},
{
"StartTime": 20113,
"Position": 381.664154,
"HyperDash": false
},
{
"StartTime": 20247,
"Position": 370.872864,
"HyperDash": false
}
]
}]
}

View File

@ -3,18 +3,21 @@
"StartTime": 2589,
"Objects": [{
"StartTime": 2589,
"Position": 256
"Position": 256,
"HyperDash": false
}]
},
{
"StartTime": 2915,
"Objects": [{
"StartTime": 2915,
"Position": 65
"Position": 65,
"HyperDash": false
},
{
"StartTime": 2916,
"Position": 482
"Position": 482,
"HyperDash": false
}
]
},
@ -22,11 +25,13 @@
"StartTime": 3078,
"Objects": [{
"StartTime": 3078,
"Position": 164
"Position": 164,
"HyperDash": false
},
{
"StartTime": 3079,
"Position": 315
"Position": 315,
"HyperDash": false
}
]
},
@ -34,11 +39,13 @@
"StartTime": 3241,
"Objects": [{
"StartTime": 3241,
"Position": 145
"Position": 145,
"HyperDash": false
},
{
"StartTime": 3242,
"Position": 159
"Position": 159,
"HyperDash": false
}
]
},
@ -46,11 +53,13 @@
"StartTime": 3404,
"Objects": [{
"StartTime": 3404,
"Position": 310
"Position": 310,
"HyperDash": false
},
{
"StartTime": 3405,
"Position": 441
"Position": 441,
"HyperDash": false
}
]
},
@ -58,7 +67,8 @@
"StartTime": 5197,
"Objects": [{
"StartTime": 5197,
"Position": 256
"Position": 256,
"HyperDash": false
}]
}
]

View File

@ -3,71 +3,88 @@
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
"Position": 65
"Position": 65,
"HyperDash": false
},
{
"StartTime": 18559,
"Position": 482
"Position": 482,
"HyperDash": false
},
{
"StartTime": 18618,
"Position": 164
"Position": 164,
"HyperDash": false
},
{
"StartTime": 18678,
"Position": 315
"Position": 315,
"HyperDash": false
},
{
"StartTime": 18737,
"Position": 145
"Position": 145,
"HyperDash": false
},
{
"StartTime": 18796,
"Position": 159
"Position": 159,
"HyperDash": false
},
{
"StartTime": 18856,
"Position": 310
"Position": 310,
"HyperDash": false
},
{
"StartTime": 18915,
"Position": 441
"Position": 441,
"HyperDash": false
},
{
"StartTime": 18975,
"Position": 428
"Position": 428,
"HyperDash": false
},
{
"StartTime": 19034,
"Position": 243
"Position": 243,
"HyperDash": false
},
{
"StartTime": 19093,
"Position": 422
"Position": 422,
"HyperDash": false
},
{
"StartTime": 19153,
"Position": 481
"Position": 481,
"HyperDash": false
},
{
"StartTime": 19212,
"Position": 104
"Position": 104,
"HyperDash": false
},
{
"StartTime": 19271,
"Position": 473
"Position": 473,
"HyperDash": false
},
{
"StartTime": 19331,
"Position": 135
"Position": 135,
"HyperDash": false
},
{
"StartTime": 19390,
"Position": 360
"Position": 360,
"HyperDash": false
},
{
"StartTime": 19450,
"Position": 123
"Position": 123,
"HyperDash": false
}
]
}]

View File

@ -90,9 +90,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
.FadeOut(duration * 2);
const float angle_variangle = 15; // should be less than 45
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
const float angle_variance = 15; // should be less than 45
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variance, angle_variance, randomSeed, 4);
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variance, angle_variance, randomSeed, 5);
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
}

View File

@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Mania.Tests.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();

View File

@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
@ -24,9 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
}
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
private void load()
{
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
}
}

View File

@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Test]
public void TestDefaultSkin()
{
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLive());
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged());
}
[Test]
public void TestLegacySkin()
{
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLive());
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged());
}
}
}

View File

@ -24,13 +24,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
public TestSceneHitExplosion()
{
int runcount = 0;
int runCount = 0;
AddRepeatStep("explode", () =>
{
runcount++;
runCount++;
if (runcount % 15 > 12)
if (runCount % 15 > 12)
return;
int poolIndex = 0;
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(hitExplosionPools[poolIndex].Get(e =>
{
e.Apply(new JudgementResult(new HitObject(), runcount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
e.Apply(new JudgementResult(new HitObject(), runCount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
e.Anchor = Anchor.Centre;
e.Origin = Anchor.Centre;

View File

@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -14,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -24,9 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture]
public class TestSceneTimingBasedNoteColouring : OsuTestScene
{
[Resolved]
private RulesetConfigCache configCache { get; set; }
private Bindable<bool> configTimingBasedNoteColouring;
private ManualClock clock;
@ -48,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests
});
AddStep("retrieve config bindable", () =>
{
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
configTimingBasedNoteColouring = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring);
});
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public override IEnumerable<BeatmapStatistic> GetStatistics()
{
int notes = HitObjects.Count(s => s is Note);
int holdnotes = HitObjects.Count(s => s is HoldNote);
int holdNotes = HitObjects.Count(s => s is HoldNote);
return new[]
{
@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
Name = @"Hold Note Count",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
Content = holdnotes.ToString(),
Content = holdNotes.ToString(),
},
};
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
yield return v;
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
}
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
base.FromDatabaseAttributes(values);
StarRating = values[ATTRIB_ID_STRAIN];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
}

View File

@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceAttributes : PerformanceAttributes
{
[JsonProperty("difficulty")]
public double Difficulty { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
[JsonProperty("scaled_score")]
public double ScaledScore { get; set; }
}
}

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
@ -61,48 +61,46 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (mods.Any(m => m is ModEasy))
multiplier *= 0.5;
double strainValue = computeStrainValue();
double accValue = computeAccuracyValue(strainValue);
double difficultyValue = computeDifficultyValue();
double accValue = computeAccuracyValue(difficultyValue);
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(difficultyValue, 1.1) +
Math.Pow(accValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
return new ManiaPerformanceAttributes
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accValue;
}
return totalValue;
Difficulty = difficultyValue,
Accuracy = accValue,
ScaledScore = scaledScore,
Total = totalValue
};
}
private double computeStrainValue()
private double computeDifficultyValue()
{
// Obtain strain difficulty
double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
// Longer maps are worth more
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
if (scaledScore <= 500000)
strainValue = 0;
difficultyValue = 0;
else if (scaledScore <= 600000)
strainValue *= (scaledScore - 500000) / 100000 * 0.3;
difficultyValue *= (scaledScore - 500000) / 100000 * 0.3;
else if (scaledScore <= 700000)
strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
else if (scaledScore <= 800000)
strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
else if (scaledScore <= 900000)
strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
else
strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
return strainValue;
return difficultyValue;
}
private double computeAccuracyValue(double strainValue)
private double computeAccuracyValue(double difficultyValue)
{
if (Attributes.GreatHitWindow <= 0)
return 0;
@ -110,12 +108,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue
* difficultyValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
// accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
return accuracyValue;
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
/// Mapping of <see cref="HitResult"/> to their corresponding
/// <see cref="LegacyManiaSkinConfigurationLookups"/> value.
/// </summary>
private static readonly IReadOnlyDictionary<HitResult, LegacyManiaSkinConfigurationLookups> hitresult_mapping
private static readonly IReadOnlyDictionary<HitResult, LegacyManiaSkinConfigurationLookups> hit_result_mapping
= new Dictionary<HitResult, LegacyManiaSkinConfigurationLookups>
{
{ HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g },
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
/// Mapping of <see cref="HitResult"/> to their corresponding
/// default filenames.
/// </summary>
private static readonly IReadOnlyDictionary<HitResult, string> default_hitresult_skin_filenames
private static readonly IReadOnlyDictionary<HitResult, string> default_hit_result_skin_filenames
= new Dictionary<HitResult, string>
{
{ HitResult.Perfect, "mania-hit300g" },
@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private Drawable getResult(HitResult result)
{
if (!hitresult_mapping.ContainsKey(result))
if (!hit_result_mapping.ContainsKey(result))
return null;
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
?? default_hitresult_skin_filenames[result];
string filename = this.GetManiaSkinConfig<string>(hit_result_mapping[result])?.Value
?? default_hit_result_skin_filenames[result];
var animation = this.GetAnimation(filename, true, true);
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
const float angle_variangle = 15; // should be less than 45
const float angle_variance = 15; // should be less than 45
const float roundness = 80;
const float initial_height = 10;
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
Rotation = RNG.NextSingle(-angle_variance, angle_variance),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true,
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
Rotation = RNG.NextSingle(-angle_variance, angle_variance),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,

View File

@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline));
public void Add(BarLine barLine) => stages.ForEach(s => s.Add(barLine));
/// <summary>
/// Retrieves a column from a screen-space position.

View File

@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
public void Add(BarLine barLine) => base.Add(new DrawableBarLine(barLine));
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{

View File

@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Osu.Tests.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();

View File

@ -1,7 +1,10 @@
// 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 Humanizer;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -47,6 +50,126 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddBlueprint(new TestSliderBlueprint(slider), drawableObject);
});
[Test]
public void TestSelection()
{
moveMouseToControlPoint(0);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
assertSelectionCount(1);
assertSelected(0);
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
assertSelectionCount(1);
assertSelected(0);
moveMouseToControlPoint(3);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
assertSelectionCount(1);
assertSelected(3);
AddStep("press control", () => InputManager.PressKey(Key.ControlLeft));
moveMouseToControlPoint(2);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
assertSelectionCount(2);
assertSelected(2);
assertSelected(3);
moveMouseToControlPoint(0);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
assertSelectionCount(3);
assertSelected(0);
assertSelected(2);
assertSelected(3);
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
assertSelectionCount(3);
assertSelected(0);
assertSelected(2);
assertSelected(3);
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
assertSelectionCount(1);
assertSelected(0);
moveMouseToRelativePosition(new Vector2(350, 0));
AddStep("ctrl+click to create new point", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressButton(MouseButton.Left);
});
assertSelectionCount(1);
assertSelected(3);
AddStep("release ctrl+click", () =>
{
InputManager.ReleaseButton(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
assertSelectionCount(1);
assertSelected(3);
}
[Test]
public void TestNewControlPointCreation()
{
moveMouseToRelativePosition(new Vector2(350, 0));
AddStep("ctrl+click to create new point", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressButton(MouseButton.Left);
});
AddAssert("slider has 6 control points", () => slider.Path.ControlPoints.Count == 6);
AddStep("release ctrl+click", () =>
{
InputManager.ReleaseButton(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
// ensure that the next drag doesn't attempt to move the placement that just finished.
moveMouseToRelativePosition(new Vector2(0, 50));
AddStep("press left mouse", () => InputManager.PressButton(MouseButton.Left));
moveMouseToRelativePosition(new Vector2(0, 100));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(3, new Vector2(350, 0));
moveMouseToRelativePosition(new Vector2(400, 75));
AddStep("ctrl+click to create new point", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressButton(MouseButton.Left);
});
AddAssert("slider has 7 control points", () => slider.Path.ControlPoints.Count == 7);
moveMouseToRelativePosition(new Vector2(350, 75));
AddStep("release ctrl+click", () =>
{
InputManager.ReleaseButton(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
assertControlPointPosition(5, new Vector2(350, 75));
// ensure that the next drag doesn't attempt to move the placement that just finished.
moveMouseToRelativePosition(new Vector2(0, 50));
AddStep("press left mouse", () => InputManager.PressButton(MouseButton.Left));
moveMouseToRelativePosition(new Vector2(0, 100));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(5, new Vector2(350, 75));
}
private void assertSelectionCount(int count) =>
AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == count);
private void assertSelected(int index) =>
AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected",
() => this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
private void moveMouseToRelativePosition(Vector2 relativePosition) =>
AddStep($"move mouse to {relativePosition}", () =>
{
Vector2 position = slider.Position + relativePosition;
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
});
[Test]
public void TestDragControlPoint()
{
@ -60,6 +183,83 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointType(0, PathType.PerfectCurve);
}
[Test]
public void TestDragMultipleControlPoints()
{
moveMouseToControlPoint(2);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
AddStep("hold control", () => InputManager.PressKey(Key.LControl));
moveMouseToControlPoint(3);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
moveMouseToControlPoint(4);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
moveMouseToControlPoint(2);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
addMovementStep(new Vector2(450, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
assertControlPointPosition(2, new Vector2(450, 50));
assertControlPointType(2, PathType.PerfectCurve);
assertControlPointPosition(3, new Vector2(550, 50));
assertControlPointPosition(4, new Vector2(550, 200));
AddStep("release control", () => InputManager.ReleaseKey(Key.LControl));
}
[Test]
public void TestDragMultipleControlPointsIncludingHead()
{
moveMouseToControlPoint(0);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
AddStep("hold control", () => InputManager.PressKey(Key.LControl));
moveMouseToControlPoint(3);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
moveMouseToControlPoint(4);
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
moveMouseToControlPoint(3);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
addMovementStep(new Vector2(550, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
// note: if the head is part of the selection being moved, the entire slider is moved.
// the unselected nodes will therefore change position relative to the slider head.
AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50)));
assertControlPointPosition(0, Vector2.Zero);
assertControlPointType(0, PathType.PerfectCurve);
assertControlPointPosition(1, new Vector2(0, 100));
assertControlPointPosition(2, new Vector2(150, -50));
assertControlPointPosition(3, new Vector2(400, 0));
assertControlPointPosition(4, new Vector2(400, 150));
AddStep("release control", () => InputManager.ReleaseKey(Key.LControl));
}
[Test]
public void TestDragControlPointAlmostLinearlyExterior()
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -9,6 +9,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
@ -46,9 +47,9 @@ namespace osu.Game.Rulesets.Osu.Tests
=> new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
private void load()
{
var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
}

View File

@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("aim_strain")]
public double AimStrain { get; set; }
[JsonProperty("aim_difficulty")]
public double AimDifficulty { get; set; }
[JsonProperty("speed_strain")]
public double SpeedStrain { get; set; }
[JsonProperty("speed_difficulty")]
public double SpeedDifficulty { get; set; }
[JsonProperty("flashlight_rating")]
public double FlashlightRating { get; set; }
[JsonProperty("flashlight_difficulty")]
public double FlashlightDifficulty { get; set; }
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
@ -49,15 +49,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
foreach (var v in base.ToDatabaseAttributes())
yield return v;
yield return (ATTRIB_ID_AIM, AimStrain);
yield return (ATTRIB_ID_SPEED, SpeedStrain);
yield return (ATTRIB_ID_AIM, AimDifficulty);
yield return (ATTRIB_ID_SPEED, SpeedDifficulty);
yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
if (ShouldSerializeFlashlightRating())
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightRating);
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
}
@ -66,18 +66,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
base.FromDatabaseAttributes(values);
AimStrain = values[ATTRIB_ID_AIM];
SpeedStrain = values[ATTRIB_ID_SPEED];
AimDifficulty = values[ATTRIB_ID_AIM];
SpeedDifficulty = values[ATTRIB_ID_SPEED];
OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_STRAIN];
FlashlightRating = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
StarRating = values[ATTRIB_ID_DIFFICULTY];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
}
// Used implicitly by Newtonsoft.Json to not serialize flashlight property in some cases.
#region Newtonsoft.Json implicit ShouldSerialize() methods
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
// They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed
// unless the fields are also renamed.
[UsedImplicitly]
public bool ShouldSerializeFlashlightRating() => Mods.Any(m => m is ModFlashlight);
#endregion
}
}

View File

@ -81,9 +81,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
StarRating = starRating,
Mods = mods,
AimStrain = aimRating,
SpeedStrain = speedRating,
FlashlightRating = flashlightRating,
AimDifficulty = aimRating,
SpeedDifficulty = speedRating,
FlashlightDifficulty = flashlightRating,
SliderFactor = sliderFactor,
AimDifficultStrainCount = aimDifficultyStrainCount,
SpeedDifficultStrainCount = speedDifficultyStrainCount,

View File

@ -0,0 +1,26 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuPerformanceAttributes : PerformanceAttributes
{
[JsonProperty("aim")]
public double Aim { get; set; }
[JsonProperty("speed")]
public double Speed { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
[JsonProperty("flashlight")]
public double Flashlight { get; set; }
[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }
}
}

View File

@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using MathNet.Numerics;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@ -33,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
}
public override double Calculate(Dictionary<string, double> categoryRatings = null)
public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
accuracy = Score.Accuracy;
@ -46,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
// Custom multipliers for NoFail and SpunOut.
if (mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
@ -73,37 +71,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryRatings != null)
return new OsuPerformanceAttributes
{
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("Flashlight", flashlightValue);
categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", Attributes.MaxCombo);
}
return totalValue;
Aim = aimValue,
Speed = speedValue,
Accuracy = accuracyValue,
Flashlight = flashlightValue,
EffectiveMissCount = effectiveMissCount,
Total = totalValue
};
}
private double computeAimValue()
{
double rawAim = Attributes.AimStrain;
double rawAim = Attributes.AimDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
rawAim = Math.Pow(rawAim, 0.8);
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount);
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
aimValue *= getComboScalingFactor();
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@ -132,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
}
aimValue *= accuracy;
// It is important to also consider accuracy difficulty when doing that.
// It is important to consider accuracy difficulty when scaling with accuracy.
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
@ -140,15 +136,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeSpeedValue()
{
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount);
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
speedValue *= getComboScalingFactor();
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@ -218,14 +216,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (!mods.Any(h => h is OsuModFlashlight))
return 0.0;
double rawFlashlight = Attributes.FlashlightRating;
double rawFlashlight = Attributes.FlashlightDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
// Add an additional bonus for HDFL.
if (mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
@ -233,9 +230,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
// Combo scaling.
if (Attributes.MaxCombo > 0)
flashlightValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
flashlightValue *= getComboScalingFactor();
// Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) +
@ -267,11 +262,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
private double calculateMissPenalty(double missCount, double strainCount)
{
return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
}
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
}
private double skillMultiplier => 0.15;
private double skillMultiplier => 0.07;
private double strainDecayBase => 0.15;
protected override double DecayWeight => 1.0;
protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
@ -40,26 +40,31 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double result = 0.0;
OsuDifficultyHitObject lastObj = osuCurrent;
// This is iterating backwards in time from the current object.
for (int i = 0; i < Previous.Count; i++)
{
var osuPrevious = (OsuDifficultyHitObject)Previous[i];
var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject);
var currentObj = (OsuDifficultyHitObject)Previous[i];
var currentHitObject = (OsuHitObject)(currentObj.BaseObject);
if (!(osuPrevious.BaseObject is Spinner))
if (!(currentObj.BaseObject is Spinner))
{
double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length;
double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length;
cumulativeStrainTime += osuPrevious.StrainTime;
cumulativeStrainTime += lastObj.StrainTime;
// We want to nerf objects that can be easily seen within the Flashlight circle radius.
if (i == 0)
smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
// We also want to nerf stacks so that only the first object of the stack is accounted for.
double stackNerf = Math.Min(1.0, (osuPrevious.LazyJumpDistance / scalingFactor) / 25.0);
double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0);
result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
}
lastObj = currentObj;
}
return Math.Pow(smallDistNerf * result, 2.0);

View File

@ -55,73 +55,75 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
bool firstDeltaSwitch = false;
for (int i = Previous.Count - 2; i > 0; i--)
int rhythmStart = 0;
while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max)
rhythmStart++;
for (int i = rhythmStart; i > 0; i--)
{
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1];
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i];
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1];
double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now
double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
if (currHistoricalDecay != 0)
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
double currDelta = currObj.StrainTime;
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
windowPenalty = Math.Min(1, windowPenalty);
double effectiveRatio = windowPenalty * currRatio;
if (firstDeltaSwitch)
{
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
double currDelta = currObj.StrainTime;
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
windowPenalty = Math.Min(1, windowPenalty);
double effectiveRatio = windowPenalty * currRatio;
if (firstDeltaSwitch)
if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
{
if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
{
if (islandSize < 7)
islandSize++; // island is still progressing, count size.
}
else
{
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
effectiveRatio *= 0.125;
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
effectiveRatio *= 0.25;
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
effectiveRatio *= 0.25;
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
effectiveRatio *= 0.50;
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
effectiveRatio *= 0.125;
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
startRatio = effectiveRatio;
previousIslandSize = islandSize; // log the last island size.
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
islandSize = 1;
}
if (islandSize < 7)
islandSize++; // island is still progressing, count size.
}
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
else
{
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
effectiveRatio *= 0.125;
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
effectiveRatio *= 0.25;
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
effectiveRatio *= 0.25;
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
effectiveRatio *= 0.50;
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
effectiveRatio *= 0.125;
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
startRatio = effectiveRatio;
previousIslandSize = islandSize; // log the last island size.
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
islandSize = 1;
}
}
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
{
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
startRatio = effectiveRatio;
islandSize = 1;
}
}
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)

View File

@ -16,11 +16,9 @@ using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@ -33,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
{
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
public Action<PathControlPoint> DragStarted;
public Action<DragEvent> DragInProgress;
public Action DragEnded;
public List<PathControlPoint> PointsInSegment;
public readonly BindableBool IsSelected = new BindableBool();
@ -42,12 +45,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private readonly Container marker;
private readonly Drawable markerRing;
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
[Resolved(CanBeNull = true)]
private IPositionSnapProvider snapProvider { get; set; }
[Resolved]
private OsuColour colours { get; set; }
@ -138,6 +135,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
updateMarkerDisplay();
}
// Used to pair up mouse down/drag events with their corresponding mouse up events,
// to avoid deselecting the piece by accident when the mouse up corresponding to the mouse down/drag fires.
private bool keepSelection;
protected override bool OnMouseDown(MouseDownEvent e)
{
if (RequestSelection == null)
@ -146,22 +147,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (e.Button)
{
case MouseButton.Left:
// if control is pressed, do not do anything as the user may be adding to current selection
// or dragging all currently selected control points.
// if it isn't and the user's intent is to deselect, deselection will happen on mouse up.
if (e.ControlPressed && IsSelected.Value)
return true;
RequestSelection.Invoke(this, e);
keepSelection = true;
return true;
case MouseButton.Right:
if (!IsSelected.Value)
RequestSelection.Invoke(this, e);
keepSelection = true;
return false; // Allow context menu to show
}
return false;
}
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
protected override void OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
private Vector2 dragStartPosition;
private PathType? dragPathType;
// ctrl+click deselects this piece, but only if this event
// wasn't immediately preceded by a matching mouse down or drag.
if (IsSelected.Value && e.ControlPressed && !keepSelection)
IsSelected.Value = false;
keepSelection = false;
}
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
protected override bool OnDragStart(DragStartEvent e)
{
@ -170,54 +190,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (e.Button == MouseButton.Left)
{
dragStartPosition = ControlPoint.Position;
dragPathType = PointsInSegment[0].Type;
changeHandler?.BeginChange();
DragStarted?.Invoke(ControlPoint);
keepSelection = true;
return true;
}
return false;
}
protected override void OnDrag(DragEvent e)
{
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
var oldPosition = slider.Position;
double oldStartTime = slider.StartTime;
protected override void OnDrag(DragEvent e) => DragInProgress?.Invoke(e);
if (ControlPoint == slider.Path.ControlPoints[0])
{
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition);
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position;
slider.Position += movementDelta;
slider.StartTime = result?.Time ?? slider.StartTime;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position -= movementDelta;
}
else
ControlPoint.Position = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
if (!slider.Path.HasValidLength)
{
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position = oldControlPoints[i];
slider.Position = oldPosition;
slider.StartTime = oldStartTime;
return;
}
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
PointsInSegment[0].Type = dragPathType;
}
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
@ -267,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (pathType)
{
case PathType.Catmull:
return colours.Seafoam;
return colours.SeaFoam;
case PathType.Bezier:
return colours.Pink;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
@ -16,6 +17,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
@ -40,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
[Resolved(CanBeNull = true)]
private IPositionSnapProvider snapProvider { get; set; }
public PathControlPointVisualiser(Slider slider, bool allowSelection)
{
this.slider = slider;
@ -64,6 +69,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
controlPoints.BindTo(slider.Path.ControlPoints);
}
/// <summary>
/// Selects the <see cref="PathControlPointPiece"/> corresponding to the given <paramref name="pathControlPoint"/>,
/// and deselects all other <see cref="PathControlPointPiece"/>s.
/// </summary>
public void SetSelectionTo(PathControlPoint pathControlPoint)
{
foreach (var p in Pieces)
p.IsSelected.Value = p.ControlPoint == pathControlPoint;
}
/// <summary>
/// Delete all visually selected <see cref="PathControlPoint"/>s.
/// </summary>
/// <returns></returns>
public bool DeleteSelected()
{
List<PathControlPoint> toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
// Ensure that there are any points to be deleted
if (toRemove.Count == 0)
return false;
changeHandler?.BeginChange();
RemoveControlPointsRequested?.Invoke(toRemove);
changeHandler?.EndChange();
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
foreach (var piece in Pieces)
piece.IsSelected.Value = false;
return true;
}
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@ -87,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
{
if (allowSelection)
d.RequestSelection = selectPiece;
d.RequestSelection = selectionRequested;
d.DragStarted = dragStarted;
d.DragInProgress = dragInProgress;
d.DragEnded = dragEnded;
}));
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
@ -119,6 +161,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnClick(ClickEvent e)
{
if (Pieces.Any(piece => piece.IsHovered))
return false;
foreach (var piece in Pieces)
{
piece.IsSelected.Value = false;
@ -142,15 +187,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
piece.IsSelected.Toggle();
else
{
foreach (var p in Pieces)
p.IsSelected.Value = p == piece;
}
SetSelectionTo(piece.ControlPoint);
}
/// <summary>
@ -184,25 +226,82 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
public bool DeleteSelected()
{
List<PathControlPoint> toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
#region Drag handling
// Ensure that there are any points to be deleted
if (toRemove.Count == 0)
return false;
private Vector2[] dragStartPositions;
private PathType?[] dragPathTypes;
private int draggedControlPointIndex;
private HashSet<PathControlPoint> selectedControlPoints;
private void dragStarted(PathControlPoint controlPoint)
{
dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray();
dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray();
draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint);
selectedControlPoints = new HashSet<PathControlPoint>(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));
Debug.Assert(draggedControlPointIndex >= 0);
changeHandler?.BeginChange();
RemoveControlPointsRequested?.Invoke(toRemove);
changeHandler?.EndChange();
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
foreach (var piece in Pieces)
piece.IsSelected.Value = false;
return true;
}
private void dragInProgress(DragEvent e)
{
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
var oldPosition = slider.Position;
double oldStartTime = slider.StartTime;
if (selectedControlPoints.Contains(slider.Path.ControlPoints[0]))
{
// Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account
Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
var result = snapProvider?.SnapScreenSpacePositionToValidTime(newHeadPosition);
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position;
slider.Position += movementDelta;
slider.StartTime = result?.Time ?? slider.StartTime;
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
{
var controlPoint = slider.Path.ControlPoints[i];
// Since control points are relative to the position of the slider, all points that are _not_ selected
// need to be offset _back_ by the delta corresponding to the movement of the head point.
// All other selected control points (if any) will move together with the head point
// (and so they will not move at all, relative to each other).
if (!selectedControlPoints.Contains(controlPoint))
controlPoint.Position -= movementDelta;
}
}
else
{
for (int i = 0; i < controlPoints.Count; ++i)
{
var controlPoint = controlPoints[i];
if (selectedControlPoints.Contains(controlPoint))
controlPoint.Position = dragStartPositions[i] + (e.MousePosition - e.MouseDownPosition);
}
}
if (!slider.Path.HasValidLength)
{
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position = oldControlPoints[i];
slider.Position = oldPosition;
slider.StartTime = oldStartTime;
return;
}
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Type = dragPathTypes[i];
}
private void dragEnded() => changeHandler?.EndChange();
#endregion
public MenuItem[] ContextMenuItems
{
get

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@ -140,7 +139,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
case MouseButton.Left:
if (e.ControlPressed && IsSelected)
{
placementControlPointIndex = addControlPoint(e.MousePosition);
changeHandler?.BeginChange();
placementControlPoint = addControlPoint(e.MousePosition);
ControlPointVisualiser?.SetSelectionTo(placementControlPoint);
return true; // Stop input from being handled and modifying the selection
}
@ -150,31 +151,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false;
}
private int? placementControlPointIndex;
[CanBeNull]
private PathControlPoint placementControlPoint;
protected override bool OnDragStart(DragStartEvent e)
{
if (placementControlPointIndex != null)
{
changeHandler?.BeginChange();
return true;
}
return false;
}
protected override bool OnDragStart(DragStartEvent e) => placementControlPoint != null;
protected override void OnDrag(DragEvent e)
{
Debug.Assert(placementControlPointIndex != null);
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position = e.MousePosition - HitObject.Position;
if (placementControlPoint != null)
placementControlPoint.Position = e.MousePosition - HitObject.Position;
}
protected override void OnDragEnd(DragEndEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
if (placementControlPointIndex != null)
if (placementControlPoint != null)
{
placementControlPointIndex = null;
placementControlPoint = null;
changeHandler?.EndChange();
}
}
@ -193,7 +185,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false;
}
private int addControlPoint(Vector2 position)
private PathControlPoint addControlPoint(Vector2 position)
{
position -= HitObject.Position;
@ -211,10 +203,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
}
// Move the control points from the insertion index onwards to make room for the insertion
controlPoints.Insert(insertionIndex, new PathControlPoint { Position = position });
var pathControlPoint = new PathControlPoint { Position = position };
return insertionIndex;
// Move the control points from the insertion index onwards to make room for the insertion
controlPoints.Insert(insertionIndex, pathControlPoint);
return pathControlPoint;
}
private void removeControlPoints(List<PathControlPoint> toRemove)

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
public override IconUsage? Icon => OsuIcon.ModSpunout;
public override IconUsage? Icon => OsuIcon.ModSpunOut;
public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;

View File

@ -3,8 +3,10 @@
using System.Collections.Generic;
using System.ComponentModel;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges.Events;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu
@ -39,6 +41,19 @@ namespace osu.Game.Rulesets.Osu
return base.Handle(e);
}
protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
{
if (!AllowUserCursorMovement)
{
// Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse.
// Primarily relied upon by the "autopilot" osu! mod.
var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position);
e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null);
}
return base.HandleMouseTouchStateChange(e);
}
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
public bool AllowUserPresses = true;

View File

@ -0,0 +1,50 @@
// 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.Audio.Track;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
internal class KiaiFlashingDrawable : BeatSyncedContainer
{
private readonly Drawable flashingDrawable;
private const float flash_opacity = 0.3f;
public KiaiFlashingDrawable(Func<Drawable> creationFunc)
{
AutoSizeAxes = Axes.Both;
Children = new[]
{
creationFunc.Invoke().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
}),
flashingDrawable = creationFunc.Invoke().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
d.Alpha = 0;
d.Blending = BlendingParameters.Additive;
})
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
if (!effectPoint.KiaiMode)
return;
flashingDrawable
.FadeTo(flash_opacity)
.Then()
.FadeOut(timingPoint.BeatLength * 0.75f);
}
}
}

View File

@ -1,61 +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 osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
internal class KiaiFlashingSprite : BeatSyncedContainer
{
private readonly Sprite mainSprite;
private readonly Sprite flashingSprite;
public Texture Texture
{
set
{
mainSprite.Texture = value;
flashingSprite.Texture = value;
}
}
private const float flash_opacity = 0.3f;
public KiaiFlashingSprite()
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
mainSprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
flashingSprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Blending = BlendingParameters.Additive,
}
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
if (!effectPoint.KiaiMode)
return;
flashingSprite
.FadeTo(flash_opacity)
.Then()
.FadeOut(timingPoint.BeatLength * 0.75f);
}
}
}

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -68,13 +69,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
Texture overlayTexture = getTextureWithFallback("overlay");
InternalChildren = new[]
{
hitCircleSprite = new KiaiFlashingSprite
hitCircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = baseTexture })
{
Texture = baseTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@ -82,9 +81,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = hitCircleOverlay = new KiaiFlashingSprite
Child = hitCircleOverlay = new KiaiFlashingDrawable(() => getAnimationWithFallback(@"overlay", 1000 / 2d))
{
Texture = overlayTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@ -126,6 +124,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return tex ?? skin.GetTexture($"hitcircle{name}");
}
Drawable getAnimationWithFallback(string name, double frameLength)
{
Drawable animation = null;
if (!string.IsNullOrEmpty(priorityLookup))
{
animation = skin.GetAnimation($"{priorityLookup}{name}", true, true, frameLength: frameLength);
if (!allowFallback)
return animation;
}
return animation ?? skin.GetAnimation($"hitcircle{name}", true, true, frameLength: frameLength);
}
}
protected override void LoadComplete()

View File

@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
using Android.Content.PM;
using osu.Framework.Android;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Taiko.Tests.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();

View File

@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
private BarLine createBarLineAtCurrentTime(bool major = false)
{
var barline = new BarLine
var barLine = new BarLine
{
Major = major,
StartTime = Time.Current + 2000,
@ -92,9 +92,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
var cpi = new ControlPointInfo();
cpi.Add(0, new TimingControlPoint { BeatLength = 500 });
barline.ApplyDefaults(cpi, new BeatmapDifficulty());
barLine.ApplyDefaults(cpi, new BeatmapDifficulty());
return barline;
return barLine;
}
}
}

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
public override IEnumerable<BeatmapStatistic> GetStatistics()
{
int hits = HitObjects.Count(s => s is Hit);
int drumrolls = HitObjects.Count(s => s is DrumRoll);
int drumRolls = HitObjects.Count(s => s is DrumRoll);
int swells = HitObjects.Count(s => s is Swell);
return new[]
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
Name = @"Drumroll Count",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
Content = drumrolls.ToString(),
Content = drumRolls.ToString(),
},
new BeatmapStatistic
{

View File

@ -9,14 +9,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("stamina_strain")]
public double StaminaStrain { get; set; }
[JsonProperty("stamina_difficulty")]
public double StaminaDifficulty { get; set; }
[JsonProperty("rhythm_strain")]
public double RhythmStrain { get; set; }
[JsonProperty("rhythm_difficulty")]
public double RhythmDifficulty { get; set; }
[JsonProperty("colour_strain")]
public double ColourStrain { get; set; }
[JsonProperty("colour_difficulty")]
public double ColourDifficulty { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
yield return v;
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
base.FromDatabaseAttributes(values);
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_STRAIN];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
}

View File

@ -91,9 +91,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
StarRating = starRating,
Mods = mods,
StaminaStrain = staminaRating,
RhythmStrain = rhythmRating,
ColourStrain = colourRating,
StaminaDifficulty = staminaRating,
RhythmDifficulty = rhythmRating,
ColourDifficulty = colourRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
};

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoPerformanceAttributes : PerformanceAttributes
{
[JsonProperty("difficulty")]
public double Difficulty { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
}
}

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
public override PerformanceAttributes Calculate()
{
mods = Score.Mods;
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (mods.Any(m => m is ModNoFail))
@ -44,43 +43,38 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (mods.Any(m => m is ModHidden))
multiplier *= 1.10;
double strainValue = computeStrainValue();
double difficultyValue = computeDifficultyValue();
double accuracyValue = computeAccuracyValue();
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(difficultyValue, 1.1) +
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
return new TaikoPerformanceAttributes
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accuracyValue;
}
return totalValue;
Difficulty = difficultyValue,
Accuracy = accuracyValue,
Total = totalValue
};
}
private double computeStrainValue()
private double computeDifficultyValue()
{
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
strainValue *= lengthBonus;
difficultyValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
strainValue *= Math.Pow(0.985, countMiss);
difficultyValue *= Math.Pow(0.985, countMiss);
if (mods.Any(m => m is ModHidden))
strainValue *= 1.025;
difficultyValue *= 1.025;
if (mods.Any(m => m is ModFlashlight<TaikoHitObject>))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
strainValue *= 1.05 * lengthBonus;
difficultyValue *= 1.05 * lengthBonus;
// Scale the speed value with accuracy _slightly_
return strainValue * Score.Accuracy;
return difficultyValue * Score.Accuracy;
}
private double computeAccuracyValue()
@ -88,11 +82,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (Attributes.GreatHitWindow <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
// Bonus for many objects - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
}

View File

@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using Android.App;
using Android.Content.PM;
using osu.Framework.Android;
namespace osu.Game.Tests.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
[Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)]
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();

View File

@ -40,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWhenClosed()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDelete()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -78,7 +78,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteFromStream()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -140,7 +140,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithReZip()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -192,7 +192,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithChangedHashedFile()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -247,7 +247,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Ignore("intentionally broken by import optimisations")]
public async Task TestImportThenImportWithChangedFile()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -298,7 +298,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithDifferentFilename()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -351,7 +351,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportCorruptThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -392,7 +392,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestModelCreationFailureDoesntReturn()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -428,7 +428,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestRollbackOnFailure()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -507,7 +507,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -534,7 +534,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -566,7 +566,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWithDuplicateBeatmapIDs()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -621,8 +621,8 @@ namespace osu.Game.Tests.Beatmaps.IO
[NonParallelizable]
public void TestImportOverIPC()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-host", true))
using (HeadlessGameHost client = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-client", true))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(true))
using (HeadlessGameHost client = new CleanRunHeadlessGameHost(true))
{
try
{
@ -651,7 +651,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWhenFileOpen()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -673,7 +673,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithDuplicateHashes()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -715,7 +715,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportNestedStructure()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -760,7 +760,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithIgnoredDirectoryInArchive()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -814,7 +814,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestUpdateBeatmapInfo()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -844,7 +844,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestUpdateBeatmapFile()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -888,7 +888,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public void TestSaveRemovesInvalidCharactersFromPath()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -922,7 +922,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestCreateNewEmptyBeatmap()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@ -949,7 +949,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public void TestCreateNewBeatmapWithObject()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{

View File

@ -128,8 +128,12 @@ namespace osu.Game.Tests.Collections.IO
[Test]
public async Task TestSaveAndReload()
{
using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload", bypassCleanup: true))
string firstRunName;
using (var host = new CleanRunHeadlessGameHost(bypassCleanup: true))
{
firstRunName = host.Name;
try
{
var osu = LoadOsuIntoHost(host, true);
@ -149,7 +153,8 @@ namespace osu.Game.Tests.Collections.IO
}
}
using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload"))
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
{
try
{

View File

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Models;
using Realms;
@ -21,14 +22,41 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive();
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive(realmFactory);
Assert.AreEqual(beatmap, beatmap2);
});
}
[Test]
public void TestAccessAfterStorageMigrate()
{
RunTestWithRealm((realmFactory, storage) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
ILive<RealmBeatmap> liveBeatmap;
using (var context = realmFactory.CreateContext())
{
context.Write(r => r.Add(beatmap));
liveBeatmap = beatmap.ToLive(realmFactory);
}
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
migratedStorage.DeleteDirectory(string.Empty);
storage.Migrate(migratedStorage);
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
}
});
}
[Test]
public void TestAccessAfterAttach()
{
@ -36,7 +64,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
var liveBeatmap = beatmap.ToLive(realmFactory);
using (var context = realmFactory.CreateContext())
context.Write(r => r.Add(beatmap));
@ -49,7 +77,7 @@ namespace osu.Game.Tests.Database
public void TestAccessNonManaged()
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
var liveBeatmap = beatmap.ToLiveUnmanaged();
Assert.IsFalse(beatmap.Hidden);
Assert.IsFalse(liveBeatmap.Value.Hidden);
@ -74,7 +102,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -103,7 +131,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -123,7 +151,7 @@ namespace osu.Game.Tests.Database
RunTestWithRealm((realmFactory, _) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
var liveBeatmap = beatmap.ToLive(realmFactory);
Assert.DoesNotThrow(() =>
{
@ -145,7 +173,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -183,7 +211,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -222,7 +250,7 @@ namespace osu.Game.Tests.Database
// not just a refresh from the resolved Live.
threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();

View File

@ -10,6 +10,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Models;
#nullable enable
@ -27,15 +28,16 @@ namespace osu.Game.Tests.Database
storage.DeleteDirectory(string.Empty);
}
protected void RunTestWithRealm(Action<RealmContextFactory, Storage> testAction, [CallerMemberName] string caller = "")
protected void RunTestWithRealm(Action<RealmContextFactory, OsuStorage> testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
host.Run(new RealmTestGame(() =>
{
var testStorage = storage.GetStorageForDirectory(caller);
// ReSharper disable once AccessToDisposedClosure
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
using (var realmFactory = new RealmContextFactory(testStorage, caller))
using (var realmFactory = new RealmContextFactory(testStorage, "client"))
{
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
testAction(realmFactory, testStorage);
@ -52,13 +54,13 @@ namespace osu.Game.Tests.Database
protected void RunTestWithRealmAsync(Func<RealmContextFactory, Storage, Task> testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
host.Run(new RealmTestGame(async () =>
{
var testStorage = storage.GetStorageForDirectory(caller);
using (var realmFactory = new RealmContextFactory(testStorage, caller))
using (var realmFactory = new RealmContextFactory(testStorage, "client"))
{
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
await testAction(realmFactory, testStorage);

View File

@ -40,80 +40,80 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestNormalControlPointVolume()
{
var hitcircle = new HitCircle
var hitCircle = new HitCircle
{
StartTime = 0,
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
};
hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
assertOk(new List<HitObject> { hitcircle });
assertOk(new List<HitObject> { hitCircle });
}
[Test]
public void TestLowControlPointVolume()
{
var hitcircle = new HitCircle
var hitCircle = new HitCircle
{
StartTime = 1000,
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
};
hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
assertLowVolume(new List<HitObject> { hitcircle });
assertLowVolume(new List<HitObject> { hitCircle });
}
[Test]
public void TestMutedControlPointVolume()
{
var hitcircle = new HitCircle
var hitCircle = new HitCircle
{
StartTime = 2000,
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
};
hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
assertMuted(new List<HitObject> { hitcircle });
assertMuted(new List<HitObject> { hitCircle });
}
[Test]
public void TestNormalSampleVolume()
{
// The sample volume should take precedence over the control point volume.
var hitcircle = new HitCircle
var hitCircle = new HitCircle
{
StartTime = 2000,
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
};
hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
assertOk(new List<HitObject> { hitcircle });
assertOk(new List<HitObject> { hitCircle });
}
[Test]
public void TestLowSampleVolume()
{
var hitcircle = new HitCircle
var hitCircle = new HitCircle
{
StartTime = 2000,
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) }
};
hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
assertLowVolume(new List<HitObject> { hitcircle });
assertLowVolume(new List<HitObject> { hitCircle });
}
[Test]
public void TestMutedSampleVolume()
{
var hitcircle = new HitCircle
var hitCircle = new HitCircle
{
StartTime = 0,
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) }
};
hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty());
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
assertMuted(new List<HitObject> { hitcircle });
assertMuted(new List<HitObject> { hitCircle });
}
[Test]

View File

@ -5,8 +5,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Audio;
@ -15,6 +17,8 @@ using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -33,6 +37,9 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneStoryboardSamples : OsuTestScene, IStorageResourceProvider
{
[Resolved]
private OsuConfigManager config { get; set; }
[Test]
public void TestRetrieveTopLevelSample()
{
@ -164,6 +171,39 @@ namespace osu.Game.Tests.Gameplay
((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
}
[Test]
public void TestSamplePlaybackWithBeatmapHitsoundsOff()
{
GameplayClockContainer gameplayContainer = null;
TestDrawableStoryboardSample sample = null;
AddStep("disable beatmap hitsounds", () => config.SetValue(OsuSetting.BeatmapHitsounds, false));
AddStep("setup storyboard sample", () =>
{
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
{
Child = beatmapSkinSourceContainer
});
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
{
Clock = gameplayContainer.GameplayClock
});
});
AddStep("start", () => gameplayContainer.Start());
AddUntilStep("sample played", () => sample.IsPlayed);
AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue);
AddStep("restore default", () => config.GetBindable<bool>(OsuSetting.BeatmapHitsounds).SetDefault());
}
private class TestSkin : LegacySkin
{
public TestSkin(string resourceName, IStorageResourceProvider resources)
@ -183,7 +223,8 @@ namespace osu.Game.Tests.Gameplay
public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/Samples/test-sample.mp3") : null;
public Task<byte[]> GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/Samples/test-sample.mp3") : null;
public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = default)
=> name == resourceName ? TestResources.GetStore().GetAsync("Resources/Samples/test-sample.mp3", cancellationToken) : null;
public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/Samples/test-sample.mp3") : null;
@ -220,6 +261,7 @@ namespace osu.Game.Tests.Gameplay
public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => null;
public new IResourceStore<byte[]> Resources => base.Resources;
public RealmContextFactory RealmContextFactory => null;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
#endregion

View File

@ -34,20 +34,20 @@ namespace osu.Game.Tests.Mods
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
var doubleConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
var doubleConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
Assert.That(mod1, Is.Not.EqualTo(mod2));
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doubleConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod2));
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod3));
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod3));
Assert.That(mod3, Is.EqualTo(mod2));
Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
}
}
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
{
try
{
string defaultStorageLocation = getDefaultLocationFor(nameof(TestDefaultDirectory));
string defaultStorageLocation = getDefaultLocationFor(host);
var osu = LoadOsuIntoHost(host);
var storage = osu.Dependencies.Get<Storage>();
@ -61,6 +61,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
}
}
}
@ -94,6 +95,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
}
}
}
@ -107,7 +109,7 @@ namespace osu.Game.Tests.NonVisual
{
try
{
string defaultStorageLocation = getDefaultLocationFor(nameof(TestMigration));
string defaultStorageLocation = getDefaultLocationFor(host);
var osu = LoadOsuIntoHost(host);
var storage = osu.Dependencies.Get<Storage>();
@ -160,6 +162,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
}
}
}
@ -168,7 +171,7 @@ namespace osu.Game.Tests.NonVisual
public void TestMigrationBetweenTwoTargets()
{
string customPath = prepareCustomPath();
string customPath2 = prepareCustomPath("-2");
string customPath2 = prepareCustomPath();
using (var host = new CustomTestHeadlessGameHost())
{
@ -185,7 +188,7 @@ namespace osu.Game.Tests.NonVisual
Assert.That(File.Exists(Path.Combine(customPath2, database_filename)));
// some files may have been left behind for whatever reason, but that's not what we're testing here.
customPath = prepareCustomPath();
cleanupPath(customPath);
Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
@ -193,6 +196,8 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
cleanupPath(customPath2);
}
}
}
@ -214,6 +219,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
}
}
}
@ -243,6 +249,7 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
}
}
}
@ -272,13 +279,14 @@ namespace osu.Game.Tests.NonVisual
finally
{
host.Exit();
cleanupPath(customPath);
}
}
}
private static string getDefaultLocationFor(string testTypeName)
private static string getDefaultLocationFor(CustomTestHeadlessGameHost host)
{
string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, testTypeName);
string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, host.Name);
if (Directory.Exists(path))
Directory.Delete(path, true);
@ -286,14 +294,18 @@ namespace osu.Game.Tests.NonVisual
return path;
}
private string prepareCustomPath(string suffix = "")
private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}");
private static void cleanupPath(string path)
{
string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path{suffix}");
if (Directory.Exists(path))
Directory.Delete(path, true);
return path;
try
{
if (Directory.Exists(path))
Directory.Delete(path, true);
}
catch
{
}
}
public class CustomTestHeadlessGameHost : CleanRunHeadlessGameHost
@ -303,7 +315,7 @@ namespace osu.Game.Tests.NonVisual
public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"")
: base(callingMethodName: callingMethodName)
{
string defaultStorageLocation = getDefaultLocationFor(callingMethodName);
string defaultStorageLocation = getDefaultLocationFor(this);
InitialStorage = new DesktopStorage(defaultStorageLocation, this);
InitialStorage.DeleteDirectory(string.Empty);

View File

@ -45,8 +45,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5);
checkPlayingUserCount(0);
AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
@ -64,15 +62,13 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
}
[Test]
public void TestPlayingUsersUpdatedOnJoin()
{
AddStep("leave room", () => Client.LeaveRoom());
AddUntilStep("wait for room part", () => Client.Room == null);
AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("create room initially in gameplay", () =>
{

View File

@ -1,9 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Game.Configuration;
using osu.Game.Input;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tests.NonVisual
{
@ -11,37 +12,32 @@ namespace osu.Game.Tests.NonVisual
public class SessionStaticsTest
{
private SessionStatics sessionStatics;
private IdleTracker sessionIdleTracker;
[SetUp]
public void SetUp()
[Test]
public void TestSessionStaticsReset()
{
sessionStatics = new SessionStatics();
sessionIdleTracker = new GameIdleTracker(1000);
sessionStatics.SetValue(Static.LoginOverlayDisplayed, true);
sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true);
sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true);
sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d);
sessionStatics.SetValue(Static.SeasonalBackgrounds, new APISeasonalBackgrounds { EndDate = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) });
sessionIdleTracker.IsIdle.BindValueChanged(e =>
{
if (e.NewValue)
sessionStatics.ResetValues();
});
}
Assert.IsFalse(sessionStatics.GetBindable<bool>(Static.LoginOverlayDisplayed).IsDefault);
Assert.IsFalse(sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).IsDefault);
Assert.IsFalse(sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).IsDefault);
Assert.IsFalse(sessionStatics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime).IsDefault);
Assert.IsFalse(sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds).IsDefault);
[Test]
[Timeout(2000)]
public void TestSessionStaticsReset()
{
sessionIdleTracker.IsIdle.BindValueChanged(e =>
{
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.LoginOverlayDisplayed).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime).IsDefault);
});
sessionStatics.ResetAfterInactivity();
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.LoginOverlayDisplayed).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).IsDefault);
Assert.IsTrue(sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce).IsDefault);
// some statics should not reset despite inactivity.
Assert.IsFalse(sessionStatics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime).IsDefault);
Assert.IsFalse(sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds).IsDefault);
}
}
}

View File

@ -0,0 +1,90 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Online.Chat;
namespace osu.Game.Tests.Online.Chat
{
[TestFixture]
public class MessageNotifierTest
{
[Test]
public void TestContainsUsernameMidlinePositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test message", "Test"));
}
[Test]
public void TestContainsUsernameStartOfLinePositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test message", "Test"));
}
[Test]
public void TestContainsUsernameEndOfLinePositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test", "Test"));
}
[Test]
public void TestContainsUsernameMidlineNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a testmessage for notifications", "Test"));
}
[Test]
public void TestContainsUsernameStartOfLineNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Testmessage", "Test"));
}
[Test]
public void TestContainsUsernameEndOfLineNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a notificationtest", "Test"));
}
[Test]
public void TestContainsUsernameBetweenInterpunction()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test"));
}
[Test]
public void TestContainsUsernameUnicode()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test \u0460\u0460 message", "\u0460\u0460"));
}
[Test]
public void TestContainsUsernameUnicodeNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460"));
}
[Test]
public void TestContainsUsernameSpecialCharactersPositive()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test [#^-^#] message", "[#^-^#]"));
}
[Test]
public void TestContainsUsernameSpecialCharactersNegative()
{
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]"));
}
[Test]
public void TestContainsUsernameAtSign()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("@username hi", "username"));
}
[Test]
public void TestContainsUsernameColon()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("username: hi", "username"));
}
}
}

View File

@ -39,7 +39,7 @@ namespace osu.Game.Tests.Online
}
[Test]
public void TestSerialiseUnionFailsWithSingalR()
public void TestSerialiseUnionFailsWithSignalR()
{
var state = new TeamVersusUserState();

View File

@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
AddStep("import altered beatmap", () =>
{
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
});
addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded);
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem }
});
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait());
addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
}
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)

View File

@ -0,0 +1,100 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Game.Online.Rooms;
namespace osu.Game.Tests.OnlinePlay
{
[TestFixture]
public class PlaylistExtensionsTest
{
[Test]
public void TestEmpty()
{
// mostly an extreme edge case, i.e. during room creation.
var items = Array.Empty<PlaylistItem>();
Assert.Multiple(() =>
{
Assert.That(items.GetHistoricalItems(), Is.Empty);
Assert.That(items.GetCurrentItem(), Is.Null);
Assert.That(items.GetUpcomingItems(), Is.Empty);
});
}
[Test]
public void TestPlaylistItemsInOrder()
{
var items = new[]
{
new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
{
Assert.That(items.GetHistoricalItems(), Is.Empty);
Assert.That(items.GetCurrentItem(), Is.EqualTo(items[0]));
Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(items));
});
}
[Test]
public void TestPlaylistItemsOutOfOrder()
{
var items = new[]
{
new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
{
Assert.That(items.GetHistoricalItems(), Is.Empty);
Assert.That(items.GetCurrentItem(), Is.EqualTo(items[1]));
Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] }));
});
}
[Test]
public void TestExpiredPlaylistItemsSkipped()
{
var items = new[]
{
new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
{
Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0] }));
Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2]));
Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[2] }));
});
}
[Test]
public void TestAllItemsExpired()
{
var items = new[]
{
new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) },
};
Assert.Multiple(() =>
{
Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] }));
// if all items are expired, the last-played item is expected to be returned.
Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2]));
Assert.That(items.GetUpcomingItems(), Is.Empty);
});
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -114,7 +115,7 @@ namespace osu.Game.Tests.Rulesets
public Sample Get(string name) => null;
public Task<Sample> GetAsync(string name) => null;
public Task<Sample> GetAsync(string name, CancellationToken cancellationToken = default) => null;
public Stream GetStream(string name) => null;

View File

@ -319,7 +319,7 @@ namespace osu.Game.Tests.Skins.IO
private async Task runSkinTest(Func<OsuGameBase, Task> action, [CallerMemberName] string callingMethodName = @"")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: callingMethodName))
{
try
{

View File

@ -5,8 +5,11 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.Backgrounds;
@ -15,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Background
{
@ -22,8 +26,7 @@ namespace osu.Game.Tests.Visual.Background
public class TestSceneBackgroundScreenDefault : OsuTestScene
{
private BackgroundScreenStack stack;
private BackgroundScreenDefault screen;
private TestBackgroundScreenDefault screen;
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault();
[Resolved]
@ -36,10 +39,96 @@ namespace osu.Game.Tests.Visual.Background
public void SetUpSteps()
{
AddStep("create background stack", () => Child = stack = new BackgroundScreenStack());
AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false)));
AddStep("push default screen", () => stack.Push(screen = new TestBackgroundScreenDefault()));
AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen());
}
[Test]
public void TestBeatmapBackgroundTracksBeatmap()
{
setSupporter(true);
setSourceMode(BackgroundSource.Beatmap);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
Graphics.Backgrounds.Background last = null;
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
AddStep("store background", () => last = getCurrentBackground());
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
AddStep("store background", () => last = getCurrentBackground());
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
}
[Test]
public void TestBeatmapBackgroundTracksBeatmapWhenSuspended()
{
setSupporter(true);
setSourceMode(BackgroundSource.Beatmap);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
BackgroundScreenBeatmap nestedScreen = null;
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
AddUntilStep("previous background hidden", () => !screen.IsAlive);
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("pop screen back to top level", () => screen.MakeCurrent());
AddAssert("top level background changed", () => screen.CheckLastLoadChange() == true);
}
[Test]
public void TestBeatmapBackgroundIgnoresNoChangeWhenSuspended()
{
BackgroundScreenBeatmap nestedScreen = null;
WorkingBeatmap originalWorking = null;
setSupporter(true);
setSourceMode(BackgroundSource.Beatmap);
AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
// we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("pop screen back to top level", () => screen.MakeCurrent());
AddStep("top level screen is current", () => screen.IsCurrentScreen());
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
}
[Test]
public void TestBackgroundTypeSwitch()
{
@ -78,36 +167,24 @@ namespace osu.Game.Tests.Visual.Background
[TestCase(BackgroundSource.Skin, typeof(SkinBackground))]
public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType)
{
Graphics.Backgrounds.Background last = null;
setSourceMode(source);
setSupporter(true);
if (source == BackgroundSource.Skin)
setCustomSkin();
AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType);
AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == backgroundType);
AddAssert("next doesn't load new background", () => screen.Next() == false);
// doesn't really need to be checked but might as well.
AddWaitStep("wait a bit", 5);
AddUntilStep("ensure same background instance", () => last == getCurrentBackground());
}
[Test]
public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter)
{
Graphics.Backgrounds.Background last = null;
setSourceMode(BackgroundSource.Skin);
setSupporter(supporter);
setDefaultSkin();
AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
AddAssert("next cycles background", () => screen.Next());
// doesn't really need to be checked but might as well.
AddWaitStep("wait a bit", 5);
AddUntilStep("ensure different background instance", () => last != getCurrentBackground());
}
private void setSourceMode(BackgroundSource source) =>
@ -120,10 +197,46 @@ namespace osu.Game.Tests.Visual.Background
Id = API.LocalUser.Value.Id + 1,
});
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
private class TestBackgroundScreenDefault : BackgroundScreenDefault
{
private bool? lastLoadTriggerCausedChange;
public TestBackgroundScreenDefault()
: base(false)
{
}
public override bool Next()
{
bool didChange = base.Next();
lastLoadTriggerCausedChange = didChange;
return didChange;
}
public bool? CheckLastLoadChange()
{
bool? lastChange = lastLoadTriggerCausedChange;
lastLoadTriggerCausedChange = null;
return lastChange;
}
}
private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
{
public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
: base(new Beatmap(), null, audioManager)
{
}
protected override Texture GetBackground() => new Texture(1, 1);
}
private void setCustomSkin()
{
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLive());
AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLiveUnmanaged());
}
private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault());

View File

@ -6,12 +6,12 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
@ -19,11 +19,10 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osuTK;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tests.Visual.Beatmaps
{
public class TestSceneBeatmapCard : OsuTestScene
public class TestSceneBeatmapCard : OsuManualInputManagerTestScene
{
/// <summary>
/// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources.
@ -97,6 +96,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
var longName = CreateAPIBeatmapSet(Ruleset.Value);
longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title";
longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name.";
longName.Source = "wow. even the source field has an impossibly long string in it. this really takes the cake, doesn't it?";
longName.HasExplicitContent = true;
longName.TrackId = 444;
@ -252,15 +252,39 @@ namespace osu.Game.Tests.Visual.Beatmaps
[Test]
public void TestNormal()
{
createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
createTestCase(beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo));
}
AddToggleStep("toggle expanded state", expanded =>
{
var card = this.ChildrenOfType<BeatmapCard>().Last();
if (!card.Expanded.Disabled)
card.Expanded.Value = expanded;
});
AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType<BeatmapCard>().ForEach(card => card.Expanded.Disabled = disabled));
[Test]
public void TestExtra()
{
createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo));
}
[Test]
public void TestHoverState()
{
AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCardNormal(s)));
AddStep("Hover card", () => InputManager.MoveMouseTo(firstCard()));
AddWaitStep("wait for potential state change", 5);
AddAssert("card is not expanded", () => !firstCard().Expanded.Value);
AddStep("Hover spectrum display", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType<DifficultySpectrumDisplay>().Single()));
AddUntilStep("card is expanded", () => firstCard().Expanded.Value);
AddStep("Hover difficulty content", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType<BeatmapCardDifficultyList>().Single()));
AddWaitStep("wait for potential state change", 5);
AddAssert("card is still expanded", () => firstCard().Expanded.Value);
AddStep("Hover main content again", () => InputManager.MoveMouseTo(firstCard()));
AddWaitStep("wait for potential state change", 5);
AddAssert("card is still expanded", () => firstCard().Expanded.Value);
AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType<BeatmapCardNormal>().Last()));
AddUntilStep("card is not expanded", () => !firstCard().Expanded.Value);
BeatmapCardNormal firstCard() => this.ChildrenOfType<BeatmapCardNormal>().First();
}
}
}

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("setup skins", () =>
{
skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLive();
skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLiveUnmanaged();
currentBeatmapSkin = getBeatmapSkin();
});
});

View File

@ -85,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay
loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
double targetTime = addEventToLoop ? 20000 : 0;
target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1);
// these should be ignored due to being in the future.
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
storyboard.GetLayer("Background").Add(sprite);

View File

@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@ -24,5 +25,17 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
}
[Test]
public void TestLegacyComboCounterHiddenByRulesetImplementation()
{
AddToggleStep("toggle legacy hidden by ruleset", visible =>
{
foreach (var legacyCounter in this.ChildrenOfType<LegacyComboCounter>())
legacyCounter.HiddenByRulesetImplementation = visible;
});
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
}
}
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void checkForFirstSamplePlayback()
{
AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded);
AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying));
}

View File

@ -5,7 +5,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
@ -20,7 +19,6 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.Play;
using osu.Game.Tests.Resources;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -31,17 +29,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected BeatmapInfo InitialBeatmap { get; private set; }
protected BeatmapInfo OtherBeatmap { get; private set; }
protected IScreen CurrentScreen => multiplayerScreenStack.CurrentScreen;
protected IScreen CurrentSubScreen => multiplayerScreenStack.MultiplayerScreen.CurrentSubScreen;
protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen;
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerScreenStack multiplayerScreenStack;
private TestMultiplayerComponents multiplayerComponents;
protected TestMultiplayerClient Client => multiplayerScreenStack.Client;
protected TestMultiplayerRoomManager RoomManager => multiplayerScreenStack.RoomManager;
protected TestMultiplayerClient Client => multiplayerComponents.Client;
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache();
@ -65,12 +62,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0);
});
AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));
AddUntilStep("wait for multiplayer to load", () => multiplayerComponents.IsLoaded);
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open(new Room
AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open(new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = Mode },
@ -87,13 +84,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);
AddStep("create room", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomManager.RoomJoined);
AddUntilStep("wait for join", () => Client.RoomJoined);
}
[Test]
@ -105,24 +98,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected void RunGameplay()
{
AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
clickReadyButton();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
clickReadyButton();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded);
AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent());
}
private void clickReadyButton()
{
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<Button>().Single().Enabled.Value);
AddStep("click ready button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded);
AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent());
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestFirstItemSelectedByDefault()
{
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
}
[Test]
@ -27,13 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
addItem(() => OtherBeatmap);
AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2);
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID);
addItem(() => InitialBeatmap);
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID);
AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
AddAssert("first item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
}
[Test]
@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1);
AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true);
AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
AddAssert("last item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
}
[Test]
@ -55,12 +53,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
RunGameplay();
AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID);
AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID);
RunGameplay();
AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true);
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID);
AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[2].ID);
}
[Test]
@ -74,15 +72,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly));
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID);
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2);
}
[Test]
public void TestCorrectItemSelectedAfterNewItemAdded()
{
addItem(() => OtherBeatmap);
AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
}
private void addItem(Func<BeatmapInfo> beatmap)

View File

@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
@ -20,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestFirstItemSelectedByDefault()
{
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
}
[Test]
@ -28,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
selectNewItem(() => InitialBeatmap);
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
}
[Test]
@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
selectNewItem(() => OtherBeatmap);
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
}
[Test]
@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID);
AddAssert("second playlist item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID);
}
[Test]
@ -85,6 +86,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void selectNewItem(Func<BeatmapInfo> beatmap)
{
AddUntilStep("wait for playlist panels to load", () =>
{
var queueList = this.ChildrenOfType<MultiplayerQueueList>().Single();
return queueList.ChildrenOfType<DrawableRoomPlaylistItem>().Count() == queueList.Items.Count;
});
AddStep("click edit button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().First());
@ -97,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID);
AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID);
}
private void addItem(Func<BeatmapInfo> beatmap)

View File

@ -30,6 +30,8 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Spectate;
@ -44,10 +46,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerScreenStack multiplayerScreenStack;
private TestMultiplayerComponents multiplayerComponents;
private TestMultiplayerClient client => multiplayerScreenStack.Client;
private TestMultiplayerRoomManager roomManager => multiplayerScreenStack.RoomManager;
private TestMultiplayerClient client => multiplayerComponents.Client;
private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager;
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache();
@ -69,8 +71,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
});
AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));
AddUntilStep("wait for multiplayer to load", () => multiplayerComponents.IsLoaded);
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
}
@ -214,7 +216,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Press select", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for join", () => roomManager.RoomJoined);
AddUntilStep("wait for join", () => client.RoomJoined);
}
[Test]
@ -293,7 +295,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("join room", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for join", () => roomManager.RoomJoined);
AddUntilStep("wait for join", () => client.RoomJoined);
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
@ -351,7 +353,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for join", () => roomManager.RoomJoined);
AddUntilStep("wait for join", () => client.RoomJoined);
}
[Test]
@ -391,9 +393,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready));
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
pressReadyButton();
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
}
@ -413,10 +415,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
pressReadyButton();
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value);
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
@ -429,7 +433,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("start match externally", () => client.StartMatch());
AddUntilStep("play started", () => multiplayerScreenStack.CurrentScreen is Player);
AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player);
AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID);
}
@ -459,17 +463,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
AddStep("click spectate button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerSpectateButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
AddStep("start match externally", () => client.StartMatch());
AddAssert("play not started", () => multiplayerScreenStack.IsCurrentScreen());
AddAssert("play not started", () => multiplayerComponents.IsCurrentScreen());
}
[Test]
@ -497,11 +497,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready);
});
AddStep("click spectate button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerSpectateButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
@ -513,7 +509,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
});
AddUntilStep("play started", () => multiplayerScreenStack.CurrentScreen is SpectatorScreen);
AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is SpectatorScreen);
}
[Test]
@ -555,16 +551,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("open mod overlay", () => this.ChildrenOfType<RoomSubScreen.UserModSelectButton>().Single().TriggerClick());
AddStep("invoke on back button", () => multiplayerScreenStack.OnBackButton());
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
testLeave("back button", () => multiplayerScreenStack.OnBackButton());
testLeave("back button", () => multiplayerComponents.OnBackButton());
// mimics home button and OS window close
testLeave("forced exit", () => multiplayerScreenStack.Exit());
testLeave("forced exit", () => multiplayerComponents.Exit());
void testLeave(string actionName, Action action)
{
@ -592,20 +588,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click ready button", () =>
{
InputManager.MoveMouseTo(readyButton);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player);
enterGameplay();
// Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out.
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
@ -614,7 +597,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.GameplayClock.CurrentTime > time);
}
AddUntilStep("wait for results", () => multiplayerScreenStack.CurrentScreen is ResultsScreen);
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);
}
[Test]
@ -655,7 +638,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("join room", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for join", () => roomManager.RoomJoined);
AddUntilStep("wait for join", () => client.RoomJoined);
AddAssert("local room has correct settings", () =>
{
@ -665,23 +648,187 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
[Test]
public void TestSpectatingStateResetOnBackButtonDuringGameplay()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready));
pressReadyButton(1234);
AddUntilStep("wait for gameplay", () => (multiplayerComponents.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true);
AddStep("press back button and exit", () =>
{
multiplayerComponents.OnBackButton();
multiplayerComponents.Exit();
});
AddUntilStep("wait for return to match subscreen", () => multiplayerComponents.MultiplayerScreen.IsCurrentScreen());
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
}
[Test]
public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready));
pressReadyButton(1234);
AddUntilStep("wait for gameplay", () => (multiplayerComponents.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true);
AddStep("set other user loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded));
AddStep("set other user finished play", () => client.ChangeUserState(1234, MultiplayerUserState.FinishedPlay));
AddStep("press back button and exit", () =>
{
multiplayerComponents.OnBackButton();
multiplayerComponents.Exit();
});
AddUntilStep("wait for return to match subscreen", () => multiplayerComponents.MultiplayerScreen.IsCurrentScreen());
AddWaitStep("wait for possible state change", 5);
AddUntilStep("user state is spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
}
[Test]
public void TestItemAddedByOtherUserDuringGameplay()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
enterGameplay();
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
{
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1
})));
AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2);
AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent());
AddUntilStep("queue contains item", () => this.ChildrenOfType<MultiplayerQueueList>().Single().Items.Single().ID == 2);
}
[Test]
public void TestItemAddedAndDeletedByOtherUserDuringGameplay()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
enterGameplay();
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
{
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1
})));
AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2);
AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2));
AddUntilStep("item removed from playlist", () => client.Room?.Playlist.Count == 1);
AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent());
AddUntilStep("queue is empty", () => this.ChildrenOfType<MultiplayerQueueList>().Single().Items.Count == 0);
}
private void enterGameplay()
{
pressReadyButton();
pressReadyButton();
AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player);
}
private ReadyButton readyButton => this.ChildrenOfType<ReadyButton>().Single();
private void pressReadyButton(int? playingUserId = null)
{
// Can't use ClickButtonWhenEnabled<> due to needing to store the state after the button is enabled.
AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value);
MultiplayerUserState lastState = MultiplayerUserState.Idle;
MultiplayerRoomUser user = null;
AddStep("click ready button", () =>
{
user = playingUserId == null ? client.LocalUser : client.Room?.Users.Single(u => u.UserID == playingUserId);
lastState = user?.State ?? MultiplayerUserState.Idle;
InputManager.MoveMouseTo(readyButton);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for state change", () => user?.State != lastState);
}
private void createRoom(Func<Room> room)
{
AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);
AddStep("create room", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => roomManager.RoomJoined);
AddUntilStep("wait for join", () => client.RoomJoined);
}
}
}

View File

@ -14,12 +14,10 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -62,22 +60,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestCreatedRoom()
{
AddStep("create room", () =>
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
});
// Needs to run after components update with the playlist item.
Schedule(() =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
InputManager.Click(MouseButton.Left);
});
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
}
@ -110,11 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
});
AddStep("click create button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for room join", () => RoomJoined);
@ -124,21 +113,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready);
});
AddStep("click spectate button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerSpectateButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
AddStep("click ready button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
InputManager.Click(MouseButton.Left);
});
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad);
}

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@ -27,7 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("initialise gameplay", () =>
{
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
}, Client.Room?.Users.ToArray()));
});
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);

View File

@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("finish current item", () => Client.FinishCurrentItem());
AddStep("leave room", () => RoomManager.PartRoom());
AddUntilStep("wait for room part", () => Client.Room == null);
AddUntilStep("wait for room part", () => !RoomJoined);
AddUntilStep("item 0 not in lists", () => !inHistoryList(0) && !inQueueList(0));
AddUntilStep("item 1 not in lists", () => !inHistoryList(0) && !inQueueList(0));
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
{
AddStep("leave room", () => RoomManager.PartRoom());
AddUntilStep("wait for room part", () => Client.Room == null);
AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("join room with items", () =>
{

View File

@ -12,7 +12,6 @@ using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
@ -21,17 +20,15 @@ using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerQueueList : MultiplayerTestScene
{
private MultiplayerQueueList playlist;
[Cached(typeof(UserLookupCache))]
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
private MultiplayerQueueList playlist;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
@ -55,7 +52,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300),
SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem },
Items = { BindTarget = Client.APIRoom!.Playlist }
};
});
@ -107,22 +103,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
assertDeleteButtonVisibility(0, false);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
assertDeleteButtonVisibility(0, false);
assertDeleteButtonVisibility(1, true);
// Run through gameplay.
AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready));
AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
AddStep("start match", () => Client.StartMatch());
AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded));
AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing);
AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay));
AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results);
AddStep("finish current item", () => Client.FinishCurrentItem());
AddUntilStep("wait for next item to be selected", () => Client.Room?.Settings.PlaylistItemId == 2);
AddUntilStep("wait for two items in playlist", () => playlist.ChildrenOfType<DrawableRoomPlaylistItem>().Count() == 2);
assertDeleteButtonVisibility(0, false);
assertDeleteButtonVisibility(1, false);
}
@ -146,23 +136,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("item arrived in playlist", () => playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().Any(i => i.Model.ID == itemId));
}
private void deleteItem(int index)
{
OsuRearrangeableListItem<PlaylistItem> item = null;
AddStep($"move mouse to delete button {index}", () =>
{
item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
InputManager.MoveMouseTo(item.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(0));
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddUntilStep("item removed from playlist", () => !playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().Contains(item));
}
private void assertDeleteButtonVisibility(int index, bool visible)
=> AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible",
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(index).Alpha > 0) == visible);
=> AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", () =>
{
var button = playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAtOrDefault(index);
return (button?.Alpha > 0) == visible;
});
}
}

View File

@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Framework.Utils;
@ -22,7 +21,6 @@ using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -113,10 +111,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.TransferHost(2);
});
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
}
@ -132,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.AddUser(new APIUser { Id = 2, Username = "Another user" });
});
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
verifyGameplayStartFlow();
@ -147,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.TransferHost(2);
});
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0));
verifyGameplayStartFlow();
@ -162,11 +160,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.AddUser(new APIUser { Id = 2, Username = "Another user" });
});
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddStep("transfer host", () => Client.TransferHost(Client.Room?.Users[1].UserID ?? 0));
addClickButtonStep();
AddAssert("match not started", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user is idle (match not started)", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
}
[TestCase(true)]
@ -184,7 +185,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
if (!isHost)
AddStep("transfer host", () => Client.TransferHost(2));
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddRepeatStep("change user ready state", () =>
{
@ -199,20 +200,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
}, users);
}
private void addClickButtonStep()
{
AddUntilStep("wait for button to be ready", () => button.ChildrenOfType<Button>().Single().Enabled.Value);
AddStep("click button", () =>
{
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
}
private void verifyGameplayStartFlow()
{
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
addClickButtonStep();
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);

View File

@ -21,7 +21,6 @@ using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -121,10 +120,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestCase(MultiplayerUserState.Ready)]
public void TestToggleWhenIdle(MultiplayerUserState initialState)
{
addClickSpectateButtonStep();
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
AddUntilStep("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
addClickSpectateButtonStep();
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
}
@ -138,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestReadyButtonDisabledWhenHostAndNoReadyUsers()
{
addClickSpectateButtonStep();
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
assertReadyButtonEnablement(false);
}
@ -148,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add user", () => Client.AddUser(new APIUser { Id = PLAYER_1_ID }));
AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready));
addClickSpectateButtonStep();
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
assertReadyButtonEnablement(true);
}
@ -163,16 +162,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready));
addClickSpectateButtonStep();
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
assertReadyButtonEnablement(false);
}
private void addClickSpectateButtonStep() => AddStep("click spectate button", () =>
{
InputManager.MoveMouseTo(spectateButton);
InputManager.Click(MouseButton.Left);
});
private void assertSpectateButtonEnablement(bool shouldBeEnabled)
=> AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);

View File

@ -31,9 +31,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
private TestMultiplayerScreenStack multiplayerScreenStack;
private TestMultiplayerComponents multiplayerComponents;
private TestMultiplayerClient client => multiplayerScreenStack.Client;
private TestMultiplayerClient client => multiplayerComponents.Client;
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache();
@ -55,8 +55,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
});
AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));
AddUntilStep("wait for multiplayer to load", () => multiplayerComponents.IsLoaded);
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
}
@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("press own button", () =>
{
InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType<TeamDisplay>().First());
InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType<TeamDisplay>().First());
InputManager.Click(MouseButton.Left);
});
AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1);
@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("press other user's button", () =>
{
InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType<TeamDisplay>().ElementAt(1));
InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType<TeamDisplay>().ElementAt(1));
InputManager.Click(MouseButton.Left);
});
AddAssert("user still on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
@ -164,18 +164,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam == null));
AddUntilStep("team displays are not displaying teams", () => multiplayerComponents.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam == null));
AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus));
AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam != null));
AddUntilStep("team displays are displaying teams", () => multiplayerComponents.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam != null));
}
private void createRoom(Func<Room> room)
{
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for join", () => multiplayerScreenStack.RoomManager.RoomJoined);
AddUntilStep("wait for join", () => client.RoomJoined);
}
}
}

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