1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:07:52 +08:00

Merge branch 'master' into diffcalc/skill-mods

This commit is contained in:
Dan Balasescu 2021-03-04 13:06:26 +09:00 committed by GitHub
commit 5b6018295d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
157 changed files with 1392 additions and 624 deletions

View File

@ -194,4 +194,7 @@ dotnet_diagnostic.IDE0068.severity = none
dotnet_diagnostic.IDE0069.severity = none
#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:*
- `%AppData%/osu/logs` *(on Windows),*
- `~/.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:*
- `%AppData%/osu/logs` *(on Windows),*
- `~/.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:**

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)
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;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

@ -18,7 +18,7 @@
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
<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>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>

View File

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

View File

@ -17,7 +17,7 @@ using osu.Game.Database;
namespace osu.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

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

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
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)
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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.85, 0.36, 0.03);
@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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.56, 0.18, 0);
@ -157,13 +158,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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.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.27, 0, 0);
@ -221,7 +222,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
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);
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;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes)
@ -406,7 +407,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int endTime = startTime + SegmentDuration * SpanCount;
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);
for (int i = 0; i < columnRepeat; i++)
@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
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);
// Create the hold note

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osuTK;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
else
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)
convertType |= PatternType.Mirror;
@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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
for (int i = RandomStart; i < TotalColumns; i++)
@ -113,11 +114,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern;
}
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
int column = RandomStart + TotalColumns - lastColumn - 1;
@ -126,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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
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 (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"
int targetColumn = lastColumn + 1;
@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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"
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);
if (convertType.HasFlag(PatternType.Mirror))
if (convertType.HasFlagFast(PatternType.Mirror))
{
if (ConversionDifficulty > 6.5)
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 (convertType.HasFlag(PatternType.LowProbability))
if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.78, 0.42, 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 (convertType.HasFlag(PatternType.LowProbability))
if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.35, 0.08, 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 (convertType.HasFlag(PatternType.LowProbability))
if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.18, 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)
{
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair;
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
StairType = PatternType.Stair;
}
@ -229,7 +230,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
var pattern = new Pattern();
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
if (!allowStacking)
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int getNextColumn(int last)
{
if (convertType.HasFlag(PatternType.Gathered))
if (convertType.HasFlagFast(PatternType.Gathered))
{
last++;
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>
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);
var pattern = new Pattern();

View File

@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit
int minColumn = int.MaxValue;
int maxColumn = int.MinValue;
// find min/max in an initial pass before actually performing the movement.
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
{
if (obj.Column < minColumn)
@ -55,8 +56,11 @@ namespace osu.Game.Rulesets.Mania.Edit
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
obj.Column += columnDelta;
EditorBeatmap.PerformOnSelection(h =>
{
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 System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
@ -59,76 +60,76 @@ namespace osu.Game.Rulesets.Mania
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new ManiaModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime))
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new ManiaModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect))
if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new ManiaModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath))
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema))
if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new ManiaModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay))
else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
if (mods.HasFlagFast(LegacyMods.Easy))
yield return new ManiaModEasy();
if (mods.HasFlag(LegacyMods.FadeIn))
if (mods.HasFlagFast(LegacyMods.FadeIn))
yield return new ManiaModFadeIn();
if (mods.HasFlag(LegacyMods.Flashlight))
if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new ManiaModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime))
if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new ManiaModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock))
if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new ManiaModHardRock();
if (mods.HasFlag(LegacyMods.Hidden))
if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new ManiaModHidden();
if (mods.HasFlag(LegacyMods.Key1))
if (mods.HasFlagFast(LegacyMods.Key1))
yield return new ManiaModKey1();
if (mods.HasFlag(LegacyMods.Key2))
if (mods.HasFlagFast(LegacyMods.Key2))
yield return new ManiaModKey2();
if (mods.HasFlag(LegacyMods.Key3))
if (mods.HasFlagFast(LegacyMods.Key3))
yield return new ManiaModKey3();
if (mods.HasFlag(LegacyMods.Key4))
if (mods.HasFlagFast(LegacyMods.Key4))
yield return new ManiaModKey4();
if (mods.HasFlag(LegacyMods.Key5))
if (mods.HasFlagFast(LegacyMods.Key5))
yield return new ManiaModKey5();
if (mods.HasFlag(LegacyMods.Key6))
if (mods.HasFlagFast(LegacyMods.Key6))
yield return new ManiaModKey6();
if (mods.HasFlag(LegacyMods.Key7))
if (mods.HasFlagFast(LegacyMods.Key7))
yield return new ManiaModKey7();
if (mods.HasFlag(LegacyMods.Key8))
if (mods.HasFlagFast(LegacyMods.Key8))
yield return new ManiaModKey8();
if (mods.HasFlag(LegacyMods.Key9))
if (mods.HasFlagFast(LegacyMods.Key9))
yield return new ManiaModKey9();
if (mods.HasFlag(LegacyMods.KeyCoop))
if (mods.HasFlagFast(LegacyMods.KeyCoop))
yield return new ManiaModDualStages();
if (mods.HasFlag(LegacyMods.NoFail))
if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new ManiaModNoFail();
if (mods.HasFlag(LegacyMods.Random))
if (mods.HasFlagFast(LegacyMods.Random))
yield return new ManiaModRandom();
if (mods.HasFlag(LegacyMods.Mirror))
if (mods.HasFlagFast(LegacyMods.Mirror))
yield return new ManiaModMirror();
}

View File

@ -1,18 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using System;
using System.Linq;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModFadeIn : ManiaModHidden
public class ManiaModFadeIn : ManiaModPlayfieldCover
{
public override string Name => "Fade In";
public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModHidden;
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;
}

View File

@ -3,43 +3,17 @@
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 class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
public class ManiaModHidden : ManiaModPlayfieldCover
{
public override string Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
/// <summary>
/// The direction in which the cover should expand.
/// </summary>
protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
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;
}));
}
}
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
}
}

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

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
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;

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

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

View File

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

View File

@ -71,7 +71,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

View File

@ -45,7 +45,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</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)
{
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.IO.Stores;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Rulesets;
@ -90,6 +91,7 @@ namespace osu.Game.Tests.Gameplay
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
{
GameplayClockContainer gameplayContainer = null;
StoryboardSampleInfo sampleInfo = null;
TestDrawableStoryboardSample sample = null;
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
@ -101,7 +103,7 @@ namespace osu.Game.Tests.Gameplay
break;
case ModTimeRamp m:
m.InitialRate.Value = m.FinalRate.Value = expectedRate;
m.FinalRate.Value = m.InitialRate.Value = expectedRate;
break;
}
@ -117,7 +119,7 @@ namespace osu.Game.Tests.Gameplay
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
});
@ -125,7 +127,10 @@ namespace osu.Game.Tests.Gameplay
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

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

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

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");
/// <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)
{
var tempPath = Path.GetTempFileName() + ".osz";

View File

@ -118,6 +118,10 @@ namespace osu.Game.Tests.Rulesets
public BindableNumber<double> Frequency => throw new NotImplementedException();
public BindableNumber<double> Tempo => throw new NotImplementedException();
public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotImplementedException();
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotImplementedException();

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(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}

View File

@ -38,13 +38,13 @@ namespace osu.Game.Tests.Visual.Collections
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
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[]
{
manager = new CollectionManager(LocalStorage),
Content,
dialogOverlay = new DialogOverlay()
dialogOverlay = new DialogOverlay(),
});
Dependencies.Cache(manager);
@ -134,6 +134,27 @@ namespace osu.Game.Tests.Visual.Collections
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]
public void TestRemoveCollectionViaButton()
{

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -8,21 +9,17 @@ using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
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
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
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]
private GameHost host { get; set; }
@ -33,10 +30,57 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("resume player", () => Player.GameplayClockContainer.Start());
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);
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 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

@ -7,17 +7,23 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Select;
@ -137,8 +143,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
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
{
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
public new Bindable<IReadOnlyList<Mod>> FreeMods => base.FreeMods;
public new BeatmapCarousel Carousel => base.Carousel;
}
}

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
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
{

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation
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);
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("press enter", () => InputManager.Key(Key.Enter));
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);
AddStep("attempt to retry", () => results.ChildrenOfType<HotkeyRetryOverlay>().First().Action());
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);
@ -214,6 +214,21 @@ namespace osu.Game.Tests.Visual.Navigation
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);
}
private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape));

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
ensureSoleilyRemoved();
createButtonWithBeatmap(createSoleily());
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));
createButtonWithBeatmap(createSoleily());
AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);

View File

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

View File

@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Ranking
Beatmap = createTestBeatmap(author)
}));
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Text == "mapper_name"));
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Current.Value == "mapper_name"));
}
[Test]
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking
}));
AddAssert("mapped by text not present", () =>
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Current.Value, "mapped", "by")));
}
private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score);

View File

@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Settings
clickClearButton();
AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text));
AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().Text.Text.ToString()));
AddStep("click second binding", () =>
{
@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Settings
clickClearButton();
AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text));
AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(1).Text.Text.ToString()));
void clickClearButton()
{

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.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Overlays.Settings;
namespace osu.Game.Tests.Visual.Settings
{
[TestFixture]
public class TestSceneSettingsItem : OsuTestScene
{
[Test]
public void TestRestoreDefaultValueButtonVisibility()
{
TestSettingsTextBox textBox = null;
AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox
{
Current = new Bindable<string>
{
Default = "test",
Value = "test"
}
});
AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
AddStep("change value from default", () => textBox.Current.Value = "non-default");
AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0);
AddStep("restore default", () => textBox.Current.SetDefault());
AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
}
private class TestSettingsTextBox : SettingsTextBox
{
public new Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton>().Single();
}
}
}

View File

@ -7,6 +7,7 @@ using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
@ -103,27 +104,27 @@ namespace osu.Game.Tests.Visual.SongSelect
private void testBeatmapLabels(Ruleset ruleset)
{
AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version");
AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist");
AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType<OsuSpriteText>().Any(s => s.Text == $"{ruleset.ShortName}Author"));
AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version");
AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType<OsuSpriteText>().Any(s => s.Current.Value == $"{ruleset.ShortName}Author"));
}
private void testInfoLabels(int expectedCount)
{
AddAssert("check info labels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any());
AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount);
AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Count() == expectedCount);
}
[Test]
public void TestNullBeatmap()
{
selectBeatmap(null);
AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text));
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title);
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Current.Value));
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
}
[Test]

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
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[]
{

View File

@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.UserInterface
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), dependencies.Get<GameHost>(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result.Beatmaps[0];
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
for (int i = 0; i < 50; i++)
{
@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("click delete option", () =>
{
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.Value.ToLowerInvariant() == "delete"));
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete"));
InputManager.Click(MouseButton.Left);
});

View File

@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
return expectedValue == footerButtonMods.MultiplierText.Text;
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
}
private class TestFooterButtonMods : FooterButtonMods

View File

@ -3,11 +3,11 @@
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="DeepEqual" 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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Moq" Version="4.16.0" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>

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 System.IO;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Game.Graphics.Cursor;
using osu.Game.Tournament.Screens.Drawings;
namespace osu.Game.Tournament.Tests.Screens
{
public class TestSceneDrawingsScreen : TournamentTestScene
{
[BackgroundDependencyLoader]
private void load(Storage storage)
{
using (var stream = storage.GetStream("drawings.txt", FileAccess.Write))
using (var writer = new StreamWriter(stream))
{
writer.WriteLine("KR : South Korea : KOR");
writer.WriteLine("US : United States : USA");
writer.WriteLine("PH : Philippines : PHL");
writer.WriteLine("BR : Brazil : BRA");
writer.WriteLine("JP : Japan : JPN");
}
Add(new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Child = new DrawingsScreen()
});
}
}
}

View File

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>

View File

@ -74,9 +74,9 @@ namespace osu.Game.Tournament.Components
{
new TournamentSpriteText
{
Text = new LocalisedString((
Text = new RomanisableString(
$"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}",
$"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")),
$"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"),
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
},
new FillFlowContainer

View File

@ -345,7 +345,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
Flag.Anchor = Anchor.Centre;
Flag.Origin = Anchor.Centre;
Flag.Scale = new Vector2(0.9f);
Flag.Scale = new Vector2(0.7f);
InternalChildren = new Drawable[]
{

View File

@ -69,8 +69,8 @@ namespace osu.Game.Beatmaps
/// </summary>
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available.</returns>
public IBindable<StarDifficulty> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated).</returns>
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
{
var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
@ -90,9 +90,9 @@ namespace osu.Game.Beatmaps
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param>
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with. If <c>null</c>, no mods will be assumed.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available.</returns>
public IBindable<StarDifficulty> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods,
CancellationToken cancellationToken = default)
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state.</returns>
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods,
CancellationToken cancellationToken = default)
=> createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken);
/// <summary>
@ -313,7 +313,7 @@ namespace osu.Game.Beatmaps
}
}
private class BindableStarDifficulty : Bindable<StarDifficulty>
private class BindableStarDifficulty : Bindable<StarDifficulty?>
{
public readonly BeatmapInfo Beatmap;
public readonly CancellationToken CancellationToken;

View File

@ -20,6 +20,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
@ -311,6 +312,9 @@ namespace osu.Game.Beatmaps
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
// best effort; may be higher than expected.
GlobalStatistics.Get<int>(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count();
return working;
}
}

View File

@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables
this.mods = mods;
}
private IBindable<StarDifficulty> localStarDifficulty;
private IBindable<StarDifficulty?> localStarDifficulty;
[BackgroundDependencyLoader]
private void load()
@ -160,7 +160,11 @@ namespace osu.Game.Beatmaps.Drawables
localStarDifficulty = ruleset != null
? difficultyCache.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token)
: difficultyCache.GetBindableDifficulty(beatmap, difficultyCancellation.Token);
localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue);
localStarDifficulty.BindValueChanged(d =>
{
if (d.NewValue is StarDifficulty diff)
StarDifficulty.Value = diff;
});
}
protected override void Dispose(bool isDisposing)

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Beatmaps.Timing;
@ -348,8 +349,8 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 8)
{
LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]);
kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai);
omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine);
kiaiMode = effectFlags.HasFlagFast(LegacyEffectFlags.Kiai);
omitFirstBarSignature = effectFlags.HasFlagFast(LegacyEffectFlags.OmitFirstBarLine);
}
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();

View File

@ -12,7 +12,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -34,8 +33,6 @@ namespace osu.Game.Beatmaps
protected AudioManager AudioManager { get; }
private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s");
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
{
AudioManager = audioManager;
@ -47,8 +44,6 @@ namespace osu.Game.Beatmaps
waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
skin = new RecyclableLazy<ISkin>(GetSkin);
total_count.Value++;
}
protected virtual Track GetVirtualTrack(double emptyLength = 0)
@ -331,11 +326,6 @@ namespace osu.Game.Beatmaps
protected virtual ISkin GetSkin() => new DefaultSkin();
private readonly RecyclableLazy<ISkin> skin;
~WorkingBeatmap()
{
total_count.Value--;
}
public class RecyclableLazy<T>
{
private Lazy<T> lazy;

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@ -121,7 +122,7 @@ namespace osu.Game.Collections
Current.TriggerChange();
}
protected override string GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d =>
{
@ -139,7 +140,7 @@ namespace osu.Game.Collections
public readonly Bindable<CollectionFilterMenuItem> SelectedItem = new Bindable<CollectionFilterMenuItem>();
private readonly Bindable<string> collectionName = new Bindable<string>();
protected override string Label
protected override LocalisableString Label
{
get => base.Label;
set { } // See updateText().

View File

@ -36,7 +36,19 @@ namespace osu.Game.Collections
}
public bool Equals(CollectionFilterMenuItem other)
=> other != null && CollectionName.Value == other.CollectionName.Value;
{
if (other == null)
return false;
// collections may have the same name, so compare first on reference equality.
// this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager.
if (Collection != null)
return Collection == other.Collection;
// fallback to name-based comparison.
// this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below).
return CollectionName.Value == other.CollectionName.Value;
}
public override int GetHashCode() => CollectionName.Value.GetHashCode();
}

View File

@ -138,10 +138,10 @@ namespace osu.Game.Collections
PostNotification?.Invoke(notification);
var collection = readCollections(stream, notification);
await importCollections(collection);
var collections = readCollections(stream, notification);
await importCollections(collections);
notification.CompletionText = $"Imported {collection.Count} collections";
notification.CompletionText = $"Imported {collections.Count} collections";
notification.State = ProgressNotificationState.Completed;
}
@ -155,7 +155,7 @@ namespace osu.Game.Collections
{
foreach (var newCol in newCollections)
{
var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value);
if (existing == null)
Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });

View File

@ -8,6 +8,7 @@ using System.Reflection;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Overlays.Settings;
namespace osu.Game.Configuration
@ -22,9 +23,9 @@ namespace osu.Game.Configuration
/// </remarks>
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)]
public class SettingSourceAttribute : Attribute
public class SettingSourceAttribute : Attribute, IComparable<SettingSourceAttribute>
{
public string Label { get; }
public LocalisableString Label { get; }
public string Description { get; }
@ -41,6 +42,21 @@ namespace osu.Game.Configuration
{
OrderPosition = orderPosition;
}
public int CompareTo(SettingSourceAttribute other)
{
if (OrderPosition == other.OrderPosition)
return 0;
// unordered items come last (are greater than any ordered items).
if (OrderPosition == null)
return 1;
if (other.OrderPosition == null)
return -1;
// ordered items are sorted by the order value.
return OrderPosition.Value.CompareTo(other.OrderPosition);
}
}
public static class SettingSourceExtensions
@ -136,14 +152,9 @@ namespace osu.Game.Configuration
}
}
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
{
var original = obj.GetSettingsSourceProperties();
var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition);
var unordered = original.Except(orderedRelative);
return orderedRelative.Concat(unordered);
}
public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
=> obj.GetSettingsSourceProperties()
.OrderBy(attr => attr.Item1)
.ToArray();
}
}

View File

@ -141,6 +141,13 @@ namespace osu.Game.Database
protected async Task<IEnumerable<TModel>> Import(ProgressNotification notification, params ImportTask[] tasks)
{
if (tasks.Length == 0)
{
notification.CompletionText = $"No {HumanisedModelName}s were found to import!";
notification.State = ProgressNotificationState.Completed;
return Enumerable.Empty<TModel>();
}
notification.Progress = 0;
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";

View File

@ -54,10 +54,5 @@ namespace osu.Game.Database
Dispose(true);
GC.SuppressFinalize(this);
}
~DatabaseWriteUsage()
{
Dispose(false);
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osuTK;
namespace osu.Game.Graphics.Sprites
@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Sprites
{
private readonly OsuSpriteText spriteText, blurredText;
public string Text
public LocalisableString Text
{
get => spriteText.Text;
set => blurredText.Text = spriteText.Text = value;

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
namespace osu.Game.Graphics.UserInterface
{
@ -24,11 +25,11 @@ namespace osu.Game.Graphics.UserInterface
set
{
direction = value;
base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
foreach (var bar in Children)
{
bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
bar.Direction = direction;
}
}
@ -56,14 +57,14 @@ namespace osu.Game.Graphics.UserInterface
if (bar.Bar != null)
{
bar.Bar.Length = length;
bar.Bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
}
else
{
Add(new Bar
{
RelativeSizeAxes = Axes.Both,
Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
Length = length,
Direction = Direction,
});

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface
protected class TextContainer : Container, IHasText
{
public string Text
public LocalisableString Text
{
get => NormalText.Text;
set

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@ -21,9 +22,9 @@ namespace osu.Game.Graphics.UserInterface
/// </summary>
public class OsuButton : Button
{
public string Text
public LocalisableString Text
{
get => SpriteText?.Text;
get => SpriteText?.Text ?? default;
set
{
if (SpriteText != null)

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -168,7 +169,7 @@ namespace osu.Game.Graphics.UserInterface
protected new class Content : FillFlowContainer, IHasText
{
public string Text
public LocalisableString Text
{
get => Label.Text;
set => Label.Text = value;
@ -215,7 +216,7 @@ namespace osu.Game.Graphics.UserInterface
{
protected readonly SpriteText Text;
protected override string Label
protected override LocalisableString Label
{
get => Text.Text;
set => Text.Text = value;

View File

@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@ -35,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
}
}
public string Text
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;

View File

@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK;
using System.Collections.Generic;
using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@ -18,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface
{
private const int duration = 200;
public string Text
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;

View File

@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface
});
}
public virtual IEnumerable<string> FilterTerms => new[] { Text };
public virtual IEnumerable<string> FilterTerms => new[] { Text.ToString() };
public bool MatchingFilter
{

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework.Audio.Track;
using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
@ -56,15 +57,15 @@ namespace osu.Game.Graphics.UserInterface
set
{
base.Origin = value;
c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
c1.Origin = c1.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
c2.Origin = c2.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
X = value.HasFlagFast(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
Remove(c1);
Remove(c2);
c1.Depth = value.HasFlag(Anchor.x2) ? 0 : 1;
c2.Depth = value.HasFlag(Anchor.x2) ? 1 : 0;
c1.Depth = value.HasFlagFast(Anchor.x2) ? 0 : 1;
c2.Depth = value.HasFlagFast(Anchor.x2) ? 1 : 0;
Add(c1);
Add(c2);
}

View File

@ -10,6 +10,7 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using osu.Framework;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Extensions.ObjectExtensions;
@ -246,7 +247,14 @@ namespace osu.Game.Online.API
this.password = password;
}
public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this, versionHash);
public IHubClientConnector GetHubConnector(string clientName, string endpoint)
{
// disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805.
if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS)
return null;
return new HubClientConnector(clientName, endpoint, this, versionHash);
}
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
{
@ -373,7 +381,13 @@ namespace osu.Game.Online.API
public void Queue(APIRequest request)
{
lock (queue) queue.Enqueue(request);
lock (queue)
{
if (state.Value == APIState.Offline)
return;
queue.Enqueue(request);
}
}
private void flushQueue(bool failOldRequests = true)
@ -394,8 +408,6 @@ namespace osu.Game.Online.API
public void Logout()
{
flushQueue();
password = null;
authentication.Clear();
@ -407,6 +419,7 @@ namespace osu.Game.Online.API
});
state.Value = APIState.Offline;
flushQueue();
}
private static User createGuestUser() => new GuestUser();

View File

@ -1,6 +1,7 @@
// 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.IO.Network;
using osu.Game.Beatmaps;
namespace osu.Game.Online.API.Requests
@ -15,6 +16,13 @@ namespace osu.Game.Online.API.Requests
this.noVideo = noVideo;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Timeout = 60000;
return req;
}
protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}";
}
}

View File

@ -152,7 +152,7 @@ namespace osu.Game.Online.Chat
createNewPrivateMessageRequest.Failure += exception =>
{
Logger.Error(exception, "Posting message failed.");
handlePostException(exception);
target.ReplaceMessage(message, null);
dequeueAndRun();
};
@ -171,7 +171,7 @@ namespace osu.Game.Online.Chat
req.Failure += exception =>
{
Logger.Error(exception, "Posting message failed.");
handlePostException(exception);
target.ReplaceMessage(message, null);
dequeueAndRun();
};
@ -184,6 +184,14 @@ namespace osu.Game.Online.Chat
dequeueAndRun();
}
private static void handlePostException(Exception exception)
{
if (exception is APIException apiException)
Logger.Log(apiException.Message, level: LogLevel.Important);
else
Logger.Error(exception, "Posting message failed.");
}
/// <summary>
/// Posts a command locally. Commands like /help will result in a help message written in the current channel.
/// </summary>

View File

@ -361,14 +361,6 @@ namespace osu.Game
PerformFromScreen(screen =>
{
// we might already be at song select, so a check is required before performing the load to solo.
if (screen is MainMenu)
menuScreen.LoadToSolo();
// we might even already be at the song
if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true))
return;
// Find beatmaps that match our predicate.
var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList();
@ -381,9 +373,16 @@ namespace osu.Game
?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value))
?? beatmaps.First();
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
}, validScreens: new[] { typeof(SongSelect) });
if (screen is IHandlePresentBeatmap presentableScreen)
{
presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset);
}
else
{
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
}
}, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) });
}
/// <summary>

View File

@ -84,14 +84,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)),
Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)),
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
},

View File

@ -107,14 +107,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)),
Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)),
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -96,7 +97,7 @@ namespace osu.Game.Overlays.BeatmapSet
public string TooltipText { get; }
public string Value
public LocalisableString Value
{
get => value.Text;
set => this.value.Text = value;

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -204,7 +203,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
this.text = text;
}
public LocalisedString Text
public string Text
{
set => text.Text = value;
}

View File

@ -4,19 +4,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osuTK;
namespace osu.Game.Overlays.Chat.Selection
{
public class ChannelSection : Container, IHasFilterableChildren
{
private readonly OsuSpriteText header;
public readonly FillFlowContainer<ChannelListItem> ChannelFlow;
public IEnumerable<IFilterable> FilterableChildren => ChannelFlow.Children;
@ -29,12 +27,6 @@ namespace osu.Game.Overlays.Chat.Selection
public bool FilteringActive { get; set; }
public string Header
{
get => header.Text;
set => header.Text = value.ToUpperInvariant();
}
public IEnumerable<Channel> Channels
{
set => ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c));
@ -47,9 +39,10 @@ namespace osu.Game.Overlays.Chat.Selection
Children = new Drawable[]
{
header = new OsuSpriteText
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
Text = "All Channels".ToUpperInvariant()
},
ChannelFlow = new FillFlowContainer<ChannelListItem>
{

View File

@ -131,11 +131,7 @@ namespace osu.Game.Overlays.Chat.Selection
{
sectionsFlow.ChildrenEnumerable = new[]
{
new ChannelSection
{
Header = "All Channels",
Channels = channels,
},
new ChannelSection { Channels = channels, },
};
foreach (ChannelSection s in sectionsFlow.Children)

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Comments.Buttons
{
public abstract class CommentRepliesButton : CompositeDrawable
{
protected string Text
protected LocalisableString Text
{
get => text.Text;
set => text.Text = value;

View File

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.Dialog
{
/// <summary>
/// A dialog which confirms a user action.
/// </summary>
public class ConfirmDialog : PopupDialog
{
/// <summary>
/// Construct a new confirmation dialog.
/// </summary>
/// <param name="message">The description of the action to be displayed to the user.</param>
/// <param name="onConfirm">An action to perform on confirmation.</param>
/// <param name="onCancel">An optional action to perform on cancel.</param>
public ConfirmDialog(string message, Action onConfirm, Action onCancel = null)
{
HeaderText = message;
BodyText = "Last chance to turn back";
Icon = FontAwesome.Solid.ExclamationTriangle;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes",
Action = onConfirm
},
new PopupDialogCancelButton
{
Text = @"Cancel",
Action = onCancel
},
};
}
}
}

View File

@ -51,7 +51,7 @@ namespace osu.Game.Overlays.KeyBinding
private FillFlowContainer cancelAndClearButtons;
private FillFlowContainer<KeyButton> buttons;
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text);
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
public KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
{

View File

@ -23,13 +23,15 @@ namespace osu.Game.Overlays.Mods
public FillFlowContainer<ModButtonEmpty> ButtonsContainer { get; }
protected IReadOnlyList<ModButton> Buttons { get; private set; } = Array.Empty<ModButton>();
public Action<Mod> Action;
public Key[] ToggleKeys;
public readonly ModType ModType;
public IEnumerable<Mod> SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null);
public IEnumerable<Mod> SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null);
private CancellationTokenSource modsLoadCts;
@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Mods
ButtonsContainer.ChildrenEnumerable = c;
}, (modsLoadCts = new CancellationTokenSource()).Token);
buttons = modContainers.OfType<ModButton>().ToArray();
Buttons = modContainers.OfType<ModButton>().ToArray();
header.FadeIn(200);
this.FadeIn(200);
@ -88,8 +90,6 @@ namespace osu.Game.Overlays.Mods
{
}
private ModButton[] buttons = Array.Empty<ModButton>();
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.ControlPressed) return false;
@ -97,8 +97,8 @@ namespace osu.Game.Overlays.Mods
if (ToggleKeys != null)
{
var index = Array.IndexOf(ToggleKeys, e.Key);
if (index > -1 && index < buttons.Length)
buttons[index].SelectNext(e.ShiftPressed ? -1 : 1);
if (index > -1 && index < Buttons.Count)
Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1);
}
return base.OnKeyDown(e);
@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Mods
{
pendingSelectionOperations.Clear();
foreach (var button in buttons.Where(b => !b.Selected))
foreach (var button in Buttons.Where(b => !b.Selected))
pendingSelectionOperations.Enqueue(() => button.SelectAt(0));
}
@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Mods
public void DeselectAll()
{
pendingSelectionOperations.Clear();
DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
}
/// <summary>
@ -161,7 +161,7 @@ namespace osu.Game.Overlays.Mods
/// <param name="immediate">Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow.</param>
public void DeselectTypes(IEnumerable<Type> modTypes, bool immediate = false)
{
foreach (var button in buttons)
foreach (var button in Buttons)
{
if (button.SelectedMod == null) continue;
@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Mods
/// <param name="newSelectedMods">The new list of selected mods to select.</param>
public void UpdateSelectedButtons(IReadOnlyList<Mod> newSelectedMods)
{
foreach (var button in buttons)
foreach (var button in Buttons)
updateButtonSelection(button, newSelectedMods);
}

View File

@ -456,6 +456,7 @@ namespace osu.Game.Overlays.Mods
}
updateSelectedButtons();
OnAvailableModsChanged();
}
/// <summary>
@ -533,6 +534,13 @@ namespace osu.Game.Overlays.Mods
private void playSelectedSound() => sampleOn?.Play();
private void playDeselectedSound() => sampleOff?.Play();
/// <summary>
/// Invoked after <see cref="availableMods"/> has changed.
/// </summary>
protected virtual void OnAvailableModsChanged()
{
}
/// <summary>
/// Invoked when a new <see cref="Mod"/> has been selected.
/// </summary>

View File

@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Music
artistColour = colours.Gray9;
HandleColour = colours.Gray5;
title = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title)));
artist = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist)));
title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title));
artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist));
}
protected override void LoadComplete()

View File

@ -8,10 +8,11 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Notifications
{
@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Notifications
public NotificationSection(string title, string clearButtonText)
{
this.clearButtonText = clearButtonText;
this.clearButtonText = clearButtonText.ToUpperInvariant();
titleText = title;
}
@ -121,7 +122,20 @@ namespace osu.Game.Overlays.Notifications
{
base.Update();
countDrawable.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString();
countDrawable.Text = getVisibleCount().ToString();
}
private int getVisibleCount()
{
int count = 0;
foreach (var c in notifications)
{
if (c.Alpha > 0.99f)
count++;
}
return count;
}
private class ClearAllButton : OsuClickableContainer
@ -138,10 +152,10 @@ namespace osu.Game.Overlays.Notifications
};
}
public string Text
public LocalisableString Text
{
get => text.Text;
set => text.Text = value.ToUpperInvariant();
set => text.Text = value;
}
}

View File

@ -293,8 +293,8 @@ namespace osu.Game.Overlays
else
{
BeatmapMetadata metadata = beatmap.Metadata;
title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title);
artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
}
});

View File

@ -17,6 +17,7 @@ using osu.Game.Overlays.Comments;
using JetBrains.Annotations;
using System;
using osu.Framework.Extensions;
using osu.Framework.Localisation;
namespace osu.Game.Overlays
{
@ -30,7 +31,7 @@ namespace osu.Game.Overlays
set => current.Current = value;
}
public string Title
public LocalisableString Title
{
get => text.Text;
set => text.Text = value;

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
@ -13,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
@ -129,14 +129,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
{
new OsuSpriteText
{
Text = new LocalisedString((
Text = new RomanisableString(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ",
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")),
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "),
Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
new OsuSpriteText
{
Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Regular)
},
};

View File

@ -256,16 +256,16 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = new LocalisedString((
Text = new RomanisableString(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ",
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} ")),
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} "),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
Font = OsuFont.GetFont(size: 12, italics: true)
},
};

View File

@ -6,6 +6,7 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings.Sections.Audio
@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl
{
protected override string GenerateItemText(string item)
protected override LocalisableString GenerateItemText(string item)
=> string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item);
}
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
@ -234,7 +235,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private class ResolutionDropdownControl : DropdownControl
{
protected override string GenerateItemText(Size item)
protected override LocalisableString GenerateItemText(Size item)
{
if (item == new Size(9999, 9999))
return "Default";

View File

@ -17,7 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override string Header => "Mouse";
private readonly BindableBool rawInputToggle = new BindableBool();
private Bindable<double> sensitivityBindable = new BindableDouble();
private Bindable<double> configSensitivity;
private Bindable<double> localSensitivity;
private Bindable<string> ignoredInputHandlers;
private Bindable<WindowMode> windowMode;
@ -26,12 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input
[BackgroundDependencyLoader]
private void load(OsuConfigManager osuConfig, FrameworkConfigManager config)
{
var configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
// use local bindable to avoid changing enabled state of game host's bindable.
sensitivityBindable = configSensitivity.GetUnboundCopy();
configSensitivity.BindValueChanged(val => sensitivityBindable.Value = val.NewValue);
sensitivityBindable.BindValueChanged(val => configSensitivity.Value = val.NewValue);
configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
localSensitivity = configSensitivity.GetUnboundCopy();
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
Children = new Drawable[]
{
@ -43,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
new SensitivitySetting
{
LabelText = "Cursor sensitivity",
Current = sensitivityBindable
Current = localSensitivity
},
new SettingsCheckbox
{
@ -66,14 +70,35 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
},
};
}
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
windowMode.BindValueChanged(mode => confineMouseModeSetting.Alpha = mode.NewValue == WindowMode.Fullscreen ? 0 : 1, true);
protected override void LoadComplete()
{
base.LoadComplete();
configSensitivity.BindValueChanged(val => localSensitivity.Value = val.NewValue, true);
localSensitivity.BindValueChanged(val => configSensitivity.Value = val.NewValue);
windowMode.BindValueChanged(mode =>
{
var isFullscreen = mode.NewValue == WindowMode.Fullscreen;
if (isFullscreen)
{
confineMouseModeSetting.Current.Disabled = true;
confineMouseModeSetting.TooltipText = "Not applicable in full screen mode";
}
else
{
confineMouseModeSetting.Current.Disabled = false;
confineMouseModeSetting.TooltipText = string.Empty;
}
}, true);
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{
rawInputToggle.Disabled = true;
sensitivityBindable.Disabled = true;
localSensitivity.Disabled = true;
}
else
{
@ -86,12 +111,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler;
};
ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
ignoredInputHandlers.ValueChanged += handler =>
{
bool raw = !handler.NewValue.Contains("Raw");
rawInputToggle.Value = raw;
sensitivityBindable.Disabled = !raw;
localSensitivity.Disabled = !raw;
};
ignoredInputHandlers.TriggerChange();

View File

@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@ -178,7 +179,7 @@ namespace osu.Game.Overlays.Settings.Sections
private class SkinDropdownControl : DropdownControl
{
protected override string GenerateItemText(SkinInfo item) => item.ToString();
protected override LocalisableString GenerateItemText(SkinInfo item) => item.ToString();
}
}

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