mirror of
https://github.com/ppy/osu.git
synced 2026-05-22 19:41:06 +08:00
Compare commits
584 Commits
@@ -27,10 +27,10 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2021.725.0",
|
||||
"version": "2021.1210.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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.**
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
-2
@@ -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.1203.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1225.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. -->
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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)]
|
||||
@@ -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" />
|
||||
|
||||
@@ -70,7 +70,9 @@ namespace osu.Desktop
|
||||
if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
|
||||
return stableInstallPath;
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
|
||||
@@ -113,7 +115,7 @@ namespace osu.Desktop
|
||||
base.LoadComplete();
|
||||
|
||||
if (!noVersionOverlay)
|
||||
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
|
||||
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
|
||||
|
||||
LoadComponentAsync(new DiscordRichPresence(), Add);
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ namespace osu.Desktop
|
||||
Logger.Log("Starting legacy IPC provider...");
|
||||
legacyIpc = new LegacyTcpIpcProvider();
|
||||
legacyIpc.Bind();
|
||||
legacyIpc.StartAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
|
||||
{
|
||||
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
|
||||
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.CreateInfo()));
|
||||
var testSkinProvider = new SkinProvidingContainer(skin);
|
||||
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+614
-307
File diff suppressed because it is too large
Load Diff
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"StartTime": 369,
|
||||
"Objects": [{
|
||||
"StartTime": 369,
|
||||
"Position": 0,
|
||||
"HyperDash": true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"StartTime": 450,
|
||||
"Objects": [{
|
||||
"StartTime": 450,
|
||||
"Position": 512,
|
||||
"HyperDash": false
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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:
|
||||
+72
-36
@@ -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
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
+34
-17
@@ -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
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
+66
-33
@@ -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
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
+4
-2
@@ -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
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
+20
-10
@@ -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
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
+34
-17
@@ -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
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Screens.Edit;
|
||||
@@ -55,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
[Test]
|
||||
public void TestDefaultSkin()
|
||||
{
|
||||
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = SkinInfo.Default);
|
||||
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLegacySkin()
|
||||
{
|
||||
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info);
|
||||
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddStep("create slider", () =>
|
||||
{
|
||||
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
|
||||
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
||||
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
||||
|
||||
Child = new SkinProvidingContainer(tintingSkin)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
@@ -43,15 +43,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);
|
||||
}
|
||||
@@ -60,13 +60,13 @@ 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];
|
||||
}
|
||||
|
||||
|
||||
@@ -74,9 +74,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,
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -32,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;
|
||||
@@ -45,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);
|
||||
|
||||
@@ -72,42 +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 *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
|
||||
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
aimValue *= getComboScalingFactor();
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
@@ -136,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;
|
||||
@@ -144,9 +136,8 @@ 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;
|
||||
@@ -155,9 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (effectiveMissCount > 0)
|
||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
speedValue *= getComboScalingFactor();
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
@@ -227,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;
|
||||
|
||||
@@ -242,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) +
|
||||
@@ -276,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,49 +11,65 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
private const int min_delta_time = 25;
|
||||
private const float maximum_slider_radius = normalized_radius * 2.4f;
|
||||
private const float assumed_slider_radius = normalized_radius * 1.8f;
|
||||
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
||||
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
||||
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double JumpDistance { get; private set; }
|
||||
public readonly double StrainTime;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Normalised distance from the "lazy" end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// <para>
|
||||
/// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public double MovementDistance { get; private set; }
|
||||
public double LazyJumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Normalised shortest distance to consider for a jump between the previous <see cref="OsuDifficultyHitObject"/> and this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is bounded from above by <see cref="LazyJumpDistance"/>, and is smaller than the former if a more natural path is able to be taken through the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Suppose a linear slider - circle pattern.
|
||||
/// <br />
|
||||
/// Following the slider lazily (see: <see cref="LazyJumpDistance"/>) will result in underestimating the true end position of the slider as being closer towards the start position.
|
||||
/// As a result, <see cref="LazyJumpDistance"/> overestimates the jump distance because the player is able to take a more natural path by following through the slider to its end,
|
||||
/// such that the jump is felt as only starting from the slider's true end position.
|
||||
/// <br />
|
||||
/// Now consider a slider - circle pattern where the circle is stacked along the path inside the slider.
|
||||
/// In this case, the lazy end position correctly estimates the true end position of the slider and provides the more natural movement path.
|
||||
/// </example>
|
||||
public double MinimumJumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time taken to travel through <see cref="MinimumJumpDistance"/>, with a minimum value of 25ms.
|
||||
/// </summary>
|
||||
public double MinimumJumpTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalised distance between the start and end position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double TravelDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for a non-zero distance.
|
||||
/// </summary>
|
||||
public double TravelTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Calculated as the angle between the circles (current-2, current-1, current).
|
||||
/// </summary>
|
||||
public double? Angle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the end time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double MovementTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/> to the end time of the same previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double TravelTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
private readonly OsuHitObject lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
|
||||
@@ -71,12 +87,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
private void setDistances(double clockRate)
|
||||
{
|
||||
if (BaseObject is Slider currentSlider)
|
||||
{
|
||||
computeSliderCursorPosition(currentSlider);
|
||||
TravelDistance = currentSlider.LazyTravelDistance;
|
||||
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
}
|
||||
|
||||
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
|
||||
if (BaseObject is Spinner || lastObject is Spinner)
|
||||
return;
|
||||
|
||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
|
||||
float scalingFactor = normalised_radius / (float)BaseObject.Radius;
|
||||
|
||||
if (BaseObject.Radius < 30)
|
||||
{
|
||||
@@ -85,29 +108,40 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
}
|
||||
|
||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
|
||||
LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
MinimumJumpTime = StrainTime;
|
||||
MinimumJumpDistance = LazyJumpDistance;
|
||||
|
||||
if (lastObject is Slider lastSlider)
|
||||
{
|
||||
computeSliderCursorPosition(lastSlider);
|
||||
TravelDistance = lastSlider.LazyTravelDistance;
|
||||
TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time);
|
||||
double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time);
|
||||
|
||||
//
|
||||
// There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects.
|
||||
//
|
||||
// 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject.
|
||||
//
|
||||
// <======o==> ← slider
|
||||
// | ← most natural jump path
|
||||
// o ← a follow-up hitcircle
|
||||
//
|
||||
// In this case the most natural jump path is approximated by LazyJumpDistance.
|
||||
//
|
||||
// 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject.
|
||||
//
|
||||
// <======o==>---o
|
||||
// ↑
|
||||
// most natural jump path
|
||||
//
|
||||
// In this case the most natural jump path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject.
|
||||
//
|
||||
// Thus, the player is assumed to jump the minimum of these two distances in all cases.
|
||||
//
|
||||
|
||||
// Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance.
|
||||
float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
|
||||
|
||||
// For hitobjects which continue in the direction of the slider, the player will normally follow through the slider,
|
||||
// such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider.
|
||||
// In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance.
|
||||
// Additional distance is removed based on position of jump relative to slider follow circle radius.
|
||||
// JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible.
|
||||
MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
|
||||
}
|
||||
else
|
||||
{
|
||||
MovementTime = StrainTime;
|
||||
MovementDistance = JumpDistance;
|
||||
MinimumJumpDistance = Math.Max(0, Math.Min(LazyJumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
|
||||
}
|
||||
|
||||
if (lastLastObject != null && !(lastLastObject is Spinner))
|
||||
@@ -139,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
||||
var currCursorPosition = slider.StackedPosition;
|
||||
double scalingFactor = normalized_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||
double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||
|
||||
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
||||
{
|
||||
@@ -167,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
else if (currMovementObj is SliderRepeat)
|
||||
{
|
||||
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
||||
requiredMovement = normalized_radius;
|
||||
requiredMovement = normalised_radius;
|
||||
}
|
||||
|
||||
if (currMovementLength > requiredMovement)
|
||||
|
||||
@@ -44,24 +44,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
|
||||
|
||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
|
||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
||||
|
||||
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
if (osuLastObj.BaseObject is Slider && withSliders)
|
||||
{
|
||||
double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
|
||||
double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object
|
||||
|
||||
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
|
||||
}
|
||||
|
||||
// As above, do the same for the previous hitobject.
|
||||
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
|
||||
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
|
||||
|
||||
if (osuLastLastObj.BaseObject is Slider && withSliders)
|
||||
{
|
||||
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime;
|
||||
double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime;
|
||||
|
||||
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
||||
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||
}
|
||||
|
||||
// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
||||
@@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
if (Math.Max(prevVelocity, currVelocity) != 0)
|
||||
{
|
||||
// We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
|
||||
prevVelocity = (osuLastObj.JumpDistance + osuLastObj.TravelDistance) / osuLastObj.StrainTime;
|
||||
currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime;
|
||||
prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime;
|
||||
currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime;
|
||||
|
||||
// Scale with ratio of difference compared to 0.5 * max dist.
|
||||
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
|
||||
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
// Reward for % distance slowed down compared to previous, paying attention to not award overlap
|
||||
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
|
||||
// do not award overlap
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.JumpDistance, osuLastObj.JumpDistance) / 100)), 2);
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);
|
||||
|
||||
// Choose the largest bonus, multiplied by ratio.
|
||||
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
|
||||
@@ -128,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
||||
}
|
||||
|
||||
if (osuCurrObj.TravelTime != 0)
|
||||
if (osuLastObj.TravelTime != 0)
|
||||
{
|
||||
// Reward sliders based on velocity.
|
||||
sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
|
||||
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
}
|
||||
|
||||
// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
|
||||
|
||||
@@ -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.JumpDistance / 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);
|
||||
|
||||
@@ -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)
|
||||
@@ -154,7 +156,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
if (strainTime < min_speed_bonus)
|
||||
speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
||||
|
||||
double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance);
|
||||
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
||||
double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
|
||||
|
||||
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+119
-20
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Scoring;
|
||||
@@ -21,6 +27,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[TestFixture]
|
||||
public class LegacyScoreDecoderTest
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
originalCulture = CultureInfo.CurrentCulture;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeManiaReplay()
|
||||
{
|
||||
@@ -44,6 +58,59 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCultureInvariance()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
var score = new Score
|
||||
{
|
||||
ScoreInfo = scoreInfo,
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// the "se" culture is used here, as it encodes the negative number sign as U+2212 MINUS SIGN,
|
||||
// rather than the classic ASCII U+002D HYPHEN-MINUS.
|
||||
CultureInfo.CurrentCulture = new CultureInfo("se");
|
||||
|
||||
var encodeStream = new MemoryStream();
|
||||
|
||||
var encoder = new LegacyScoreEncoder(score, beatmap);
|
||||
encoder.Encode(encodeStream);
|
||||
|
||||
var decodeStream = new MemoryStream(encodeStream.GetBuffer());
|
||||
|
||||
var decoder = new TestLegacyScoreDecoder();
|
||||
var decodedAfterEncode = decoder.Parse(decodeStream);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(decodedAfterEncode, Is.Not.Null);
|
||||
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Date, Is.EqualTo(scoreInfo.Date));
|
||||
|
||||
Assert.That(decodedAfterEncode.Replay.Frames.Count, Is.EqualTo(1));
|
||||
});
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
CultureInfo.CurrentCulture = originalCulture;
|
||||
}
|
||||
|
||||
private class TestLegacyScoreDecoder : LegacyScoreDecoder
|
||||
{
|
||||
private static readonly Dictionary<int, Ruleset> rulesets = new Ruleset[]
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
|
||||
{
|
||||
OnlineScoreID = 2,
|
||||
OnlineID = 2,
|
||||
BeatmapInfo = beatmapInfo,
|
||||
BeatmapInfoID = beatmapInfo.ID
|
||||
}, new ImportScoreTest.TestArchiveReader());
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -809,7 +809,7 @@ namespace osu.Game.Tests.Database
|
||||
// TODO: reimplement when we have score support in realm.
|
||||
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
|
||||
// {
|
||||
// OnlineScoreID = 2,
|
||||
// OnlineID = 2,
|
||||
// Beatmap = beatmap,
|
||||
// BeatmapInfoID = beatmap.ID
|
||||
// }, new ImportScoreTest.TestArchiveReader());
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||
|
||||
Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
|
||||
Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
|
||||
Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
|
||||
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
|
||||
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
|
||||
Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database
|
||||
Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultsPopulationRemovesExcess()
|
||||
{
|
||||
Assert.That(queryCount(), Is.EqualTo(0));
|
||||
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
// Add some excess bindings for an action which only supports 1.
|
||||
using (var realm = realmContextFactory.CreateContext())
|
||||
using (var transaction = realm.BeginWrite())
|
||||
{
|
||||
realm.Add(new RealmKeyBinding
|
||||
{
|
||||
Action = GlobalAction.Back,
|
||||
KeyCombination = new KeyCombination(InputKey.A)
|
||||
});
|
||||
|
||||
realm.Add(new RealmKeyBinding
|
||||
{
|
||||
Action = GlobalAction.Back,
|
||||
KeyCombination = new KeyCombination(InputKey.S)
|
||||
});
|
||||
|
||||
realm.Add(new RealmKeyBinding
|
||||
{
|
||||
Action = GlobalAction.Back,
|
||||
KeyCombination = new KeyCombination(InputKey.D)
|
||||
});
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
||||
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private int queryCount(GlobalAction? match = null)
|
||||
{
|
||||
using (var realm = realmContextFactory.CreateContext())
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.Audio;
|
||||
@@ -15,6 +16,7 @@ using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -167,7 +169,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
private class TestSkin : LegacySkin
|
||||
{
|
||||
public TestSkin(string resourceName, IStorageResourceProvider resources)
|
||||
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), resources, "skin.ini")
|
||||
: base(DefaultLegacySkin.CreateInfo(), new TestResourceStore(resourceName), resources, "skin.ini")
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -183,7 +185,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 +223,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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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", () =>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
@@ -94,7 +93,7 @@ namespace osu.Game.Tests.Online
|
||||
[Test]
|
||||
public void TestDeserialiseSubmittableScoreWithEmptyMods()
|
||||
{
|
||||
var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo });
|
||||
var score = new SubmittableScore(new ScoreInfo());
|
||||
|
||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
||||
|
||||
@@ -106,7 +105,6 @@ namespace osu.Game.Tests.Online
|
||||
{
|
||||
var score = new SubmittableScore(new ScoreInfo
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
|
||||
});
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
@@ -12,8 +13,12 @@ using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Tests.Resources
|
||||
{
|
||||
@@ -137,5 +142,63 @@ namespace osu.Game.Tests.Resources
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a test score model.
|
||||
/// </summary>
|
||||
/// <param name="ruleset">The ruleset for which the score was set against.</param>
|
||||
/// <returns></returns>
|
||||
public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null) =>
|
||||
CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First());
|
||||
|
||||
/// <summary>
|
||||
/// Create a test score model.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap for which the score was set against.</param>
|
||||
/// <returns></returns>
|
||||
public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap) => new ScoreInfo
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
BeatmapInfo = beatmap,
|
||||
Ruleset = beatmap.Ruleset,
|
||||
RulesetID = beatmap.Ruleset.ID ?? 0,
|
||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
Accuracy = 0.95,
|
||||
MaxCombo = 999,
|
||||
Position = 1,
|
||||
Rank = ScoreRank.S,
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
[HitResult.Miss] = 1,
|
||||
[HitResult.Meh] = 50,
|
||||
[HitResult.Ok] = 100,
|
||||
[HitResult.Good] = 200,
|
||||
[HitResult.Great] = 300,
|
||||
[HitResult.Perfect] = 320,
|
||||
[HitResult.SmallTickHit] = 50,
|
||||
[HitResult.SmallTickMiss] = 25,
|
||||
[HitResult.LargeTickHit] = 100,
|
||||
[HitResult.LargeTickMiss] = 50,
|
||||
[HitResult.SmallBonus] = 10,
|
||||
[HitResult.SmallBonus] = 50
|
||||
},
|
||||
};
|
||||
|
||||
private class TestModHardRock : ModHardRock
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
|
||||
private class TestModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineScoreID = 12345,
|
||||
OnlineID = 12345,
|
||||
};
|
||||
|
||||
var imported = await LoadScoreIntoOsu(osu, toImport);
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
Assert.AreEqual(toImport.Combo, imported.Combo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
|
||||
await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader());
|
||||
|
||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||
|
||||
// Note: A new score reference is used here since the import process mutates the original object to set an ID
|
||||
Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
|
||||
Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 }));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO
|
||||
Assert.That(score1, Is.EqualTo(score2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonMatchingByHash()
|
||||
{
|
||||
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
|
||||
ScoreInfo score2 = new ScoreInfo { Hash = "b" };
|
||||
|
||||
Assert.That(score1, Is.Not.EqualTo(score2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMatchingByHash()
|
||||
{
|
||||
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
|
||||
ScoreInfo score2 = new ScoreInfo { Hash = "a" };
|
||||
|
||||
Assert.That(score1, Is.EqualTo(score2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonMatchingByNull()
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Skinning;
|
||||
@@ -163,32 +164,109 @@ namespace osu.Game.Tests.Skins.IO
|
||||
assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestExportThenImportDefaultSkin() => runSkinTest(osu =>
|
||||
{
|
||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||
|
||||
skinManager.EnsureMutableSkin();
|
||||
|
||||
MemoryStream exportStream = new MemoryStream();
|
||||
|
||||
Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID;
|
||||
|
||||
skinManager.CurrentSkinInfo.Value.PerformRead(s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType());
|
||||
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
|
||||
Assert.Greater(exportStream.Length, 0);
|
||||
});
|
||||
|
||||
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
|
||||
|
||||
imported.Result.PerformRead(s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreNotEqual(originalSkinId, s.ID);
|
||||
Assert.AreEqual(typeof(DefaultSkin), s.CreateInstance(skinManager).GetType());
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestExportThenImportClassicSkin() => runSkinTest(osu =>
|
||||
{
|
||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||
|
||||
skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
|
||||
|
||||
skinManager.EnsureMutableSkin();
|
||||
|
||||
MemoryStream exportStream = new MemoryStream();
|
||||
|
||||
Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID;
|
||||
|
||||
skinManager.CurrentSkinInfo.Value.PerformRead(s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType());
|
||||
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
|
||||
Assert.Greater(exportStream.Length, 0);
|
||||
});
|
||||
|
||||
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
|
||||
|
||||
imported.Result.PerformRead(s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreNotEqual(originalSkinId, s.ID);
|
||||
Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType());
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
private void assertCorrectMetadata(SkinInfo import1, string name, string creator, OsuGameBase osu)
|
||||
private void assertCorrectMetadata(ILive<SkinInfo> import1, string name, string creator, OsuGameBase osu)
|
||||
{
|
||||
Assert.That(import1.Name, Is.EqualTo(name));
|
||||
Assert.That(import1.Creator, Is.EqualTo(creator));
|
||||
import1.PerformRead(i =>
|
||||
{
|
||||
Assert.That(i.Name, Is.EqualTo(name));
|
||||
Assert.That(i.Creator, Is.EqualTo(creator));
|
||||
|
||||
// for extra safety let's reconstruct the skin, reading from the skin.ini.
|
||||
var instance = import1.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager)));
|
||||
// for extra safety let's reconstruct the skin, reading from the skin.ini.
|
||||
var instance = i.CreateInstance((IStorageResourceProvider)osu.Dependencies.Get(typeof(SkinManager)));
|
||||
|
||||
Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
|
||||
Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
|
||||
Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
|
||||
Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertImportedBoth(SkinInfo import1, SkinInfo import2)
|
||||
private void assertImportedBoth(ILive<SkinInfo> import1, ILive<SkinInfo> import2)
|
||||
{
|
||||
Assert.That(import2.ID, Is.Not.EqualTo(import1.ID));
|
||||
Assert.That(import2.Hash, Is.Not.EqualTo(import1.Hash));
|
||||
Assert.That(import2.Files.Select(f => f.FileInfoID), Is.Not.EquivalentTo(import1.Files.Select(f => f.FileInfoID)));
|
||||
import1.PerformRead(i1 => import2.PerformRead(i2 =>
|
||||
{
|
||||
Assert.That(i2.ID, Is.Not.EqualTo(i1.ID));
|
||||
Assert.That(i2.Hash, Is.Not.EqualTo(i1.Hash));
|
||||
Assert.That(i2.Files.First(), Is.Not.EqualTo(i1.Files.First()));
|
||||
}));
|
||||
}
|
||||
|
||||
private void assertImportedOnce(SkinInfo import1, SkinInfo import2)
|
||||
private void assertImportedOnce(ILive<SkinInfo> import1, ILive<SkinInfo> import2)
|
||||
{
|
||||
Assert.That(import2.ID, Is.EqualTo(import1.ID));
|
||||
Assert.That(import2.Hash, Is.EqualTo(import1.Hash));
|
||||
Assert.That(import2.Files.Select(f => f.FileInfoID), Is.EquivalentTo(import1.Files.Select(f => f.FileInfoID)));
|
||||
import1.PerformRead(i1 => import2.PerformRead(i2 =>
|
||||
{
|
||||
Assert.That(i2.ID, Is.EqualTo(i1.ID));
|
||||
Assert.That(i2.Hash, Is.EqualTo(i1.Hash));
|
||||
Assert.That(i2.Files.First(), Is.EqualTo(i1.Files.First()));
|
||||
}));
|
||||
}
|
||||
|
||||
private MemoryStream createEmptyOsk()
|
||||
@@ -241,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
|
||||
{
|
||||
@@ -255,10 +333,10 @@ namespace osu.Game.Tests.Skins.IO
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SkinInfo> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
|
||||
private async Task<ILive<SkinInfo>> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
|
||||
{
|
||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||
return (await skinManager.Import(archive)).Value;
|
||||
return await skinManager.Import(archive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins
|
||||
private void load()
|
||||
{
|
||||
var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result;
|
||||
skin = skins.GetSkin(imported.Value);
|
||||
skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -5,15 +5,20 @@ 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;
|
||||
using osu.Game.Online.API;
|
||||
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
|
||||
{
|
||||
@@ -21,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]
|
||||
@@ -35,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()
|
||||
{
|
||||
@@ -77,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) =>
|
||||
@@ -119,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 { ID = 5 });
|
||||
AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLiveUnmanaged());
|
||||
}
|
||||
|
||||
private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
|
||||
@@ -18,7 +18,6 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
@@ -28,7 +27,6 @@ using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
FadeAccessibleResults results = null;
|
||||
|
||||
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
|
||||
{
|
||||
User = new APIUser { Username = "osu!" },
|
||||
BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo,
|
||||
Ruleset = Ruleset.Value,
|
||||
})));
|
||||
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
|
||||
|
||||
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
||||
|
||||
|
||||
@@ -11,17 +11,18 @@ 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;
|
||||
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.
|
||||
@@ -95,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;
|
||||
|
||||
@@ -227,7 +229,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
Child = new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@@ -248,6 +250,41 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
|
||||
public void TestNormal()
|
||||
{
|
||||
createTestCase(beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo));
|
||||
}
|
||||
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
|
||||
@@ -89,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
|
||||
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
|
||||
AddUntilStep("wait for drawable ruleset", () => Editor.ChildrenOfType<DrawableRuleset>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("paste object", () => Editor.Paste());
|
||||
|
||||
if (sameRuleset)
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
public void TestCreateNewBeatmap()
|
||||
{
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
|
||||
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged);
|
||||
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Edit;
|
||||
@@ -44,6 +45,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
protected override void LoadEditor()
|
||||
{
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
|
||||
SelectedMods.Value = new[] { new ModCinema() };
|
||||
base.LoadEditor();
|
||||
}
|
||||
|
||||
@@ -67,6 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
|
||||
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
||||
});
|
||||
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||
{
|
||||
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
||||
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||
}
|
||||
@@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("setup skins", () =>
|
||||
{
|
||||
skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
|
||||
skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLiveUnmanaged();
|
||||
currentBeatmapSkin = getBeatmapSkin();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("total number of results == 1", () =>
|
||||
{
|
||||
var score = new ScoreInfo();
|
||||
|
||||
((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
|
||||
|
||||
return score.Statistics.Values.Sum() == 1;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -251,7 +251,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestMutedNotificationMuteButton()
|
||||
{
|
||||
addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value);
|
||||
addVolumeSteps("mute button", () =>
|
||||
{
|
||||
// Importantly, in the case the volume is muted but the user has a volume level set, it should be retained.
|
||||
audioManager.VolumeTrack.Value = 0.5f;
|
||||
volumeOverlay.IsMuted.Value = true;
|
||||
}, () => !volumeOverlay.IsMuted.Value && audioManager.VolumeTrack.Value == 0.5f);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable)
|
||||
{
|
||||
return new APIScoreInfo
|
||||
return new APIScore
|
||||
{
|
||||
OnlineID = 2553163309,
|
||||
RulesetID = 0,
|
||||
|
||||
@@ -43,83 +43,88 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty<Mod>());
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
replay = new Replay();
|
||||
AddStep("Reset recorder state", cleanUpState);
|
||||
|
||||
Add(new GridContainer
|
||||
AddStep("Setup containers", () =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
replay = new Replay();
|
||||
|
||||
Add(new GridContainer
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
new Drawable[]
|
||||
{
|
||||
Recorder = recorder = new TestReplayRecorder(new Score
|
||||
recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
|
||||
})
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
Recorder = recorder = new TestReplayRecorder(new Score
|
||||
{
|
||||
new Box
|
||||
Replay = replay,
|
||||
ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
|
||||
})
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Recording",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Recording",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
||||
playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
||||
{
|
||||
new Box
|
||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Playback",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Playback",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
@@ -184,7 +189,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TearDownSteps]
|
||||
public void TearDown()
|
||||
{
|
||||
AddStep("stop recorder", () => recorder.Expire());
|
||||
AddStep("stop recorder", cleanUpState);
|
||||
}
|
||||
|
||||
private void cleanUpState()
|
||||
{
|
||||
// Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`.
|
||||
recorder?.RemoveAndDisposeImmediately();
|
||||
recorder = null;
|
||||
}
|
||||
|
||||
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneReplayRecording : OsuTestScene
|
||||
{
|
||||
private readonly TestRulesetInputManager playbackManager;
|
||||
|
||||
private readonly TestRulesetInputManager recordingManager;
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty<Mod>());
|
||||
|
||||
public TestSceneReplayRecording()
|
||||
{
|
||||
Replay replay = new Replay();
|
||||
|
||||
Add(new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Recorder = new TestReplayRecorder(new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
|
||||
})
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager?.ToLocalSpace(pos) ?? Vector2.Zero,
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Recording",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
|
||||
{
|
||||
GamefieldToScreenSpace = pos => playbackManager?.ToScreenSpace(pos) ?? Vector2.Zero,
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Playback",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
{
|
||||
public TestFramedReplayInputHandler(Replay replay)
|
||||
: base(replay)
|
||||
{
|
||||
}
|
||||
|
||||
public override void CollectPendingInputs(List<IInput> inputs)
|
||||
{
|
||||
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
|
||||
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });
|
||||
}
|
||||
}
|
||||
|
||||
public class TestConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private readonly Box box;
|
||||
|
||||
public TestConsumer()
|
||||
{
|
||||
Size = new Vector2(30);
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
Position = e.MousePosition;
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TestAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
box.Colour = Color4.White;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<TestAction> e)
|
||||
{
|
||||
box.Colour = Color4.Black;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestRulesetInputManager : RulesetInputManager<TestAction>
|
||||
{
|
||||
public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
: base(ruleset, variant, unique)
|
||||
{
|
||||
}
|
||||
|
||||
protected override KeyBindingContainer<TestAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
=> new TestKeyBindingContainer();
|
||||
|
||||
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
|
||||
{
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => new[]
|
||||
{
|
||||
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class TestReplayFrame : ReplayFrame
|
||||
{
|
||||
public Vector2 Position;
|
||||
|
||||
public List<TestAction> Actions = new List<TestAction>();
|
||||
|
||||
public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
|
||||
: base(time)
|
||||
{
|
||||
Position = position;
|
||||
Actions.AddRange(actions);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TestAction
|
||||
{
|
||||
Down,
|
||||
}
|
||||
|
||||
internal class TestReplayRecorder : ReplayRecorder<TestAction>
|
||||
{
|
||||
public TestReplayRecorder(Score target)
|
||||
: base(target)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TestAction> actions, ReplayFrame previousFrame) =>
|
||||
new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private TestReplayRecorder recorder;
|
||||
|
||||
private readonly ManualClock manualClock = new ManualClock();
|
||||
private ManualClock manualClock;
|
||||
|
||||
private OsuSpriteText latencyDisplay;
|
||||
|
||||
@@ -66,113 +66,121 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty<Mod>());
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
replay = new Replay();
|
||||
AddStep("Reset recorder state", cleanUpState);
|
||||
|
||||
users.BindTo(spectatorClient.PlayingUsers);
|
||||
users.BindCollectionChanged((obj, args) =>
|
||||
AddStep("Setup containers", () =>
|
||||
{
|
||||
switch (args.Action)
|
||||
replay = new Replay();
|
||||
manualClock = new ManualClock();
|
||||
|
||||
spectatorClient.OnNewFrames += onNewFrames;
|
||||
|
||||
users.BindTo(spectatorClient.PlayingUsers);
|
||||
users.BindCollectionChanged((obj, args) =>
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Debug.Assert(args.NewItems != null);
|
||||
|
||||
foreach (int user in args.NewItems)
|
||||
{
|
||||
if (user == api.LocalUser.Value.Id)
|
||||
spectatorClient.WatchUser(user);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Debug.Assert(args.OldItems != null);
|
||||
|
||||
foreach (int user in args.OldItems)
|
||||
{
|
||||
if (user == api.LocalUser.Value.Id)
|
||||
spectatorClient.StopWatchingUser(user);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
|
||||
spectatorClient.OnNewFrames += onNewFrames;
|
||||
|
||||
Add(new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
switch (args.Action)
|
||||
{
|
||||
recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Debug.Assert(args.NewItems != null);
|
||||
|
||||
foreach (int user in args.NewItems)
|
||||
{
|
||||
if (user == api.LocalUser.Value.Id)
|
||||
spectatorClient.WatchUser(user);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Debug.Assert(args.OldItems != null);
|
||||
|
||||
foreach (int user in args.OldItems)
|
||||
{
|
||||
if (user == api.LocalUser.Value.Id)
|
||||
spectatorClient.StopWatchingUser(user);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
Recorder = recorder = new TestReplayRecorder
|
||||
new Drawable[]
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
recordingManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
new Box
|
||||
Recorder = recorder = new TestReplayRecorder
|
||||
{
|
||||
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Brown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Sending",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Sending",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Clock = new FramedClock(manualClock),
|
||||
ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
|
||||
{
|
||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Receiving",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
playbackManager = new TestRulesetInputManager(TestSceneModSettings.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
Clock = new FramedClock(manualClock),
|
||||
ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
|
||||
{
|
||||
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.DarkBlue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Receiving",
|
||||
Scale = new Vector2(3),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new TestInputConsumer()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
latencyDisplay = new OsuSpriteText()
|
||||
};
|
||||
});
|
||||
|
||||
Add(latencyDisplay = new OsuSpriteText());
|
||||
});
|
||||
}
|
||||
|
||||
private void onNewFrames(int userId, FrameDataBundle frames)
|
||||
{
|
||||
@@ -189,6 +197,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("Wait for user input", () => { });
|
||||
}
|
||||
|
||||
private double latency = SpectatorClient.TIME_BETWEEN_SENDS;
|
||||
@@ -232,11 +241,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TearDownSteps]
|
||||
public void TearDown()
|
||||
{
|
||||
AddStep("stop recorder", () =>
|
||||
{
|
||||
recorder.Expire();
|
||||
spectatorClient.OnNewFrames -= onNewFrames;
|
||||
});
|
||||
AddStep("stop recorder", cleanUpState);
|
||||
}
|
||||
|
||||
private void cleanUpState()
|
||||
{
|
||||
// Ensure previous recorder is disposed else it may affect the global playing state of `SpectatorClient`.
|
||||
recorder?.RemoveAndDisposeImmediately();
|
||||
recorder = null;
|
||||
spectatorClient.OnNewFrames -= onNewFrames;
|
||||
}
|
||||
|
||||
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
private TestToolbar toolbar;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
private IRulesetStore rulesets { get; set; }
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,22 +72,22 @@ 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)
|
||||
{
|
||||
AddStep("click edit button", () =>
|
||||
AddStep("click add button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().AddOrEditPlaylistButton);
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user