Merge branch 'master' into fix-unsafe-skinnable-sample-play

This commit is contained in:
Bartłomiej Dach
2021-03-18 19:51:46 +01:00
278 changed files with 2964 additions and 1404 deletions

View File

@ -195,3 +195,6 @@ dotnet_diagnostic.IDE0069.severity = none
#Disable operator overloads requiring alternate named methods #Disable operator overloads requiring alternate named methods
dotnet_diagnostic.CA2225.severity = none dotnet_diagnostic.CA2225.severity = none
# Banned APIs
dotnet_diagnostic.RS0030.severity = error

View File

@ -13,4 +13,6 @@ about: Issues regarding encountered bugs.
*please attach logs here, which are located at:* *please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),* - `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).* - `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
--> -->

View File

@ -13,6 +13,8 @@ about: Issues regarding crashes or permanent freezes.
*please attach logs here, which are located at:* *please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),* - `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).* - `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
--> -->
**Computer Specifications:** **Computer Specifications:**

View File

@ -7,3 +7,4 @@ M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.

View File

@ -30,7 +30,7 @@
<Rule Id="CA1819" Action="None" /> <Rule Id="CA1819" Action="None" />
<Rule Id="CA1822" Action="None" /> <Rule Id="CA1822" Action="None" />
<Rule Id="CA1823" Action="None" /> <Rule Id="CA1823" Action="None" />
<Rule Id="CA2007" Action="None" /> <Rule Id="CA2007" Action="Warning" />
<Rule Id="CA2214" Action="None" /> <Rule Id="CA2214" Action="None" />
<Rule Id="CA2227" Action="None" /> <Rule Id="CA2227" Action="None" />
</Rules> </Rules>

View File

@ -18,7 +18,7 @@
<ItemGroup Label="Code Analysis"> <ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" /> <AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Code Analysis"> <PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.222.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.317.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -100,15 +100,15 @@ namespace osu.Android
// copy to an arbitrary-access memory stream to be able to proceed with the import. // copy to an arbitrary-access memory stream to be able to proceed with the import.
var copy = new MemoryStream(); var copy = new MemoryStream();
using (var stream = ContentResolver.OpenInputStream(uri)) using (var stream = ContentResolver.OpenInputStream(uri))
await stream.CopyToAsync(copy); await stream.CopyToAsync(copy).ConfigureAwait(false);
lock (tasks) lock (tasks)
{ {
tasks.Add(new ImportTask(copy, filename)); tasks.Add(new ImportTask(copy, filename));
} }
})); })).ConfigureAwait(false);
await game.Import(tasks.ToArray()); await game.Import(tasks.ToArray()).ConfigureAwait(false);
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
} }
} }

View File

@ -136,24 +136,12 @@ namespace osu.Desktop
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
switch (host.Window) var desktopWindow = (SDL2DesktopWindow)host.Window;
{
// Legacy osuTK DesktopGameWindow
case OsuTKDesktopWindow desktopGameWindow:
desktopGameWindow.CursorState |= CursorState.Hidden;
desktopGameWindow.SetIconFromStream(iconStream);
desktopGameWindow.Title = Name;
desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
break;
// SDL2 DesktopWindow
case SDL2DesktopWindow desktopWindow:
desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(iconStream); desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name; desktopWindow.Title = Name;
desktopWindow.DragDrop += f => fileDrop(new[] { f }); desktopWindow.DragDrop += f => fileDrop(new[] { f });
break;
}
} }
private void fileDrop(string[] filePaths) private void fileDrop(string[] filePaths)

View File

@ -22,9 +22,8 @@ namespace osu.Desktop
{ {
// Back up the cwd before DesktopGameHost changes it // Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory; var cwd = Environment.CurrentDirectory;
bool useOsuTK = args.Contains("--tk");
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK)) using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
{ {
host.ExceptionThrown += handleException; host.ExceptionThrown += handleException;

View File

@ -42,7 +42,7 @@ namespace osu.Desktop.Updater
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
} }
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync(); protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{ {
@ -51,9 +51,9 @@ namespace osu.Desktop.Updater
try try
{ {
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false);
var info = await updateManager.CheckForUpdate(!useDeltaPatching); var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
if (info.ReleasesToApply.Count == 0) if (info.ReleasesToApply.Count == 0)
{ {
@ -79,12 +79,12 @@ namespace osu.Desktop.Updater
try try
{ {
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f); await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.Progress = 0; notification.Progress = 0;
notification.Text = @"Installing update..."; notification.Text = @"Installing update...";
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
updatePending = true; updatePending = true;
@ -97,7 +97,7 @@ namespace osu.Desktop.Updater
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
// try again without deltas. // try again without deltas.
await checkForUpdateAsync(false, notification); await checkForUpdateAsync(false, notification).ConfigureAwait(false);
scheduleRecheck = false; scheduleRecheck = false;
} }
else else
@ -116,7 +116,7 @@ namespace osu.Desktop.Updater
if (scheduleRecheck) if (scheduleRecheck)
{ {
// check again in 30 minutes. // check again in 30 minutes.
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30); Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
} }
} }

View File

@ -27,8 +27,8 @@
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageReference Include="System.IO.Packaging" Version="5.0.0" /> <PackageReference Include="System.IO.Packaging" Version="5.0.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" /> <PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.4" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" /> <PackageReference Include="DiscordRichPresence" Version="1.0.175" />
</ItemGroup> </ItemGroup>

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
LocalConfig.Set(OsuSetting.IncreaseFirstObjectVisibility, false); LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
} }
[Test] [Test]

View File

@ -202,7 +202,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public void TestHitLightingColour() public void TestHitLightingColour()
{ {
var fruitColour = SkinConfiguration.DefaultComboColours[1]; var fruitColour = SkinConfiguration.DefaultComboColours[1];
AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true)); AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit())); AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("correct hit lighting colour", () => AddAssert("correct hit lighting colour", () =>
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour); catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test] [Test]
public void TestHitLightingDisabled() public void TestHitLightingDisabled()
{ {
AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false)); AddStep("disable hit lighting", () => config.SetValue(OsuSetting.HitLighting, false));
AddStep("catch fruit", () => attemptCatch(new Fruit())); AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any()); AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
} }

View File

@ -2,10 +2,10 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -21,6 +21,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Catch.Skinning.Legacy; using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -50,40 +51,40 @@ namespace osu.Game.Rulesets.Catch
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new CatchModNightcore(); yield return new CatchModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime(); yield return new CatchModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new CatchModPerfect(); yield return new CatchModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath(); yield return new CatchModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new CatchModCinema(); yield return new CatchModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new CatchModAutoplay(); yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new CatchModEasy(); yield return new CatchModEasy();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new CatchModFlashlight(); yield return new CatchModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new CatchModHalfTime(); yield return new CatchModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new CatchModHardRock(); yield return new CatchModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new CatchModHidden(); yield return new CatchModHidden();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new CatchModNoFail(); yield return new CatchModNoFail();
if (mods.HasFlag(LegacyMods.Relax)) if (mods.HasFlagFast(LegacyMods.Relax))
yield return new CatchModRelax(); yield return new CatchModRelax();
} }

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{ {
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new Skill[] return new Skill[]
{ {
new Movement(halfCatcherWidth), new Movement(mods, halfCatcherWidth),
}; };
} }

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{ {
@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
private float lastDistanceMoved; private float lastDistanceMoved;
private double lastStrainTime; private double lastStrainTime;
public Movement(float halfCatcherWidth) public Movement(Mod[] mods, float halfCatcherWidth)
: base(mods)
{ {
HalfCatcherWidth = halfCatcherWidth; HalfCatcherWidth = halfCatcherWidth;
} }

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor) private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor); => hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor) private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor)); => verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));

View File

@ -2,10 +2,10 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.78, 0.3, 0); return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03); return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.43, 0.08, 0); return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(StartTime, 0.56, 0.18, 0); return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
@ -157,13 +158,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 2.5) if (ConversionDifficulty > 2.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.3, 0, 0); return generateNRandomNotes(StartTime, 0.3, 0, 0);
return generateNRandomNotes(StartTime, 0.37, 0.08, 0); return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
} }
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.17, 0, 0); return generateNRandomNotes(StartTime, 0.17, 0, 0);
return generateNRandomNotes(StartTime, 0.27, 0, 0); return generateNRandomNotes(StartTime, 0.27, 0, 0);
@ -221,7 +222,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
int lastColumn = nextColumn; int lastColumn = nextColumn;
@ -373,7 +374,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes) if (canGenerateTwoNotes)
@ -406,7 +407,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int endTime = startTime + SegmentDuration * SpanCount; int endTime = startTime + SegmentDuration * SpanCount;
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
for (int i = 0; i < columnRepeat; i++) for (int i = 0; i < columnRepeat; i++)
@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern); holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
// Create the hold note // Create the hold note

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osuTK; using osuTK;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
else else
convertType |= PatternType.LowProbability; convertType |= PatternType.LowProbability;
if (!convertType.HasFlag(PatternType.KeepSingle)) if (!convertType.HasFlagFast(PatternType.KeepSingle))
{ {
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8) if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror; convertType |= PatternType.Mirror;
@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any()) if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by copying the last hit objects in reverse-column order // Generate a new pattern by copying the last hit objects in reverse-column order
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key // If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0) && (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column // Make sure the last column was not the centre column
@ -126,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any()) if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by placing on the already filled columns // Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (PreviousPattern.HitObjects.Count() == 1) if (PreviousPattern.HitObjects.Count() == 1)
{ {
if (convertType.HasFlag(PatternType.Stair)) if (convertType.HasFlagFast(PatternType.Stair))
{ {
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
int targetColumn = lastColumn + 1; int targetColumn = lastColumn + 1;
@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if (convertType.HasFlag(PatternType.ReverseStair)) if (convertType.HasFlagFast(PatternType.ReverseStair))
{ {
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
int targetColumn = lastColumn - 1; int targetColumn = lastColumn - 1;
@ -163,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
} }
if (convertType.HasFlag(PatternType.KeepSingle)) if (convertType.HasFlagFast(PatternType.KeepSingle))
return generateRandomNotes(1); return generateRandomNotes(1);
if (convertType.HasFlag(PatternType.Mirror)) if (convertType.HasFlagFast(PatternType.Mirror))
{ {
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.78, 0.42, 0, 0); return generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0); return generateRandomPattern(1, 0.62, 0, 0);
@ -186,7 +187,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.35, 0.08, 0, 0); return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0); return generateRandomPattern(0.52, 0.15, 0, 0);
@ -194,7 +195,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 2) if (ConversionDifficulty > 2)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.18, 0, 0, 0); return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0); return generateRandomPattern(0.45, 0, 0, 0);
@ -207,9 +208,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
foreach (var obj in p.HitObjects) foreach (var obj in p.HitObjects)
{ {
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair; StairType = PatternType.ReverseStair;
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
StairType = PatternType.Stair; StairType = PatternType.Stair;
} }
@ -229,7 +230,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack); bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
if (!allowStacking) if (!allowStacking)
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int getNextColumn(int last) int getNextColumn(int last)
{ {
if (convertType.HasFlag(PatternType.Gathered)) if (convertType.HasFlagFast(PatternType.Gathered))
{ {
last++; last++;
if (last == TotalColumns) if (last == TotalColumns)
@ -296,7 +297,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
{ {
if (convertType.HasFlag(PatternType.ForceNotStack)) if (convertType.HasFlagFast(PatternType.ForceNotStack))
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
var pattern = new Pattern(); var pattern = new Pattern();

View File

@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Configuration
{ {
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
} }
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings public override TrackedSettings CreateTrackedSettings() => new TrackedSettings

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input; protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Strain(((ManiaBeatmap)beatmap).TotalColumns) new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
}; };
protected override Mod[] DifficultyAdjustmentMods protected override Mod[] DifficultyAdjustmentMods

View File

@ -6,6 +6,7 @@ using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills namespace osu.Game.Rulesets.Mania.Difficulty.Skills
@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
private double individualStrain; private double individualStrain;
private double overallStrain; private double overallStrain;
public Strain(int totalColumns) public Strain(Mod[] mods, int totalColumns)
: base(mods)
{ {
holdEndTimes = new double[totalColumns]; holdEndTimes = new double[totalColumns];
individualStrains = new double[totalColumns]; individualStrains = new double[totalColumns];

View File

@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit
int minColumn = int.MaxValue; int minColumn = int.MaxValue;
int maxColumn = int.MinValue; int maxColumn = int.MinValue;
// find min/max in an initial pass before actually performing the movement.
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>()) foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
{ {
if (obj.Column < minColumn) if (obj.Column < minColumn)
@ -55,8 +56,11 @@ namespace osu.Game.Rulesets.Mania.Edit
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>()) EditorBeatmap.PerformOnSelection(h =>
obj.Column += columnDelta; {
if (h is ManiaHitObject maniaObj)
maniaObj.Column += columnDelta;
});
} }
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
@ -59,76 +60,76 @@ namespace osu.Game.Rulesets.Mania
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new ManiaModNightcore(); yield return new ManiaModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new ManiaModDoubleTime(); yield return new ManiaModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new ManiaModPerfect(); yield return new ManiaModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath(); yield return new ManiaModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new ManiaModCinema(); yield return new ManiaModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new ManiaModAutoplay(); yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new ManiaModEasy(); yield return new ManiaModEasy();
if (mods.HasFlag(LegacyMods.FadeIn)) if (mods.HasFlagFast(LegacyMods.FadeIn))
yield return new ManiaModFadeIn(); yield return new ManiaModFadeIn();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new ManiaModFlashlight(); yield return new ManiaModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new ManiaModHalfTime(); yield return new ManiaModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new ManiaModHardRock(); yield return new ManiaModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new ManiaModHidden(); yield return new ManiaModHidden();
if (mods.HasFlag(LegacyMods.Key1)) if (mods.HasFlagFast(LegacyMods.Key1))
yield return new ManiaModKey1(); yield return new ManiaModKey1();
if (mods.HasFlag(LegacyMods.Key2)) if (mods.HasFlagFast(LegacyMods.Key2))
yield return new ManiaModKey2(); yield return new ManiaModKey2();
if (mods.HasFlag(LegacyMods.Key3)) if (mods.HasFlagFast(LegacyMods.Key3))
yield return new ManiaModKey3(); yield return new ManiaModKey3();
if (mods.HasFlag(LegacyMods.Key4)) if (mods.HasFlagFast(LegacyMods.Key4))
yield return new ManiaModKey4(); yield return new ManiaModKey4();
if (mods.HasFlag(LegacyMods.Key5)) if (mods.HasFlagFast(LegacyMods.Key5))
yield return new ManiaModKey5(); yield return new ManiaModKey5();
if (mods.HasFlag(LegacyMods.Key6)) if (mods.HasFlagFast(LegacyMods.Key6))
yield return new ManiaModKey6(); yield return new ManiaModKey6();
if (mods.HasFlag(LegacyMods.Key7)) if (mods.HasFlagFast(LegacyMods.Key7))
yield return new ManiaModKey7(); yield return new ManiaModKey7();
if (mods.HasFlag(LegacyMods.Key8)) if (mods.HasFlagFast(LegacyMods.Key8))
yield return new ManiaModKey8(); yield return new ManiaModKey8();
if (mods.HasFlag(LegacyMods.Key9)) if (mods.HasFlagFast(LegacyMods.Key9))
yield return new ManiaModKey9(); yield return new ManiaModKey9();
if (mods.HasFlag(LegacyMods.KeyCoop)) if (mods.HasFlagFast(LegacyMods.KeyCoop))
yield return new ManiaModDualStages(); yield return new ManiaModDualStages();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new ManiaModNoFail(); yield return new ManiaModNoFail();
if (mods.HasFlag(LegacyMods.Random)) if (mods.HasFlagFast(LegacyMods.Random))
yield return new ManiaModRandom(); yield return new ManiaModRandom();
if (mods.HasFlag(LegacyMods.Mirror)) if (mods.HasFlagFast(LegacyMods.Mirror))
yield return new ManiaModMirror(); yield return new ManiaModMirror();
} }

View File

@ -1,18 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites; using System;
using osu.Game.Graphics; using System.Linq;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModFadeIn : ManiaModHidden public class ManiaModFadeIn : ManiaModPlayfieldCover
{ {
public override string Name => "Fade In"; public override string Name => "Fade In";
public override string Acronym => "FI"; public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModHidden;
public override string Description => @"Keys appear out of nowhere!"; public override string Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
} }

View File

@ -3,43 +3,17 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject> public class ManiaModHidden : ManiaModPlayfieldCover
{ {
public override string Description => @"Keys fade out before you hit them!"; public override string Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
/// <summary> public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
/// The direction in which the cover should expand.
/// </summary>
protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset) protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
{
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
{
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
Container hocParent = (Container)hoc.Parent;
hocParent.Remove(hoc);
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
{
c.RelativeSizeAxes = Axes.Both;
c.Direction = ExpandDirection;
c.Coverage = 0.5f;
}));
}
}
} }
} }

View File

@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
{
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
/// <summary>
/// The direction in which the cover should expand.
/// </summary>
protected abstract CoverExpandDirection ExpandDirection { get; }
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
{
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
{
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
Container hocParent = (Container)hoc.Parent;
hocParent.Remove(hoc);
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
{
c.RelativeSizeAxes = Axes.Both;
c.Direction = ExpandDirection;
c.Coverage = 0.5f;
}));
}
}
}
}

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestHitLightingDisabled() public void TestHitLightingDisabled()
{ {
AddStep("hit lighting disabled", () => config.Set(OsuSetting.HitLighting, false)); AddStep("hit lighting disabled", () => config.SetValue(OsuSetting.HitLighting, false));
showResult(HitResult.Great); showResult(HitResult.Great);
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestHitLightingEnabled() public void TestHitLightingEnabled()
{ {
AddStep("hit lighting enabled", () => config.Set(OsuSetting.HitLighting, true)); AddStep("hit lighting enabled", () => config.SetValue(OsuSetting.HitLighting, true));
showResult(HitResult.Great); showResult(HitResult.Great);

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddSliderStep("circle size", 0f, 10f, 0f, val => AddSliderStep("circle size", 0f, 10f, 0f, val =>
{ {
config.Set(OsuSetting.AutoCursorSize, true); config.SetValue(OsuSetting.AutoCursorSize, true);
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
Scheduler.AddOnce(recreate); Scheduler.AddOnce(recreate);
}); });
@ -64,21 +64,21 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase(10, 1.5f)] [TestCase(10, 1.5f)]
public void TestSizing(int circleSize, float userScale) public void TestSizing(int circleSize, float userScale)
{ {
AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale)); AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
AddStep("turn on autosizing", () => config.Set(OsuSetting.AutoCursorSize, true)); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
AddStep("load content", loadContent); AddStep("load content", loadContent);
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
AddStep("set user scale to 1", () => config.Set(OsuSetting.GameplayCursorSize, 1f)); AddStep("set user scale to 1", () => config.SetValue(OsuSetting.GameplayCursorSize, 1f));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize)); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize));
AddStep("turn off autosizing", () => config.Set(OsuSetting.AutoCursorSize, false)); AddStep("turn off autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, false));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1);
AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale)); AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
} }

View File

@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
AddStep("enable user provider", () => testUserSkin.Enabled = true); AddStep("enable user provider", () => testUserSkin.Enabled = true);
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true)); AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
checkNextHitObject("beatmap"); checkNextHitObject("beatmap");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false)); AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
checkNextHitObject("user"); checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false); AddStep("disable user provider", () => testUserSkin.Enabled = false);
@ -57,20 +57,20 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
AddStep("enable user provider", () => testUserSkin.Enabled = true); AddStep("enable user provider", () => testUserSkin.Enabled = true);
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true)); AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true)); AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true));
checkNextHitObject("beatmap"); checkNextHitObject("beatmap");
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true)); AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false)); AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false));
checkNextHitObject("beatmap"); checkNextHitObject("beatmap");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false)); AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true)); AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true));
checkNextHitObject("user"); checkNextHitObject("user");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false)); AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false)); AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false));
checkNextHitObject("user"); checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false); AddStep("disable user provider", () => testUserSkin.Enabled = false);

View File

@ -2,10 +2,10 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -17,10 +17,10 @@ namespace osu.Game.Rulesets.Osu.Configuration
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
{ {
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(OsuRulesetSetting.SnakingInSliders, true); SetDefault(OsuRulesetSetting.SnakingInSliders, true);
Set(OsuRulesetSetting.SnakingOutSliders, true); SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
Set(OsuRulesetSetting.ShowCursorTrail, true); SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
} }
} }

View File

@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Aim(), new Aim(mods),
new Speed() new Speed(mods)
}; };
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -17,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double angle_bonus_begin = Math.PI / 3; private const double angle_bonus_begin = Math.PI / 3;
private const double timing_threshold = 107; private const double timing_threshold = 107;
public Aim(Mod[] mods)
: base(mods)
{
}
protected override double SkillMultiplier => 26.25; protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15; protected override double StrainDecayBase => 0.15;

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -27,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double max_speed_bonus = 45; // ~330BPM private const double max_speed_bonus = 45; // ~330BPM
private const double speed_balancing_factor = 40; private const double speed_balancing_factor = 40;
public Speed(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
if (current.BaseObject is Spinner) if (current.BaseObject is Spinner)

View File

@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.Judgements
/// </example> /// </example>
public float RateAdjustedRotation; public float RateAdjustedRotation;
/// <summary>
/// Time instant at which the spin was started (the first user input which caused an increase in spin).
/// </summary>
public double? TimeStarted;
/// <summary> /// <summary>
/// Time instant at which the spinner has been completed (the user has executed all required spins). /// Time instant at which the spinner has been completed (the user has executed all required spins).
/// Will be null if all required spins haven't been completed. /// Will be null if all required spins haven't been completed.

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation; public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
public bool PerformFail() => false; public bool PerformFail() => false;

View File

@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
internal class OsuModTraceable : ModWithVisibilityAdjustment public class OsuModTraceable : ModWithVisibilityAdjustment
{ {
public override string Name => "Traceable"; public override string Name => "Traceable";
public override string Acronym => "TC"; public override string Acronym => "TC";

View File

@ -158,6 +158,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected override void UpdateStartTimeStateTransforms()
{
base.UpdateStartTimeStateTransforms();
if (Result?.TimeStarted is double startTime)
{
using (BeginAbsoluteSequence(startTime))
fadeInCounter();
}
}
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
{ {
base.UpdateHitStateTransforms(state); base.UpdateHitStateTransforms(state);
@ -262,13 +273,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.UpdateAfterChildren(); base.UpdateAfterChildren();
if (!SpmCounter.IsPresent && RotationTracker.Tracking) if (!SpmCounter.IsPresent && RotationTracker.Tracking)
SpmCounter.FadeIn(HitObject.TimeFadeIn); {
Result.TimeStarted ??= Time.Current;
fadeInCounter();
}
// don't update after end time to avoid the rate display dropping during fade out.
// this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period.
if (Time.Current <= HitObject.EndTime)
SpmCounter.SetRotation(Result.RateAdjustedRotation); SpmCounter.SetRotation(Result.RateAdjustedRotation);
updateBonusScore(); updateBonusScore();
} }
private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn);
private int wholeSpins; private int wholeSpins;
private void updateBonusScore() private void updateBonusScore()

View File

@ -29,6 +29,7 @@ using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.Statistics;
@ -58,52 +59,52 @@ namespace osu.Game.Rulesets.Osu
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new OsuModNightcore(); yield return new OsuModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new OsuModDoubleTime(); yield return new OsuModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new OsuModPerfect(); yield return new OsuModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new OsuModSuddenDeath(); yield return new OsuModSuddenDeath();
if (mods.HasFlag(LegacyMods.Autopilot)) if (mods.HasFlagFast(LegacyMods.Autopilot))
yield return new OsuModAutopilot(); yield return new OsuModAutopilot();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new OsuModCinema(); yield return new OsuModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new OsuModAutoplay(); yield return new OsuModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new OsuModEasy(); yield return new OsuModEasy();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new OsuModFlashlight(); yield return new OsuModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new OsuModHalfTime(); yield return new OsuModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new OsuModHardRock(); yield return new OsuModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new OsuModHidden(); yield return new OsuModHidden();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new OsuModNoFail(); yield return new OsuModNoFail();
if (mods.HasFlag(LegacyMods.Relax)) if (mods.HasFlagFast(LegacyMods.Relax))
yield return new OsuModRelax(); yield return new OsuModRelax();
if (mods.HasFlag(LegacyMods.SpunOut)) if (mods.HasFlagFast(LegacyMods.SpunOut))
yield return new OsuModSpunOut(); yield return new OsuModSpunOut();
if (mods.HasFlag(LegacyMods.Target)) if (mods.HasFlagFast(LegacyMods.Target))
yield return new OsuModTarget(); yield return new OsuModTarget();
if (mods.HasFlag(LegacyMods.TouchDevice)) if (mods.HasFlagFast(LegacyMods.TouchDevice))
yield return new OsuModTouchDevice(); yield return new OsuModTouchDevice();
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public string Text public string Text
{ {
get => number.Text; get => number.Text.ToString();
set => number.Text = value; set => number.Text = value;
} }

View File

@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
AddInternal(scaleContainer = new Container AddInternal(scaleContainer = new Container
{ {
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Y = SPINNER_Y_CENTRE,
Children = new Drawable[] Children = new Drawable[]
{ {
glow = new Sprite glow = new Sprite

View File

@ -33,30 +33,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
AddInternal(new Container AddRangeInternal(new Drawable[]
{
// the old-style spinner relied heavily on absolute screen-space coordinate values.
// wrap everything in a container simulating absolute coords to preserve alignment
// as there are skins that depend on it.
Width = 640,
Height = 480,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{ {
new Sprite new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-background"), Texture = source.GetTexture("spinner-background"),
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE),
Y = SPINNER_Y_CENTRE,
}, },
disc = new Sprite disc = new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-circle"), Texture = source.GetTexture("spinner-circle"),
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE),
Y = SPINNER_Y_CENTRE,
}, },
metre = new Container metre = new Container
{ {
@ -64,8 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// this anchor makes no sense, but that's what stable uses. // this anchor makes no sense, but that's what stable uses.
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
// adjustment for stable (metre has additional offset) Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET },
Margin = new MarginPadding { Top = 20 },
Masking = true, Masking = true,
Child = metreSprite = new Sprite Child = metreSprite = new Sprite
{ {
@ -75,7 +67,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE)
} }
} }
}
}); });
} }

View File

@ -16,6 +16,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
public abstract class LegacySpinner : CompositeDrawable public abstract class LegacySpinner : CompositeDrawable
{ {
/// <remarks>
/// All constants are in osu!stable's gamefield space, which is shifted 16px downwards.
/// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space.
/// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable)
/// </remarks>
protected const float SPINNER_TOP_OFFSET = 45f - 16f;
protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f;
protected const float SPRITE_SCALE = 0.625f; protected const float SPRITE_SCALE = 0.625f;
protected DrawableSpinner DrawableSpinner { get; private set; } protected DrawableSpinner DrawableSpinner { get; private set; }
@ -26,7 +35,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject, ISkinSource source) private void load(DrawableHitObject drawableHitObject, ISkinSource source)
{ {
RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre;
Origin = Anchor.Centre;
// osu!stable positions spinner components in window-space (as opposed to gamefield-space). This is a 640x480 area taking up the entire screen.
// In lazer, the gamefield-space positional transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to make this area take up the entire window space.
Size = new Vector2(640, 480);
Position = new Vector2(0, -8f);
DrawableSpinner = (DrawableSpinner)drawableHitObject; DrawableSpinner = (DrawableSpinner)drawableHitObject;
@ -34,22 +49,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
spin = new Sprite spin = new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Depth = float.MinValue, Depth = float.MinValue,
Texture = source.GetTexture("spinner-spin"), Texture = source.GetTexture("spinner-spin"),
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Y = 120 - 45 // offset temporarily to avoid overlapping default spin counter Y = SPINNER_TOP_OFFSET + 335,
}, },
clear = new Sprite clear = new Sprite
{ {
Anchor = Anchor.Centre, Alpha = 0,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Depth = float.MinValue, Depth = float.MinValue,
Alpha = 0,
Texture = source.GetTexture("spinner-clear"), Texture = source.GetTexture("spinner-clear"),
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Y = -60 Y = SPINNER_TOP_OFFSET + 115,
}, },
}); });
} }

View File

@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
@ -13,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture] [TestFixture]
public class TestSceneHitExplosion : TaikoSkinnableTestScene public class TestSceneHitExplosion : TaikoSkinnableTestScene
{ {
protected override double TimePerAction => 100;
[Test] [Test]
public void TestNormalHit() public void TestNormalHit()
{ {
@ -21,11 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss))));
} }
[Test] [TestCase(HitResult.Great)]
public void TestStrongHit([Values(false, true)] bool hitBoth) [TestCase(HitResult.Ok)]
public void TestStrongHit(HitResult type)
{ {
AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); AddStep("create hit", () => SetContents(() => getContentFor(createStrongHit(type))));
AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth)))); AddStep("visualise second hit",
() => this.ChildrenOfType<HitExplosion>()
.ForEach(e => e.VisualiseSecondHit(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()))));
} }
private Drawable getContentFor(DrawableTestHit hit) private Drawable getContentFor(DrawableTestHit hit)
@ -38,17 +47,17 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
// the hit needs to be added to hierarchy in order for nested objects to be created correctly. // the hit needs to be added to hierarchy in order for nested objects to be created correctly.
// setting zero alpha is supposed to prevent the test from looking broken. // setting zero alpha is supposed to prevent the test from looking broken.
hit.With(h => h.Alpha = 0), hit.With(h => h.Alpha = 0),
new HitExplosion(hit, hit.Type) new HitExplosion(hit.Type)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
} }.With(explosion => explosion.Apply(hit))
} }
}; };
} }
private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth); private DrawableTestHit createStrongHit(HitResult type) => new DrawableTestStrongHit(Time.Current, type);
} }
} }

View File

@ -11,7 +11,6 @@ using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -108,12 +107,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
{ {
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
Hit hit = new Hit(); Hit hit = new Hit { StartTime = DrawableRuleset.Playfield.Time.Current };
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h); DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult });
} }
private void addStrongHitJudgement(bool kiai) private void addStrongHitJudgement(bool kiai)
@ -122,6 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit hit = new Hit Hit hit = new Hit
{ {
StartTime = DrawableRuleset.Playfield.Time.Current,
IsStrong = true, IsStrong = true,
Samples = createSamples(strong: true) Samples = createSamples(strong: true)
}; };
@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
DrawableRuleset.Playfield.Add(h); DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great });
} }
private void addMissJudgement() private void addMissJudgement()

View File

@ -2,10 +2,10 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// </summary> /// </summary>
private int currentMonoLength; private int currentMonoLength;
public Colour(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
// changing from/to a drum roll or a swell does not constitute a colour change. // changing from/to a drum roll or a swell does not constitute a colour change.

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -47,6 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// </summary> /// </summary>
private int notesSinceRhythmChange; private int notesSinceRhythmChange;
public Rhythm(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
// drum rolls and swells are exempt. // drum rolls and swells are exempt.

View File

@ -5,6 +5,7 @@ using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -48,8 +49,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Creates a <see cref="Stamina"/> skill. /// Creates a <see cref="Stamina"/> skill.
/// </summary> /// </summary>
/// <param name="mods">Mods for use in skill calculations.</param>
/// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param> /// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param>
public Stamina(bool rightHand) public Stamina(Mod[] mods, bool rightHand)
: base(mods)
{ {
hand = rightHand ? 1 : 0; hand = rightHand ? 1 : 0;
} }

View File

@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{ {
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Colour(), new Colour(mods),
new Rhythm(), new Rhythm(mods),
new Stamina(true), new Stamina(mods, true),
new Stamina(false), new Stamina(mods, false),
}; };
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -52,32 +52,24 @@ namespace osu.Game.Rulesets.Taiko.Edit
public void SetStrongState(bool state) public void SetStrongState(bool state)
{ {
var hits = EditorBeatmap.SelectedHitObjects.OfType<Hit>(); EditorBeatmap.PerformOnSelection(h =>
EditorBeatmap.BeginChange();
foreach (var h in hits)
{ {
if (h.IsStrong != state) if (!(h is Hit taikoHit)) return;
{
h.IsStrong = state;
EditorBeatmap.Update(h);
}
}
EditorBeatmap.EndChange(); if (taikoHit.IsStrong != state)
{
taikoHit.IsStrong = state;
EditorBeatmap.Update(taikoHit);
}
});
} }
public void SetRimState(bool state) public void SetRimState(bool state)
{ {
var hits = EditorBeatmap.SelectedHitObjects.OfType<Hit>(); EditorBeatmap.PerformOnSelection(h =>
{
EditorBeatmap.BeginChange(); if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
});
foreach (var h in hits)
h.Type = state ? HitType.Rim : HitType.Centre;
EditorBeatmap.EndChange();
} }
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection) protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)

View File

@ -1,22 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI;
namespace osu.Game.Rulesets.Taiko.Skinning.Legacy namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{ {
public class LegacyHitExplosion : CompositeDrawable public class LegacyHitExplosion : CompositeDrawable, IAnimatableHitExplosion
{ {
private readonly Drawable sprite; private readonly Drawable sprite;
private readonly Drawable strongSprite;
private DrawableStrongNestedHit nestedStrongHit; [CanBeNull]
private bool switchedToStrongSprite; private readonly Drawable strongSprite;
/// <summary> /// <summary>
/// Creates a new legacy hit explosion. /// Creates a new legacy hit explosion.
@ -27,14 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
/// </remarks> /// </remarks>
/// <param name="sprite">The normal legacy explosion sprite.</param> /// <param name="sprite">The normal legacy explosion sprite.</param>
/// <param name="strongSprite">The strong legacy explosion sprite.</param> /// <param name="strongSprite">The strong legacy explosion sprite.</param>
public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null) public LegacyHitExplosion(Drawable sprite, [CanBeNull] Drawable strongSprite = null)
{ {
this.sprite = sprite; this.sprite = sprite;
this.strongSprite = strongSprite; this.strongSprite = strongSprite;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(DrawableHitObject judgedObject) private void load()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -56,45 +56,30 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
s.Origin = Anchor.Centre; s.Origin = Anchor.Centre;
})); }));
} }
if (judgedObject is DrawableHit hit)
nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit;
} }
protected override void LoadComplete() public void Animate(DrawableHitObject drawableHitObject)
{ {
base.LoadComplete();
const double animation_time = 120; const double animation_time = 120;
(sprite as IFramedAnimation)?.GotoFrame(0);
(strongSprite as IFramedAnimation)?.GotoFrame(0);
this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5); this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5);
this.ScaleTo(0.6f) this.ScaleTo(0.6f)
.Then().ScaleTo(1.1f, animation_time * 0.8) .Then().ScaleTo(1.1f, animation_time * 0.8)
.Then().ScaleTo(0.9f, animation_time * 0.4) .Then().ScaleTo(0.9f, animation_time * 0.4)
.Then().ScaleTo(1f, animation_time * 0.2); .Then().ScaleTo(1f, animation_time * 0.2);
Expire(true);
} }
protected override void Update() public void AnimateSecondHit()
{ {
base.Update(); if (strongSprite == null)
return;
if (shouldSwitchToStrongSprite() && !switchedToStrongSprite)
{
sprite.FadeOut(50, Easing.OutQuint); sprite.FadeOut(50, Easing.OutQuint);
strongSprite.FadeIn(50, Easing.OutQuint); strongSprite.FadeIn(50, Easing.OutQuint);
switchedToStrongSprite = true;
}
}
private bool shouldSwitchToStrongSprite()
{
if (nestedStrongHit == null || strongSprite == null)
return false;
return nestedStrongHit.IsHit;
} }
} }
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{ {
// if a taiko skin is providing explosion sprites, hide the judgements completely // if a taiko skin is providing explosion sprites, hide the judgements completely
if (hasExplosion.Value) if (hasExplosion.Value)
return Drawable.Empty(); return Drawable.Empty().With(d => d.Expire());
} }
if (!(component is TaikoSkinComponent taikoComponent)) if (!(component is TaikoSkinComponent taikoComponent))
@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
// suppress the default kiai explosion if the skin brings its own sprites. // suppress the default kiai explosion if the skin brings its own sprites.
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
if (hasExplosion.Value) if (hasExplosion.Value)
return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); return Drawable.Empty().With(d => d.Expire());
return null; return null;

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -57,43 +58,43 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new TaikoModNightcore(); yield return new TaikoModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new TaikoModDoubleTime(); yield return new TaikoModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new TaikoModPerfect(); yield return new TaikoModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new TaikoModSuddenDeath(); yield return new TaikoModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new TaikoModCinema(); yield return new TaikoModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new TaikoModAutoplay(); yield return new TaikoModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new TaikoModEasy(); yield return new TaikoModEasy();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new TaikoModFlashlight(); yield return new TaikoModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new TaikoModHalfTime(); yield return new TaikoModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new TaikoModHardRock(); yield return new TaikoModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new TaikoModHidden(); yield return new TaikoModHidden();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new TaikoModNoFail(); yield return new TaikoModNoFail();
if (mods.HasFlag(LegacyMods.Relax)) if (mods.HasFlagFast(LegacyMods.Relax))
yield return new TaikoModRelax(); yield return new TaikoModRelax();
if (mods.HasFlag(LegacyMods.Random)) if (mods.HasFlagFast(LegacyMods.Random))
yield return new TaikoModRandom(); yield return new TaikoModRandom();
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -13,19 +14,23 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
internal class DefaultHitExplosion : CircularContainer internal class DefaultHitExplosion : CircularContainer, IAnimatableHitExplosion
{ {
private readonly DrawableHitObject judgedObject;
private readonly HitResult result; private readonly HitResult result;
public DefaultHitExplosion(DrawableHitObject judgedObject, HitResult result) [CanBeNull]
private Box body;
[Resolved]
private OsuColour colours { get; set; }
public DefaultHitExplosion(HitResult result)
{ {
this.judgedObject = judgedObject;
this.result = result; this.result = result;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -40,26 +45,36 @@ namespace osu.Game.Rulesets.Taiko.UI
if (!result.IsHit()) if (!result.IsHit())
return; return;
bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;
InternalChildren = new[] InternalChildren = new[]
{ {
new Box body = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = isRim ? colours.BlueDarker : colours.PinkDarker,
} }
}; };
updateColour();
} }
protected override void LoadComplete() private void updateColour([CanBeNull] DrawableHitObject judgedObject = null)
{ {
base.LoadComplete(); if (body == null)
return;
bool isRim = (judgedObject?.HitObject as Hit)?.Type == HitType.Rim;
body.Colour = isRim ? colours.BlueDarker : colours.PinkDarker;
}
public void Animate(DrawableHitObject drawableHitObject)
{
updateColour(drawableHitObject);
this.ScaleTo(3f, 1000, Easing.OutQuint); this.ScaleTo(3f, 1000, Easing.OutQuint);
this.FadeOut(500); this.FadeOut(500);
}
Expire(true); public void AnimateSecondHit()
{
} }
} }
} }

View File

@ -3,7 +3,6 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
@ -12,16 +11,6 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary> /// </summary>
public class DrawableTaikoJudgement : DrawableJudgement public class DrawableTaikoJudgement : DrawableJudgement
{ {
/// <summary>
/// Creates a new judgement text.
/// </summary>
/// <param name="judgedObject">The object which is being judged.</param>
/// <param name="result">The judgement to visualise.</param>
public DrawableTaikoJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(result, judgedObject)
{
}
protected override void ApplyHitAnimations() protected override void ApplyHitAnimations()
{ {
this.MoveToY(-100, 500); this.MoveToY(-100, 500);

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using JetBrains.Annotations;
using osuTK; using osuTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -16,31 +18,37 @@ namespace osu.Game.Rulesets.Taiko.UI
/// <summary> /// <summary>
/// A circle explodes from the hit target to indicate a hitobject has been hit. /// A circle explodes from the hit target to indicate a hitobject has been hit.
/// </summary> /// </summary>
internal class HitExplosion : CircularContainer internal class HitExplosion : PoolableDrawable
{ {
public override bool RemoveWhenNotAlive => true; public override bool RemoveWhenNotAlive => true;
public override bool RemoveCompletedTransforms => false;
[Cached(typeof(DrawableHitObject))]
public readonly DrawableHitObject JudgedObject;
private readonly HitResult result; private readonly HitResult result;
private double? secondHitTime;
[CanBeNull]
public DrawableHitObject JudgedObject;
private SkinnableDrawable skinnable; private SkinnableDrawable skinnable;
public override double LifetimeStart => skinnable.Drawable.LifetimeStart; /// <summary>
/// This constructor only exists to meet the <c>new()</c> type constraint of <see cref="DrawablePool{T}"/>.
public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; /// </summary>
public HitExplosion()
public HitExplosion(DrawableHitObject judgedObject, HitResult result) : this(HitResult.Great)
{
}
public HitExplosion(HitResult result)
{ {
JudgedObject = judgedObject;
this.result = result; this.result = result;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE); Size = new Vector2(TaikoHitObject.DEFAULT_SIZE);
RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
} }
@ -48,7 +56,47 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result)); InternalChild = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(result));
skinnable.OnSkinChanged += runAnimation;
}
public void Apply([CanBeNull] DrawableHitObject drawableHitObject)
{
JudgedObject = drawableHitObject;
secondHitTime = null;
}
protected override void PrepareForUse()
{
base.PrepareForUse();
runAnimation();
}
private void runAnimation()
{
if (JudgedObject?.Result == null)
return;
double resultTime = JudgedObject.Result.TimeAbsolute;
LifetimeStart = resultTime;
ApplyTransformsAt(double.MinValue, true);
ClearTransforms(true);
using (BeginAbsoluteSequence(resultTime))
(skinnable.Drawable as IAnimatableHitExplosion)?.Animate(JudgedObject);
if (secondHitTime != null)
{
using (BeginAbsoluteSequence(secondHitTime.Value))
{
this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50);
(skinnable.Drawable as IAnimatableHitExplosion)?.AnimateSecondHit();
}
}
LifetimeEnd = skinnable.Drawable.LatestTransformEndTime;
} }
private static TaikoSkinComponents getComponentName(HitResult result) private static TaikoSkinComponents getComponentName(HitResult result)
@ -68,12 +116,10 @@ namespace osu.Game.Rulesets.Taiko.UI
throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}"); throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}");
} }
/// <summary> public void VisualiseSecondHit(JudgementResult judgementResult)
/// Transforms this hit explosion to visualise a secondary hit.
/// </summary>
public void VisualiseSecondHit()
{ {
this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); secondHitTime = judgementResult.TimeAbsolute;
runAnimation();
} }
} }
} }

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.UI
{
/// <summary>
/// Pool for hit explosions of a specific type.
/// </summary>
internal class HitExplosionPool : DrawablePool<HitExplosion>
{
private readonly HitResult hitResult;
public HitExplosionPool(HitResult hitResult)
: base(15)
{
this.hitResult = hitResult;
}
protected override HitExplosion CreateNewDrawable() => new HitExplosion(hitResult);
}
}

View File

@ -0,0 +1,23 @@
// 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.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.UI
{
/// <summary>
/// A skinnable element of a hit explosion that supports playing an animation from the current point in time.
/// </summary>
public interface IAnimatableHitExplosion
{
/// <summary>
/// Shows the hit explosion for the supplied <paramref name="drawableHitObject"/>.
/// </summary>
void Animate(DrawableHitObject drawableHitObject);
/// <summary>
/// Transforms the hit explosion to visualise a secondary hit.
/// </summary>
void AnimateSecondHit();
}
}

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -17,6 +19,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -38,6 +41,9 @@ namespace osu.Game.Rulesets.Taiko.UI
internal Drawable HitTarget; internal Drawable HitTarget;
private SkinnableDrawable mascot; private SkinnableDrawable mascot;
private readonly IDictionary<HitResult, DrawablePool<DrawableTaikoJudgement>> judgementPools = new Dictionary<HitResult, DrawablePool<DrawableTaikoJudgement>>();
private readonly IDictionary<HitResult, HitExplosionPool> explosionPools = new Dictionary<HitResult, HitExplosionPool>();
private ProxyContainer topLevelHitContainer; private ProxyContainer topLevelHitContainer;
private Container rightArea; private Container rightArea;
private Container leftArea; private Container leftArea;
@ -159,6 +165,17 @@ namespace osu.Game.Rulesets.Taiko.UI
RegisterPool<Swell, DrawableSwell>(5); RegisterPool<Swell, DrawableSwell>(5);
RegisterPool<SwellTick, DrawableSwellTick>(100); RegisterPool<SwellTick, DrawableSwellTick>(100);
var hitWindows = new TaikoHitWindows();
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)))
{
judgementPools.Add(result, new DrawablePool<DrawableTaikoJudgement>(15));
explosionPools.Add(result, new HitExplosionPool(result));
}
AddRangeInternal(judgementPools.Values);
AddRangeInternal(explosionPools.Values);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -270,7 +287,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
case TaikoStrongJudgement _: case TaikoStrongJudgement _:
if (result.IsHit) if (result.IsHit)
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(); hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(result);
break; break;
case TaikoDrumRollTickJudgement _: case TaikoDrumRollTickJudgement _:
@ -283,13 +300,15 @@ namespace osu.Game.Rulesets.Taiko.UI
break; break;
default: default:
judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) judgementContainer.Add(judgementPools[result.Type].Get(j =>
{ {
Anchor = result.IsHit ? Anchor.TopLeft : Anchor.CentreLeft, j.Apply(result, judgedObject);
Origin = result.IsHit ? Anchor.BottomCentre : Anchor.Centre,
RelativePositionAxes = Axes.X, j.Anchor = result.IsHit ? Anchor.TopLeft : Anchor.CentreLeft;
X = result.IsHit ? judgedObject.Position.X : 0, j.Origin = result.IsHit ? Anchor.BottomCentre : Anchor.Centre;
}); j.RelativePositionAxes = Axes.X;
j.X = result.IsHit ? judgedObject.Position.X : 0;
}));
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
addExplosion(judgedObject, result.Type, type); addExplosion(judgedObject, result.Type, type);
@ -302,7 +321,8 @@ namespace osu.Game.Rulesets.Taiko.UI
private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type)
{ {
hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); hitExplosionContainer.Add(explosionPools[result]
.Get(explosion => explosion.Apply(drawableObject)));
if (drawableObject.HitObject.Kiai) if (drawableObject.HitObject.Kiai)
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
} }

View File

@ -20,6 +20,9 @@
<ItemGroup> <ItemGroup>
<None Include="Properties\AndroidManifest.xml" /> <None Include="Properties\AndroidManifest.xml" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CA2007</NoWarn>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs"> <Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link> <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
@ -71,7 +74,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

View File

@ -21,6 +21,9 @@
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link> <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CA2007</NoWarn>
</PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game\osu.Game.csproj"> <ProjectReference Include="..\osu.Game\osu.Game.csproj">
<Project>{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}</Project> <Project>{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}</Project>
@ -45,7 +48,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project> </Project>

View File

@ -852,6 +852,21 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
public static async Task<BeatmapSetInfo> LoadQuickOszIntoOsu(OsuGameBase osu)
{
var temp = TestResources.GetQuickTestBeatmapForImport();
var manager = osu.Dependencies.Get<BeatmapManager>();
var importedSet = await manager.Import(new ImportTask(temp));
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{ {
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);

View File

@ -13,6 +13,7 @@ using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -90,6 +91,7 @@ namespace osu.Game.Tests.Gameplay
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
{ {
GameplayClockContainer gameplayContainer = null; GameplayClockContainer gameplayContainer = null;
StoryboardSampleInfo sampleInfo = null;
TestDrawableStoryboardSample sample = null; TestDrawableStoryboardSample sample = null;
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
@ -101,7 +103,7 @@ namespace osu.Game.Tests.Gameplay
break; break;
case ModTimeRamp m: case ModTimeRamp m:
m.InitialRate.Value = m.FinalRate.Value = expectedRate; m.FinalRate.Value = m.InitialRate.Value = expectedRate;
break; break;
} }
@ -117,7 +119,7 @@ namespace osu.Game.Tests.Gameplay
Child = beatmapSkinSourceContainer Child = beatmapSkinSourceContainer
}); });
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1))
{ {
Clock = gameplayContainer.GameplayClock Clock = gameplayContainer.GameplayClock
}); });
@ -125,7 +127,10 @@ namespace osu.Game.Tests.Gameplay
AddStep("start", () => gameplayContainer.Start()); AddStep("start", () => gameplayContainer.Start());
AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate); AddAssert("sample playback rate matches mod rates", () =>
testedMod != null && Precision.AlmostEquals(
sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value,
((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
} }
private class TestSkin : LegacySkin private class TestSkin : LegacySkin

View File

@ -88,7 +88,7 @@ namespace osu.Game.Tests.Input
=> AddStep($"make window {mode}", () => frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode).Value = mode); => AddStep($"make window {mode}", () => frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode).Value = mode);
private void setGameSideModeTo(OsuConfineMouseMode mode) private void setGameSideModeTo(OsuConfineMouseMode mode)
=> AddStep($"set {mode} game-side", () => Game.LocalConfig.Set(OsuSetting.ConfineMouseMode, mode)); => AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode));
private void setLocalUserPlayingTo(bool playing) private void setLocalUserPlayingTo(bool playing)
=> AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing); => AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing);

View File

@ -0,0 +1,44 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Configuration;
namespace osu.Game.Tests.Mods
{
[TestFixture]
public class SettingsSourceAttributeTest
{
[Test]
public void TestOrdering()
{
var objectWithSettings = new ClassWithSettings();
var orderedSettings = objectWithSettings.GetOrderedSettingsSourceProperties().ToArray();
Assert.That(orderedSettings, Has.Length.EqualTo(4));
Assert.That(orderedSettings[0].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.FirstSetting)));
Assert.That(orderedSettings[1].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.SecondSetting)));
Assert.That(orderedSettings[2].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.ThirdSetting)));
Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting)));
}
private class ClassWithSettings
{
[SettingSource("Unordered setting", "Should be last")]
public BindableFloat UnorderedSetting { get; set; } = new BindableFloat();
[SettingSource("Second setting", "Another description", 2)]
public BindableBool SecondSetting { get; set; } = new BindableBool();
[SettingSource("First setting", "A description", 1)]
public BindableDouble FirstSetting { get; set; } = new BindableDouble();
[SettingSource("Third setting", "Yet another description", 3)]
public BindableInt ThirdSetting { get; set; } = new BindableInt();
}
}
}

View File

@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual
using (var host = new CustomTestHeadlessGameHost()) using (var host = new CustomTestHeadlessGameHost())
{ {
using (var storageConfig = new StorageConfigManager(host.InitialStorage)) using (var storageConfig = new StorageConfigManager(host.InitialStorage))
storageConfig.Set(StorageConfig.FullPath, customPath); storageConfig.SetValue(StorageConfig.FullPath, customPath);
try try
{ {
@ -73,7 +73,7 @@ namespace osu.Game.Tests.NonVisual
using (var host = new CustomTestHeadlessGameHost()) using (var host = new CustomTestHeadlessGameHost())
{ {
using (var storageConfig = new StorageConfigManager(host.InitialStorage)) using (var storageConfig = new StorageConfigManager(host.InitialStorage))
storageConfig.Set(StorageConfig.FullPath, customPath); storageConfig.SetValue(StorageConfig.FullPath, customPath);
try try
{ {

View File

@ -212,7 +212,7 @@ namespace osu.Game.Tests.NonVisual
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -4,8 +4,10 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering namespace osu.Game.Tests.NonVisual.Filtering
{ {
@ -214,5 +216,31 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(filtered, carouselItem.Filtered.Value); Assert.AreEqual(filtered, carouselItem.Filtered.Value);
} }
[Test]
public void TestCustomRulesetCriteria([Values(null, true, false)] bool? matchCustomCriteria)
{
var beatmap = getExampleBeatmap();
var customCriteria = matchCustomCriteria is bool match ? new CustomCriteria(match) : null;
var criteria = new FilterCriteria { RulesetCriteria = customCriteria };
var carouselItem = new CarouselBeatmap(beatmap);
carouselItem.Filter(criteria);
Assert.AreEqual(matchCustomCriteria == false, carouselItem.Filtered.Value);
}
private class CustomCriteria : IRulesetFilterCriteria
{
private readonly bool match;
public CustomCriteria(bool shouldMatch)
{
match = shouldMatch;
}
public bool Matches(BeatmapInfo beatmap) => match;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false;
}
} }
} }

View File

@ -4,7 +4,9 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering namespace osu.Game.Tests.NonVisual.Filtering
{ {
@ -194,5 +196,63 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm); Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
} }
[Test]
public void TestOperatorParsing()
{
const string query = "artist=><something";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("><something", filterCriteria.Artist.SearchTerm);
}
[Test]
public void TestUnrecognisedKeywordIsIgnored()
{
const string query = "unrecognised=keyword";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("unrecognised=keyword", filterCriteria.SearchText);
}
[TestCase("cs=nope")]
[TestCase("bpm>=bad")]
[TestCase("divisor<nah")]
[TestCase("status=noidea")]
public void TestInvalidKeywordValueIsIgnored(string query)
{
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual(query, filterCriteria.SearchText);
}
[Test]
public void TestCustomKeywordIsParsed()
{
var customCriteria = new CustomFilterCriteria();
const string query = "custom=readme unrecognised=keyword";
var filterCriteria = new FilterCriteria { RulesetCriteria = customCriteria };
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("readme", customCriteria.CustomValue);
Assert.AreEqual("unrecognised=keyword", filterCriteria.SearchText.Trim());
}
private class CustomFilterCriteria : IRulesetFilterCriteria
{
public string CustomValue { get; set; }
public bool Matches(BeatmapInfo beatmap) => true;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
{
if (key == "custom" && op == Operator.Equal)
{
CustomValue = value;
return true;
}
return false;
}
}
} }
} }

View File

@ -52,7 +52,7 @@ namespace osu.Game.Tests.Online
{ {
beatmaps.AllowImport = new TaskCompletionSource<bool>(); beatmaps.AllowImport = new TaskCompletionSource<bool>();
testBeatmapFile = TestResources.GetTestBeatmapForImport(); testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet; testBeatmapSet = testBeatmapInfo.BeatmapSet;

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Tests.OnlinePlay
{
[HeadlessTest]
public class StatefulMultiplayerClientTest : MultiplayerTestScene
{
[Test]
public void TestUserAddedOnJoin()
{
var user = new User { Id = 33 };
AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
}
[Test]
public void TestUserRemovedOnLeave()
{
var user = new User { Id = 44 };
AddStep("add user", () => Client.AddUser(user));
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
}
}
}

View File

@ -15,6 +15,28 @@ namespace osu.Game.Tests.Resources
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
/// <summary>
/// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track.
/// </summary>
/// <remarks>
/// This is intended for use in tests which need to run to completion as soon as possible and don't need to test a full length beatmap.</remarks>
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
public static string GetQuickTestBeatmapForImport()
{
var tempPath = Path.GetTempFileName() + ".osz";
using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz"))
using (var newFile = File.Create(tempPath))
stream.CopyTo(newFile);
Assert.IsTrue(File.Exists(tempPath));
return tempPath;
}
/// <summary>
/// Retrieve a path to a copy of a full-fledged beatmap archive.
/// </summary>
/// <param name="virtualTrack">Whether the audio track should be virtual.</param>
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
public static string GetTestBeatmapForImport(bool virtualTrack = false) public static string GetTestBeatmapForImport(bool virtualTrack = false)
{ {
var tempPath = Path.GetTempFileName() + ".osz"; var tempPath = Path.GetTempFileName() + ".osz";

View File

@ -0,0 +1,2 @@
[General]
Version: 2

View File

@ -91,6 +91,15 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion); Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
} }
[Test]
public void TestStripWhitespace()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-with-space.ini"))
using (var stream = new LineBufferedReader(resStream))
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
}
[Test] [Test]
public void TestDecodeLatestVersion() public void TestDecodeLatestVersion()
{ {

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Background
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
// reset API response in statics to avoid test crosstalk. // reset API response in statics to avoid test crosstalk.
statics.Set<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null); statics.SetValue<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
textureStore.PerformedLookups.Clear(); textureStore.PerformedLookups.Clear();
dummyAPI.SetState(APIState.Online); dummyAPI.SetState(APIState.Online);
@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.Background
}); });
private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode) private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
=> AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode)); => AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode));
private void createLoader() private void createLoader()
=> AddStep("create loader", () => => AddStep("create loader", () =>

View File

@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Background
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage)); Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
Beatmap.SetDefault(); Beatmap.SetDefault();
} }

View File

@ -38,13 +38,13 @@ namespace osu.Game.Tests.Visual.Collections
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {
manager = new CollectionManager(LocalStorage), manager = new CollectionManager(LocalStorage),
Content, Content,
dialogOverlay = new DialogOverlay() dialogOverlay = new DialogOverlay(),
}); });
Dependencies.Cache(manager); Dependencies.Cache(manager);
@ -134,6 +134,27 @@ namespace osu.Game.Tests.Visual.Collections
assertCollectionName(0, "2"); assertCollectionName(0, "2");
} }
[Test]
public void TestCollectionNameCollisions()
{
AddStep("add dropdown", () =>
{
Add(new CollectionFilterDropdown
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.X,
Width = 0.4f,
}
);
});
AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
{
new BeatmapCollection { Name = { Value = "1" } },
new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
}));
}
[Test] [Test]
public void TestRemoveCollectionViaButton() public void TestRemoveCollectionViaButton()
{ {

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
AddStep("show health", () => showHealth.Value = true); AddStep("show health", () => showHealth.Value = true);
AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddStep("enable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer is visible", () => layer.IsPresent); AddUntilStep("layer is visible", () => layer.IsPresent);
} }
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestLayerDisabledViaConfig() public void TestLayerDisabledViaConfig()
{ {
AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent); AddUntilStep("layer is not visible", () => !layer.IsPresent);
} }
@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("don't show health", () => showHealth.Value = false); AddStep("don't show health", () => showHealth.Value = false);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent); AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("don't show health", () => showHealth.Value = false); AddStep("don't show health", () => showHealth.Value = false);
AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent); AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true); AddStep("show health", () => showHealth.Value = true);
AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent); AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true); AddStep("show health", () => showHealth.Value = true);
AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is visible", () => layer.IsPresent); AddUntilStep("layer fade is visible", () => layer.IsPresent);
} }
} }

View File

@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)); AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue)); AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
} }
[Test] [Test]
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set keycounter visible false", () => AddStep("set keycounter visible false", () =>
{ {
config.Set<bool>(OsuSetting.KeyOverlay, false); config.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false; hudOverlay.KeyCounter.AlwaysVisible.Value = false;
}); });
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.Set<bool>(OsuSetting.KeyOverlay, keyCounterVisibleValue)); AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
} }
private void createNew(Action<HUDOverlay> action = null) private void createNew(Action<HUDOverlay> action = null)

View File

@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(0, 0)] [TestCase(0, 0)]
[TestCase(-1000, -1000)] [TestCase(-1000, -1000)]
[TestCase(-10000, -10000)] [TestCase(-10000, -10000)]
public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime) public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryboardEvent, double expectedStartTime)
{ {
var storyboard = new Storyboard(); var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite); storyboard.GetLayer("Background").Add(sprite);
@ -64,6 +65,43 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
} }
[TestCase(1000, 0, false)]
[TestCase(0, 0, false)]
[TestCase(-1000, -1000, false)]
[TestCase(-10000, -10000, false)]
[TestCase(1000, 0, true)]
[TestCase(0, 0, true)]
[TestCase(-1000, -1000, true)]
[TestCase(-10000, -10000, true)]
public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
// these should be ignored as we have an alpha visibility blocker proceeding this command.
sprite.TimelineGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
var loopGroup = sprite.AddLoop(-20000, 50);
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);
// 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);
storyboard.GetLayer("Background").Add(sprite);
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
AddAssert($"first frame is {expectedStartTime}", () =>
{
Debug.Assert(player.FirstFrameClockTime != null);
return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
});
}
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null) private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{ {
AddStep("create player", () => AddStep("create player", () =>

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -8,21 +9,17 @@ using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : OsuPlayerTestScene public class TestScenePauseWhenInactive : OsuPlayerTestScene
{ {
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
return beatmap;
}
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
@ -33,10 +30,57 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
AddStep("progress time to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime));
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
}
/// <summary>
/// Tests that if a pause from focus lose is performed while in pause cooldown,
/// the player will still pause after the cooldown is finished.
/// </summary>
[Test]
public void TestPauseWhileInCooldown()
{
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime));
AddStep("set inactive", () => ((Bindable<bool>)host.IsActive).Value = false);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddStep("set active", () => ((Bindable<bool>)host.IsActive).Value = true);
AddStep("resume player", () => Player.Resume());
AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value);
bool pauseCooldownActive = false;
AddStep("set inactive again", () =>
{
pauseCooldownActive = Player.PauseCooldownActive;
((Bindable<bool>)host.IsActive).Value = false;
});
AddAssert("pause cooldown active", () => pauseCooldownActive);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
} }
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
return new Beatmap
{
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 30000 },
new HitCircle { StartTime = 35000 },
},
};
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new TestWorkingBeatmap(beatmap, storyboard, Audio);
} }
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
config.Set(OsuSetting.ShowStoryboard, true); config.SetValue(OsuSetting.ShowStoryboard, true);
storyboard = new Storyboard(); storyboard = new Storyboard();
var backgroundLayer = storyboard.GetLayer("Background"); var backgroundLayer = storyboard.GetLayer("Background");

View File

@ -1,46 +1,41 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using osu.Framework.Allocation;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneMultiplayer : MultiplayerTestScene public class TestSceneMultiplayer : ScreenTestScene
{ {
private TestMultiplayer multiplayerScreen;
public TestSceneMultiplayer() public TestSceneMultiplayer()
{ {
var multi = new TestMultiplayer(); AddStep("show", () =>
AddStep("show", () => LoadScreen(multi));
AddUntilStep("wait for loaded", () => multi.IsLoaded);
}
[Test]
public void TestOneUserJoinedMultipleTimes()
{ {
var user = new User { Id = 33 }; multiplayerScreen = new TestMultiplayer();
AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); // Needs to be added at a higher level since the multiplayer screen becomes non-current.
Child = multiplayerScreen.Client;
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); LoadScreen(multiplayerScreen);
} });
[Test] AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded);
public void TestOneUserLeftMultipleTimes()
{
var user = new User { Id = 44 };
AddStep("add user", () => Client.AddUser(user));
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
} }
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
{ {
[Cached(typeof(StatefulMultiplayerClient))]
public readonly TestMultiplayerClient Client;
public TestMultiplayer()
{
Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager);
}
protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
} }
} }

View File

@ -7,17 +7,23 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -50,6 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmaps.Add(new BeatmapInfo beatmaps.Add(new BeatmapInfo
{ {
Ruleset = rulesets.GetRuleset(i % 4), Ruleset = rulesets.GetRuleset(i % 4),
RulesetID = i % 4, // workaround for efcore 5 compatibility.
OnlineBeatmapID = beatmapId, OnlineBeatmapID = beatmapId,
Length = length, Length = length,
BPM = bpm, BPM = bpm,
@ -137,8 +144,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime); AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime);
} }
[TestCase(typeof(OsuModHidden), typeof(OsuModHidden))] // Same mod.
[TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible.
public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod)
{
AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) });
AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) });
AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0);
assertHasFreeModButton(allowedMod, false);
assertHasFreeModButton(requiredMod, false);
}
private void assertHasFreeModButton(Type type, bool hasButton = true)
{
AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
() => songSelect.ChildrenOfType<FreeModSelectOverlay>().Single().ChildrenOfType<ModButton>().All(b => b.Mod.GetType() != type));
}
private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
{ {
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
public new Bindable<IReadOnlyList<Mod>> FreeMods => base.FreeMods;
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;
} }
} }

View File

@ -3,12 +3,10 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
@ -20,9 +18,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
private MultiplayerMatchSubScreen screen; private MultiplayerMatchSubScreen screen;
[Cached]
private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
public TestSceneMultiplayerMatchSubScreen() public TestSceneMultiplayerMatchSubScreen()
: base(false) : base(false)
{ {

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker
{ {

View File

@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@ -21,15 +24,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room { Name = { Value = "1" } }); roomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.CreateRoom(new Room { Name = { Value = "2" } }); roomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.ClearRooms(); roomManager.ClearRooms();
}); });
}); });
AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2); AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
} }
@ -40,16 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
}); });
}); });
AddStep("disconnect", () => roomContainer.Client.Disconnect()); AddStep("disconnect", () => roomContainer.Client.Disconnect());
AddAssert("rooms cleared", () => roomManager.Rooms.Count == 0); AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
} }
@ -60,9 +63,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
}); });
}); });
@ -70,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("disconnect", () => roomContainer.Client.Disconnect()); AddStep("disconnect", () => roomContainer.Client.Disconnect());
AddStep("connect", () => roomContainer.Client.Connect()); AddStep("connect", () => roomContainer.Client.Connect());
AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2); AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
} }
@ -81,12 +84,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.ClearRooms(); roomManager.ClearRooms();
}); });
}); });
AddAssert("manager not polled for rooms", () => roomManager.Rooms.Count == 0); AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
} }
@ -97,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
}); });
}); });
@ -111,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
}); });
}); });
@ -126,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
var r = new Room(); var r = createRoom();
roomManager.CreateRoom(r); roomManager.CreateRoom(r);
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.JoinRoom(r); roomManager.JoinRoom(r);
@ -136,6 +139,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
} }
private Room createRoom(Action<Room> initFunc = null)
{
var room = new Room();
room.Name.Value = "test room";
room.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
Ruleset = { Value = Ruleset.Value }
});
initFunc?.Invoke(room);
return room;
}
private TestMultiplayerRoomManager createRoomManager() private TestMultiplayerRoomManager createRoomManager()
{ {
Child = roomContainer = new TestMultiplayerRoomContainer Child = roomContainer = new TestMultiplayerRoomContainer

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -62,10 +61,6 @@ namespace osu.Game.Tests.Visual.Navigation
RecycleLocalStorage(); RecycleLocalStorage();
// see MouseSettings
var frameworkConfig = host.Dependencies.Get<FrameworkConfigManager>();
frameworkConfig.GetBindable<double>(FrameworkSetting.CursorSensitivity).Disabled = false;
CreateGame(); CreateGame();
}); });
@ -82,7 +77,7 @@ namespace osu.Game.Tests.Visual.Navigation
// todo: this can be removed once we can run audio tracks without a device present // todo: this can be removed once we can run audio tracks without a device present
// see https://github.com/ppy/osu/issues/1302 // see https://github.com/ppy/osu/issues/1302
Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); Game.LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(Game); Add(Game);
} }
@ -136,7 +131,7 @@ namespace osu.Game.Tests.Visual.Navigation
base.LoadComplete(); base.LoadComplete();
API.Login("Rhythm Champion", "osu!"); API.Login("Rhythm Champion", "osu!");
Dependencies.Get<SessionStatics>().Set(Static.MutedAudioNotificationShownOnce, true); Dependencies.Get<SessionStatics>().SetValue(Static.MutedAudioNotificationShownOnce, true);
} }
} }

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => new TestSongSelect()); PushAndConfirm(() => new TestSongSelect());
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("press enter", () => InputManager.Key(Key.Enter)); AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length)); AddStep("seek to end", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(beatmap().Track.Length));
AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded);
AddStep("attempt to retry", () => results.ChildrenOfType<HotkeyRetryOverlay>().First().Action()); AddStep("attempt to retry", () => results.ChildrenOfType<HotkeyRetryOverlay>().First().Action());
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);
@ -214,6 +214,50 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible); AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible);
} }
[Test]
public void TestSettingsViaHotkeyFromMainMenu()
{
AddAssert("toolbar not displayed", () => Game.Toolbar.State.Value == Visibility.Hidden);
AddStep("press settings hotkey", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.O);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible);
}
[Test]
public void TestToolbarHiddenByUser()
{
AddStep("Enter menu", () => InputManager.Key(Key.Enter));
AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
AddStep("Hide toolbar", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.T);
InputManager.ReleaseKey(Key.ControlLeft);
});
pushEscape();
AddStep("Enter menu", () => InputManager.Key(Key.Enter));
AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
AddStep("Enter song select", () =>
{
InputManager.Key(Key.Enter);
InputManager.Key(Key.Enter);
});
AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
}
private void pushEscape() => private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape)); AddStep("Press escape", () => InputManager.Key(Key.Escape));

View File

@ -15,8 +15,8 @@ namespace osu.Game.Tests.Visual.Navigation
using (var config = new OsuConfigManager(LocalStorage)) using (var config = new OsuConfigManager(LocalStorage))
{ {
config.Set(OsuSetting.Version, "2020.101.0"); config.SetValue(OsuSetting.Version, "2020.101.0");
config.Set(OsuSetting.DisplayStarsMaximum, 10.0); config.SetValue(OsuSetting.DisplayStarsMaximum, 10.0);
} }
} }
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get<double>(OsuSetting.DisplayStarsMaximum), 10.1)); AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get<double>(OsuSetting.DisplayStarsMaximum), 10.1));
AddStep("set value again", () => Game.LocalConfig.Set<double>(OsuSetting.DisplayStarsMaximum, 10)); AddStep("set value again", () => Game.LocalConfig.SetValue(OsuSetting.DisplayStarsMaximum, 10.0));
AddStep("force save config", () => Game.LocalConfig.Save()); AddStep("force save config", () => Game.LocalConfig.Save());

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
ensureSoleilyRemoved(); ensureSoleilyRemoved();
createButtonWithBeatmap(createSoleily()); createButtonWithBeatmap(createSoleily());
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
AddStep("import soleily", () => beatmaps.Import(TestResources.GetTestBeatmapForImport())); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526));
createButtonWithBeatmap(createSoleily()); createButtonWithBeatmap(createSoleily());
AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);

View File

@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "flyte", Username = "flyte",
Id = 3103765, Id = 3103765,
IsOnline = true, IsOnline = true,
CurrentModeRank = 1111, Statistics = new UserStatistics { GlobalRank = 1111 },
Country = new Country { FlagName = "JP" }, Country = new Country { FlagName = "JP" },
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
}, },
@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "peppy", Username = "peppy",
Id = 2, Id = 2,
IsOnline = false, IsOnline = false,
CurrentModeRank = 2222, Statistics = new UserStatistics { GlobalRank = 2222 },
Country = new Country { FlagName = "AU" }, Country = new Country { FlagName = "AU" },
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = true, IsSupporter = true,

View File

@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists
Room.RecentParticipants.Add(new User Room.RecentParticipants.Add(new User
{ {
Username = "peppy", Username = "peppy",
CurrentModeRank = 1234, Statistics = new UserStatistics { GlobalRank = 1234 },
Id = 2 Id = 2
}); });
} }

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -76,7 +75,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("bind user score info handler", () => AddStep("bind user score info handler", () =>
{ {
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ };
bindHandler(3000, userScore); bindHandler(true, userScore);
}); });
createResults(() => userScore); createResults(() => userScore);
@ -89,7 +88,7 @@ namespace osu.Game.Tests.Visual.Playlists
[Test] [Test]
public void TestShowNullUserScoreWithDelay() public void TestShowNullUserScoreWithDelay()
{ {
AddStep("bind delayed handler", () => bindHandler(3000)); AddStep("bind delayed handler", () => bindHandler(true));
createResults(); createResults();
waitForDisplay(); waitForDisplay();
@ -103,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists
createResults(); createResults();
waitForDisplay(); waitForDisplay();
AddStep("bind delayed handler", () => bindHandler(3000)); AddStep("bind delayed handler", () => bindHandler(true));
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
@ -134,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
createResults(() => userScore); createResults(() => userScore);
waitForDisplay(); waitForDisplay();
AddStep("bind delayed handler", () => bindHandler(3000)); AddStep("bind delayed handler", () => bindHandler(true));
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
@ -169,13 +168,17 @@ namespace osu.Game.Tests.Visual.Playlists
AddWaitStep("wait for display", 5); AddWaitStep("wait for display", 5);
} }
private void bindHandler(double delay = 0, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request =>
{ {
requestComplete = false; requestComplete = false;
double delay = delayed ? 3000 : 0;
Scheduler.AddDelayed(() =>
{
if (failRequests) if (failRequests)
{ {
triggerFail(request, delay); triggerFail(request);
return; return;
} }
@ -183,57 +186,30 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
case ShowPlaylistUserScoreRequest s: case ShowPlaylistUserScoreRequest s:
if (userScore == null) if (userScore == null)
triggerFail(s, delay); triggerFail(s);
else else
triggerSuccess(s, createUserResponse(userScore), delay); triggerSuccess(s, createUserResponse(userScore));
break; break;
case IndexPlaylistScoresRequest i: case IndexPlaylistScoresRequest i:
triggerSuccess(i, createIndexResponse(i), delay); triggerSuccess(i, createIndexResponse(i));
break; break;
} }
}, delay);
}; };
private void triggerSuccess<T>(APIRequest<T> req, T result, double delay) private void triggerSuccess<T>(APIRequest<T> req, T result)
where T : class where T : class
{
if (delay == 0)
success();
else
{
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds(delay));
Schedule(success);
});
}
void success()
{ {
requestComplete = true; requestComplete = true;
req.TriggerSuccess(result); req.TriggerSuccess(result);
} }
}
private void triggerFail(APIRequest req, double delay) private void triggerFail(APIRequest req)
{
if (delay == 0)
fail();
else
{
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds(delay));
Schedule(fail);
});
}
void fail()
{ {
requestComplete = true; requestComplete = true;
req.TriggerFailure(new WebException("Failed.")); req.TriggerFailure(new WebException("Failed."));
} }
}
private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore)
{ {

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