mirror of
https://github.com/ppy/osu.git
synced 2025-01-18 10:53:21 +08:00
Merge branch 'master' into medal-display-fix
This commit is contained in:
commit
c4374c7315
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -114,7 +114,10 @@ jobs:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Install .NET workloads
|
||||
run: dotnet workload install android
|
||||
# since windows image 20241113.3.0, not specifying a version here
|
||||
# installs the .NET 7 version of android workload for very unknown reasons.
|
||||
# revisit once we upgrade to .NET 9, it's probably fixed there.
|
||||
run: dotnet workload install android --version (dotnet --version)
|
||||
|
||||
- name: Compile
|
||||
run: dotnet build -c Debug osu.Android.slnf
|
||||
|
2
.github/workflows/diffcalc.yml
vendored
2
.github/workflows/diffcalc.yml
vendored
@ -115,7 +115,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check permissions
|
||||
run: |
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte)
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte tsunyoku stanriders)
|
||||
for i in "${ALLOWED_USERS[@]}"; do
|
||||
if [[ "${{ github.actor }}" == "$i" ]]; then
|
||||
exit 0
|
||||
|
@ -1,57 +0,0 @@
|
||||
# .NET Code Style
|
||||
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
||||
|
||||
# IDE0001: Simplify names
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
|
||||
# IDE0002: Simplify member access
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
|
||||
# IDE0003: Remove qualification
|
||||
dotnet_diagnostic.IDE0003.severity = warning
|
||||
|
||||
# IDE0004: Remove unnecessary cast
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# IDE0005: Remove unnecessary imports
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# IDE0034: Simplify default literal
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# IDE0036: Sort modifiers
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0040: Add accessibility modifier
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0049: Use keyword for type name
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = warning
|
||||
|
||||
# IDE0051: Private method is unused
|
||||
dotnet_diagnostic.IDE0051.severity = silent
|
||||
|
||||
# IDE0052: Private member is unused
|
||||
dotnet_diagnostic.IDE0052.severity = silent
|
||||
|
||||
# IDE0073: File header
|
||||
dotnet_diagnostic.IDE0073.severity = warning
|
||||
|
||||
# IDE0130: Namespace mismatch with folder
|
||||
dotnet_diagnostic.IDE0130.severity = warning
|
||||
|
||||
# IDE1006: Naming style
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
#Disable operator overloads requiring alternate named methods
|
||||
dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
||||
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
|
||||
# See: https://github.com/ppy/osu/pull/19677
|
||||
dotnet_diagnostic.OSUF001.severity = none
|
@ -14,10 +14,6 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
|
||||
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
||||
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
||||
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
|
||||
M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
|
||||
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
|
||||
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
|
||||
M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead.
|
||||
M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead.
|
||||
|
109
CodeAnalysis/osu.globalconfig
Normal file
109
CodeAnalysis/osu.globalconfig
Normal file
@ -0,0 +1,109 @@
|
||||
# .NET Code Style
|
||||
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
||||
is_global = true
|
||||
|
||||
# IDE0001: Simplify names
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
|
||||
# IDE0002: Simplify member access
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
|
||||
# IDE0003: Remove qualification
|
||||
dotnet_diagnostic.IDE0003.severity = warning
|
||||
|
||||
# IDE0004: Remove unnecessary cast
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# IDE0005: Remove unnecessary imports
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# IDE0034: Simplify default literal
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# IDE0036: Sort modifiers
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0040: Add accessibility modifier
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0049: Use keyword for type name
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = warning
|
||||
|
||||
# IDE0051: Private method is unused
|
||||
dotnet_diagnostic.IDE0051.severity = silent
|
||||
|
||||
# IDE0052: Private member is unused
|
||||
dotnet_diagnostic.IDE0052.severity = silent
|
||||
|
||||
# IDE0073: File header
|
||||
dotnet_diagnostic.IDE0073.severity = warning
|
||||
|
||||
# IDE0130: Namespace mismatch with folder
|
||||
dotnet_diagnostic.IDE0130.severity = warning
|
||||
|
||||
# IDE1006: Naming style
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
# CA1305: Specify IFormatProvider
|
||||
# Too many noisy warnings for parsing/formatting numbers
|
||||
dotnet_diagnostic.CA1305.severity = none
|
||||
|
||||
# CA1507: Use nameof to express symbol names
|
||||
# Flaggs serialization name attributes
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
|
||||
# CA1806: Do not ignore method results
|
||||
# The usages for numeric parsing are explicitly optional
|
||||
dotnet_diagnostic.CA1806.severity = suggestion
|
||||
|
||||
# CA1822: Mark members as static
|
||||
# Potential false positive around reflection/too much noise
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
# CA1826: Do not use Enumerable method on indexable collections
|
||||
dotnet_diagnostic.CA1826.severity = suggestion
|
||||
|
||||
# CA1859: Use concrete types when possible for improved performance
|
||||
# Involves design considerations
|
||||
dotnet_diagnostic.CA1859.severity = suggestion
|
||||
|
||||
# CA1860: Avoid using 'Enumerable.Any()' extension method
|
||||
dotnet_diagnostic.CA1860.severity = suggestion
|
||||
|
||||
# CA1861: Avoid constant arrays as arguments
|
||||
# Outdated with collection expressions
|
||||
dotnet_diagnostic.CA1861.severity = suggestion
|
||||
|
||||
# CA2007: Consider calling ConfigureAwait on the awaited task
|
||||
dotnet_diagnostic.CA2007.severity = warning
|
||||
|
||||
# CA2016: Forward the 'CancellationToken' parameter to methods
|
||||
# Some overloads are having special handling for debugger
|
||||
dotnet_diagnostic.CA2016.severity = suggestion
|
||||
|
||||
# CA2021: Do not call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
|
||||
# Causing a lot of false positives with generics
|
||||
dotnet_diagnostic.CA2021.severity = none
|
||||
|
||||
# CA2101: Specify marshaling for P/Invoke string arguments
|
||||
# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport
|
||||
dotnet_diagnostic.CA2101.severity = none
|
||||
|
||||
# CA2201: Do not raise reserved exception types
|
||||
dotnet_diagnostic.CA2201.severity = warning
|
||||
|
||||
# CA2208: Instantiate argument exceptions correctly
|
||||
dotnet_diagnostic.CA2208.severity = suggestion
|
||||
|
||||
# CA2242: Test for NaN correctly
|
||||
dotnet_diagnostic.CA2242.severity = warning
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
||||
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
|
||||
# See: https://github.com/ppy/osu/pull/19677
|
||||
dotnet_diagnostic.OSUF001.severity = none
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
|
||||
<Rule Id="CA1016" Action="None" />
|
||||
<Rule Id="CA1028" Action="None" />
|
||||
<Rule Id="CA1031" Action="None" />
|
||||
<Rule Id="CA1034" Action="None" />
|
||||
<Rule Id="CA1036" Action="None" />
|
||||
<Rule Id="CA1040" Action="None" />
|
||||
<Rule Id="CA1044" Action="None" />
|
||||
<Rule Id="CA1051" Action="None" />
|
||||
<Rule Id="CA1054" Action="None" />
|
||||
<Rule Id="CA1056" Action="None" />
|
||||
<Rule Id="CA1062" Action="None" />
|
||||
<Rule Id="CA1063" Action="None" />
|
||||
<Rule Id="CA1067" Action="None" />
|
||||
<Rule Id="CA1707" Action="None" />
|
||||
<Rule Id="CA1710" Action="None" />
|
||||
<Rule Id="CA1714" Action="None" />
|
||||
<Rule Id="CA1716" Action="None" />
|
||||
<Rule Id="CA1717" Action="None" />
|
||||
<Rule Id="CA1720" Action="None" />
|
||||
<Rule Id="CA1721" Action="None" />
|
||||
<Rule Id="CA1724" Action="None" />
|
||||
<Rule Id="CA1801" Action="None" />
|
||||
<Rule Id="CA1806" Action="None" />
|
||||
<Rule Id="CA1812" Action="None" />
|
||||
<Rule Id="CA1814" Action="None" />
|
||||
<Rule Id="CA1815" Action="None" />
|
||||
<Rule Id="CA1819" Action="None" />
|
||||
<Rule Id="CA1822" Action="None" />
|
||||
<Rule Id="CA1823" Action="None" />
|
||||
<Rule Id="CA2007" Action="Warning" />
|
||||
<Rule Id="CA2214" Action="None" />
|
||||
<Rule Id="CA2227" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.CSharp.Analyzers" RuleNamespace="Microsoft.CodeQuality.CSharp.Analyzers">
|
||||
<Rule Id="CA1001" Action="None" />
|
||||
<Rule Id="CA1032" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
|
||||
<Rule Id="CA1303" Action="None" />
|
||||
<Rule Id="CA1304" Action="None" />
|
||||
<Rule Id="CA1305" Action="None" />
|
||||
<Rule Id="CA1307" Action="None" />
|
||||
<Rule Id="CA1308" Action="None" />
|
||||
<Rule Id="CA1816" Action="None" />
|
||||
<Rule Id="CA1826" Action="None" />
|
||||
<Rule Id="CA2000" Action="None" />
|
||||
<Rule Id="CA2008" Action="None" />
|
||||
<Rule Id="CA2213" Action="None" />
|
||||
<Rule Id="CA2235" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.NetCore.CSharp.Analyzers" RuleNamespace="Microsoft.NetCore.CSharp.Analyzers">
|
||||
<Rule Id="CA1309" Action="Warning" />
|
||||
<Rule Id="CA2201" Action="Warning" />
|
||||
</Rules>
|
||||
</RuleSet>
|
@ -18,9 +18,21 @@
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<!-- Rider compatibility: .globalconfig needs to be explicitly referenced instead of using the global file name. -->
|
||||
<GlobalAnalyzerConfigFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\osu.globalconfig" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||
<AnalysisMode>Default</AnalysisMode>
|
||||
<AnalysisModeDesign>Default</AnalysisModeDesign>
|
||||
<AnalysisModeDocumentation>Recommended</AnalysisModeDocumentation>
|
||||
<AnalysisModeGlobalization>Recommended</AnalysisModeGlobalization>
|
||||
<AnalysisModeInteroperability>Recommended</AnalysisModeInteroperability>
|
||||
<AnalysisModeMaintainability>Recommended</AnalysisModeMaintainability>
|
||||
<AnalysisModeNaming>Default</AnalysisModeNaming>
|
||||
<AnalysisModePerformance>Minimum</AnalysisModePerformance>
|
||||
<AnalysisModeReliability>Recommended</AnalysisModeReliability>
|
||||
<AnalysisModeSecurity>Default</AnalysisModeSecurity>
|
||||
<AnalysisModeUsage>Default</AnalysisModeUsage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Documentation">
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1118.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1206.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -15,6 +15,7 @@ using osu.Framework.Threading;
|
||||
using osu.Game;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -47,6 +48,9 @@ namespace osu.Desktop
|
||||
[Resolved]
|
||||
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
@ -117,7 +121,9 @@ namespace osu.Desktop
|
||||
status.BindValueChanged(_ => schedulePresenceUpdate());
|
||||
activity.BindValueChanged(_ => schedulePresenceUpdate());
|
||||
privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
|
||||
|
||||
multiplayerClient.RoomUpdated += onRoomUpdated;
|
||||
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||
}
|
||||
|
||||
private void onReady(object _, ReadyMessage __)
|
||||
@ -133,6 +139,8 @@ namespace osu.Desktop
|
||||
|
||||
private void onRoomUpdated() => schedulePresenceUpdate();
|
||||
|
||||
private void onStatisticsUpdated(UserStatisticsUpdate _) => schedulePresenceUpdate();
|
||||
|
||||
private ScheduledDelegate? presenceUpdateDelegate;
|
||||
|
||||
private void schedulePresenceUpdate()
|
||||
@ -167,7 +175,7 @@ namespace osu.Desktop
|
||||
presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation));
|
||||
presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
|
||||
|
||||
if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0)
|
||||
if (activity.Value.GetBeatmapID(hideIdentifiableInformation) is int beatmapId && beatmapId > 0)
|
||||
{
|
||||
presence.Buttons = new[]
|
||||
{
|
||||
@ -229,10 +237,8 @@ namespace osu.Desktop
|
||||
presence.Assets.LargeImageText = string.Empty;
|
||||
else
|
||||
{
|
||||
if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics? statistics))
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
||||
else
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
||||
var statistics = statisticsProvider.GetStatisticsFor(ruleset.Value);
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics?.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
||||
}
|
||||
|
||||
// small image
|
||||
@ -327,25 +333,14 @@ namespace osu.Desktop
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int? getBeatmapID(UserActivity activity)
|
||||
{
|
||||
switch (activity)
|
||||
{
|
||||
case UserActivity.InGame game:
|
||||
return game.BeatmapID;
|
||||
|
||||
case UserActivity.EditingBeatmap edit:
|
||||
return edit.BeatmapID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (multiplayerClient.IsNotNull())
|
||||
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
||||
|
||||
if (statisticsProvider.IsNotNull())
|
||||
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||
|
||||
client.Dispose();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ namespace osu.Desktop
|
||||
|
||||
var hostOptions = new HostOptions
|
||||
{
|
||||
IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null,
|
||||
IPCPipeName = !tournamentClient ? OsuGame.IPC_PIPE_NAME : null,
|
||||
FriendlyGameName = OsuGameBase.GAME_NAME,
|
||||
};
|
||||
|
||||
|
@ -148,15 +148,7 @@ namespace osu.Desktop.Windows
|
||||
foreach (var association in uri_associations)
|
||||
association.UpdateDescription(getLocalisedString(association.Description));
|
||||
|
||||
string getLocalisedString(LocalisableString s)
|
||||
{
|
||||
if (localisation == null)
|
||||
return s.ToString();
|
||||
|
||||
var b = localisation.GetLocalisedBindableString(s);
|
||||
b.UnbindAll();
|
||||
return b.Value;
|
||||
}
|
||||
string getLocalisedString(LocalisableString s) => localisation?.GetLocalisedString(s) ?? s.ToString();
|
||||
}
|
||||
|
||||
#region Native interop
|
||||
|
@ -4,28 +4,54 @@
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkUnstableRate : BenchmarkTest
|
||||
{
|
||||
private List<HitEvent> events = null!;
|
||||
private readonly List<List<HitEvent>> incrementalEventLists = new List<List<HitEvent>>();
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
events = new List<HitEvent>();
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, new HitObject(), null, null));
|
||||
var events = new List<HitEvent>();
|
||||
|
||||
for (int i = 0; i < 2048; i++)
|
||||
{
|
||||
// Ensure the object has hit windows populated.
|
||||
var hitObject = new HitCircle();
|
||||
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, hitObject, null, null));
|
||||
|
||||
incrementalEventLists.Add(new List<HitEvent>(events));
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CalculateUnstableRate()
|
||||
{
|
||||
_ = events.CalculateUnstableRate();
|
||||
for (int i = 0; i < 2048; i++)
|
||||
{
|
||||
var events = incrementalEventLists[i];
|
||||
_ = events.CalculateUnstableRate();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CalculateUnstableRateUsingIncrementalCalculation()
|
||||
{
|
||||
HitEventExtensions.UnstableRateCalculationResult? last = null;
|
||||
|
||||
for (int i = 0; i < 2048; i++)
|
||||
{
|
||||
var events = incrementalEventLists[i];
|
||||
last = events.CalculateUnstableRate(last);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
{
|
||||
base.InitialiseDefaults();
|
||||
|
||||
SetDefault(ManiaRulesetSetting.ScrollSpeed, 8, 1, 40);
|
||||
SetDefault(ManiaRulesetSetting.ScrollSpeed, 8.0, 1.0, 40.0, 0.1);
|
||||
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
|
||||
|
||||
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
|
||||
if (Get<double?>(ManiaRulesetSetting.ScrollTime) is double scrollTime)
|
||||
{
|
||||
SetValue(ManiaRulesetSetting.ScrollSpeed, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime));
|
||||
SetValue(ManiaRulesetSetting.ScrollSpeed, Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime));
|
||||
SetValue<double?>(ManiaRulesetSetting.ScrollTime, null);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
|
||||
{
|
||||
new TrackedSetting<int>(ManiaRulesetSetting.ScrollSpeed,
|
||||
new TrackedSetting<double>(ManiaRulesetSetting.ScrollSpeed,
|
||||
speed => new SettingDescription(
|
||||
rawValue: speed,
|
||||
name: RulesetSettingsStrings.ScrollSpeed,
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get<int>(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value;
|
||||
TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get<double>(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value;
|
||||
base.Update();
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
{
|
||||
Caption = "Use special (N+1) style",
|
||||
HintText = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||
Current = { Value = Beatmap.SpecialStyle }
|
||||
},
|
||||
healthDrainSlider = new FormSliderBar<float>
|
||||
{
|
||||
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||
|
@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsSlider<int, ManiaScrollSlider>
|
||||
new SettingsSlider<double, ManiaScrollSlider>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollSpeed,
|
||||
Current = config.GetBindable<int>(ManiaRulesetSetting.ScrollSpeed),
|
||||
KeyboardStep = 5
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollSpeed),
|
||||
KeyboardStep = 1
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
};
|
||||
}
|
||||
|
||||
private partial class ManiaScrollSlider : RoundedSliderBar<int>
|
||||
private partial class ManiaScrollSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 1: return colour_cyan;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 3:
|
||||
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 2: return colour_cyan;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 4:
|
||||
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 3: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 5:
|
||||
@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 4: return colour_cyan;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 6:
|
||||
@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 5: return colour_pink;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 7:
|
||||
@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 6: return colour_pink;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 8:
|
||||
@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 7: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 9:
|
||||
@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 8: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
case 10:
|
||||
@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 9: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case 5: return colour_green;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
|
||||
private Drawable getResult(HitResult result)
|
||||
{
|
||||
if (!hit_result_mapping.ContainsKey(result))
|
||||
if (!hit_result_mapping.TryGetValue(result, out var value))
|
||||
return null;
|
||||
|
||||
string filename = this.GetManiaSkinConfig<string>(hit_result_mapping[result])?.Value
|
||||
string filename = this.GetManiaSkinConfig<string>(value)?.Value
|
||||
?? default_hit_result_skin_filenames[result];
|
||||
|
||||
var animation = this.GetAnimation(filename, true, true, frameLength: 1000 / 20d);
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
||||
|
||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||
private readonly BindableInt configScrollSpeed = new BindableInt();
|
||||
private readonly BindableDouble configScrollSpeed = new BindableDouble();
|
||||
|
||||
private double currentTimeRange;
|
||||
protected double TargetTimeRange;
|
||||
@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// </summary>
|
||||
/// <param name="scrollSpeed">The scroll speed.</param>
|
||||
/// <returns>The scroll time.</returns>
|
||||
public static double ComputeScrollTime(int scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
|
||||
public static double ComputeScrollTime(double scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
|
||||
|
||||
|
@ -231,6 +231,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestControlClickDoesNotDiscardExistingSelectionEvenIfNothingHit()
|
||||
{
|
||||
var firstSlider = new Slider
|
||||
{
|
||||
StartTime = 0,
|
||||
Position = new Vector2(0, 0),
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(),
|
||||
new PathControlPoint(new Vector2(100))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep("add object", () => EditorBeatmap.AddRange([firstSlider]));
|
||||
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange([firstSlider]));
|
||||
|
||||
AddStep("move mouse to middle of playfield", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("control-click left mouse", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private ComposeBlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();
|
||||
|
||||
|
@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
private void gridSizeIs(int size)
|
||||
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Single().Spacing.Value == new Vector2(size)
|
||||
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
||||
&& EditorBeatmap.GridSize == size);
|
||||
|
||||
[Test]
|
||||
public void TestGridTypeToggling()
|
||||
|
@ -83,10 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
})
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
StackLeniency = 0,
|
||||
}
|
||||
StackLeniency = 0,
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
|
@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
StackLeniency = 0,
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
ApproachRate = 8.5f
|
||||
}
|
||||
},
|
||||
StackLeniency = 0,
|
||||
ControlPointInfo = controlPointInfo
|
||||
};
|
||||
|
||||
|
@ -465,7 +465,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private void performTest(List<ReplayFrame> frames, Beatmap<OsuHitObject> beatmap)
|
||||
{
|
||||
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
beatmap.BeatmapInfo.StackLeniency = 0;
|
||||
beatmap.StackLeniency = 0;
|
||||
beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderMultiplier = 4,
|
||||
|
@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
applyStacking(beatmap, hitObjects, 0, hitObjects.Count - 1);
|
||||
else
|
||||
applyStackingOld(beatmap.BeatmapInfo, hitObjects);
|
||||
applyStackingOld(beatmap, hitObjects);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||
private static void applyStacking(IBeatmap beatmap, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
continue;
|
||||
|
||||
double endTime = stackBaseObject.GetEndTime();
|
||||
double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency;
|
||||
double stackThreshold = objectN.TimePreempt * beatmap.StackLeniency;
|
||||
|
||||
if (objectN.StartTime - endTime > stackThreshold)
|
||||
// We are no longer within stacking range of the next object.
|
||||
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
OsuHitObject objectI = hitObjects[i];
|
||||
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
|
||||
|
||||
double stackThreshold = objectI.TimePreempt * beatmapInfo.StackLeniency;
|
||||
double stackThreshold = objectI.TimePreempt * beatmap.StackLeniency;
|
||||
|
||||
/* If this object is a hitcircle, then we enter this "special" case.
|
||||
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
|
||||
@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
||||
private static void applyStackingOld(IBeatmap beatmap, List<OsuHitObject> hitObjects)
|
||||
{
|
||||
for (int i = 0; i < hitObjects.Count; i++)
|
||||
{
|
||||
@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
|
||||
for (int j = i + 1; j < hitObjects.Count; j++)
|
||||
{
|
||||
double stackThreshold = hitObjects[i].TimePreempt * beatmapInfo.StackLeniency;
|
||||
double stackThreshold = hitObjects[i].TimePreempt * beatmap.StackLeniency;
|
||||
|
||||
if (hitObjects[j].StartTime - stackThreshold > startTime)
|
||||
break;
|
||||
|
@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
},
|
||||
};
|
||||
|
||||
Spacing.Value = editorBeatmap.BeatmapInfo.GridSize;
|
||||
Spacing.Value = editorBeatmap.GridSize;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
|
||||
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
|
||||
SpacingVector.Value = new Vector2(spacing.NewValue);
|
||||
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue;
|
||||
editorBeatmap.GridSize = (int)spacing.NewValue;
|
||||
}, true);
|
||||
|
||||
GridLinesRotation.BindValueChanged(rotation =>
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
{
|
||||
Caption = "Stack Leniency",
|
||||
HintText = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency)
|
||||
Current = new BindableFloat(Beatmap.StackLeniency)
|
||||
{
|
||||
Default = 0.7f,
|
||||
MinValue = 0,
|
||||
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||
Beatmap.StackLeniency = stackLeniency.Current.Value;
|
||||
|
||||
Beatmap.UpdateAllHitObjects();
|
||||
Beatmap.SaveState();
|
||||
|
@ -57,11 +57,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const string base_lookup = @"hitcircle";
|
||||
|
||||
var drawableOsuObject = (DrawableOsuHitObject?)drawableObject;
|
||||
|
||||
// As a precondition, ensure that any prefix lookups are run against the skin which is providing "hitcircle".
|
||||
// This is to correctly handle a case such as:
|
||||
//
|
||||
// - Beatmap provides `hitcircle`
|
||||
// - User skin provides `sliderstartcircle`
|
||||
//
|
||||
// In such a case, the `hitcircle` should be used for slider start circles rather than the user's skin override.
|
||||
var provider = skin.FindProvider(s => s.GetTexture(base_lookup) != null) ?? skin;
|
||||
|
||||
// if a base texture for the specified prefix exists, continue using it for subsequent lookups.
|
||||
// otherwise fall back to the default prefix "hitcircle".
|
||||
string circleName = (priorityLookupPrefix != null && skin.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : @"hitcircle";
|
||||
string circleName = (priorityLookupPrefix != null && provider.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : base_lookup;
|
||||
|
||||
Vector2 maxSize = OsuHitObject.OBJECT_DIMENSIONS * 2;
|
||||
|
||||
@ -70,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png.
|
||||
InternalChildren = new[]
|
||||
{
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(maxSize) })
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(circleName)?.WithMaximumSize(maxSize) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -79,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) })
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -63,18 +63,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-top"),
|
||||
},
|
||||
fixedMiddle = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-middle"),
|
||||
},
|
||||
spinningMiddle = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-middle2"),
|
||||
},
|
||||
fixedMiddle = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-middle"),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -144,6 +144,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
simulateHit(nested, ref attributes);
|
||||
return;
|
||||
|
||||
case StrongNestedHitObject:
|
||||
// we never need to deal with these directly.
|
||||
// the only thing strong hits do in terms of scoring is double their object's score increase,
|
||||
// which is already handled at the parent object level via the `strongable.IsStrong` check lower down in this method.
|
||||
// not handling these here can lead to them falsely being counted as combo-increasing when handling strong drum rolls!
|
||||
return;
|
||||
}
|
||||
|
||||
if (hitObject is DrumRollTick tick)
|
||||
|
@ -80,16 +80,16 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
|
||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||
Assert.AreEqual(0, beatmap.AudioLeadIn);
|
||||
Assert.AreEqual(164471, metadata.PreviewTime);
|
||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||
Assert.AreEqual(0.7f, beatmap.StackLeniency);
|
||||
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
|
||||
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
|
||||
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||
Assert.IsFalse(beatmap.LetterboxInBreaks);
|
||||
Assert.IsFalse(beatmap.SpecialStyle);
|
||||
Assert.IsFalse(beatmap.WidescreenStoryboard);
|
||||
Assert.IsFalse(beatmap.SamplesMatchPlaybackRate);
|
||||
Assert.AreEqual(CountdownType.None, beatmap.Countdown);
|
||||
Assert.AreEqual(0, beatmap.CountdownOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var beatmapInfo = decoder.Decode(stream).BeatmapInfo;
|
||||
var beatmap = decoder.Decode(stream);
|
||||
|
||||
int[] expectedBookmarks =
|
||||
{
|
||||
@ -109,13 +109,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
95901, 106450, 116999, 119637, 130186, 140735, 151285,
|
||||
161834, 164471, 175020, 185570, 196119, 206669, 209306
|
||||
};
|
||||
Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length);
|
||||
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
||||
for (int i = 0; i < expectedBookmarks.Length; i++)
|
||||
Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]);
|
||||
Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing);
|
||||
Assert.AreEqual(4, beatmapInfo.BeatDivisor);
|
||||
Assert.AreEqual(4, beatmapInfo.GridSize);
|
||||
Assert.AreEqual(2, beatmapInfo.TimelineZoom);
|
||||
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
||||
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
|
||||
Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor);
|
||||
Assert.AreEqual(4, beatmap.GridSize);
|
||||
Assert.AreEqual(2, beatmap.TimelineZoom);
|
||||
}
|
||||
}
|
||||
|
||||
@ -993,15 +993,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0));
|
||||
Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f));
|
||||
Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False);
|
||||
Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False);
|
||||
Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False);
|
||||
Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False);
|
||||
Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False);
|
||||
Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.None));
|
||||
Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0));
|
||||
Assert.That(decoded.AudioLeadIn, Is.EqualTo(0));
|
||||
Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f));
|
||||
Assert.That(decoded.SpecialStyle, Is.False);
|
||||
Assert.That(decoded.LetterboxInBreaks, Is.False);
|
||||
Assert.That(decoded.WidescreenStoryboard, Is.False);
|
||||
Assert.That(decoded.EpilepsyWarning, Is.False);
|
||||
Assert.That(decoded.SamplesMatchPlaybackRate, Is.False);
|
||||
Assert.That(decoded.Countdown, Is.EqualTo(CountdownType.None));
|
||||
Assert.That(decoded.CountdownOffset, Is.EqualTo(0));
|
||||
Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
|
||||
Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
|
||||
});
|
||||
|
@ -51,14 +51,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var beatmap = decodeAsJson(normal);
|
||||
var beatmapInfo = beatmap.BeatmapInfo;
|
||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
|
||||
Assert.AreEqual(0, beatmap.AudioLeadIn);
|
||||
Assert.AreEqual(0.7f, beatmap.StackLeniency);
|
||||
Assert.AreEqual(false, beatmap.SpecialStyle);
|
||||
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
|
||||
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
|
||||
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
|
||||
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||
Assert.AreEqual(false, beatmap.LetterboxInBreaks);
|
||||
Assert.AreEqual(false, beatmap.WidescreenStoryboard);
|
||||
Assert.AreEqual(CountdownType.None, beatmap.Countdown);
|
||||
Assert.AreEqual(0, beatmap.CountdownOffset);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -73,13 +73,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
95901, 106450, 116999, 119637, 130186, 140735, 151285,
|
||||
161834, 164471, 175020, 185570, 196119, 206669, 209306
|
||||
};
|
||||
Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length);
|
||||
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
||||
for (int i = 0; i < expectedBookmarks.Length; i++)
|
||||
Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]);
|
||||
Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing);
|
||||
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
||||
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
|
||||
Assert.AreEqual(4, beatmapInfo.BeatDivisor);
|
||||
Assert.AreEqual(4, beatmapInfo.GridSize);
|
||||
Assert.AreEqual(2, beatmapInfo.TimelineZoom);
|
||||
Assert.AreEqual(4, beatmap.GridSize);
|
||||
Assert.AreEqual(2, beatmap.TimelineZoom);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
7
osu.Game.Tests/CodeAnalysis.tests.globalconfig
Normal file
7
osu.Game.Tests/CodeAnalysis.tests.globalconfig
Normal file
@ -0,0 +1,7 @@
|
||||
# Higher global_level has higher priority, the default global_level
|
||||
# is 100 for root .globalconfig and 0 for others
|
||||
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/configuration-files#precedence
|
||||
is_global = true
|
||||
global_level = 101
|
||||
|
||||
dotnet_diagnostic.CA2007.severity = none
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database
|
||||
Assert.That(lastChanges?.ModifiedIndices, Is.Empty);
|
||||
Assert.That(lastChanges?.NewModifiedIndices, Is.Empty);
|
||||
|
||||
realm.Write(r => r.All<BeatmapSetInfo>().First().Beatmaps.First().CountdownOffset = 5);
|
||||
realm.Write(r => r.All<BeatmapSetInfo>().First().Beatmaps.First().EditorTimestamp = 5);
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
Assert.That(collectionChanges, Is.EqualTo(1));
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
@ -64,6 +65,10 @@ namespace osu.Game.Tests
|
||||
// Beatmap must be imported before the collection manager is loaded.
|
||||
if (withBeatmap)
|
||||
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
|
||||
|
||||
// the logic for setting the initial ruleset exists in OsuGame rather than OsuGameBase.
|
||||
// the ruleset bindable is not meant to be nullable, so assign any ruleset in here.
|
||||
Ruleset.Value = RulesetStore.AvailableRulesets.First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,53 @@ namespace osu.Game.Tests.NonVisual.Ranking
|
||||
public void TestDistributedHits()
|
||||
{
|
||||
var events = Enumerable.Range(-5, 11)
|
||||
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null));
|
||||
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
||||
.ToList();
|
||||
|
||||
var unstableRate = new UnstableRate(events);
|
||||
|
||||
Assert.IsNotNull(unstableRate.Value);
|
||||
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10)));
|
||||
Assert.AreEqual(unstableRate.Value.Value, 10 * Math.Sqrt(10), Precision.DOUBLE_EPSILON);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDistributedHitsIncrementalRewind()
|
||||
{
|
||||
var events = Enumerable.Range(-5, 11)
|
||||
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
||||
.ToList();
|
||||
|
||||
HitEventExtensions.UnstableRateCalculationResult result = null;
|
||||
|
||||
for (int i = 0; i < events.Count; i++)
|
||||
{
|
||||
result = events.GetRange(0, i + 1)
|
||||
.CalculateUnstableRate(result);
|
||||
}
|
||||
|
||||
result = events.GetRange(0, 2).CalculateUnstableRate(result);
|
||||
|
||||
Assert.IsNotNull(result!.Result);
|
||||
Assert.AreEqual(5, result.Result, Precision.DOUBLE_EPSILON);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDistributedHitsIncremental()
|
||||
{
|
||||
var events = Enumerable.Range(-5, 11)
|
||||
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
||||
.ToList();
|
||||
|
||||
HitEventExtensions.UnstableRateCalculationResult result = null;
|
||||
|
||||
for (int i = 0; i < events.Count; i++)
|
||||
{
|
||||
result = events.GetRange(0, i + 1)
|
||||
.CalculateUnstableRate(result);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(result!.Result);
|
||||
Assert.AreEqual(10 * Math.Sqrt(10), result.Result, Precision.DOUBLE_EPSILON);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false);
|
||||
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.None);
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.None);
|
||||
AddUntilStep("other controls hidden", () => !designSection.CountdownSettings.IsPresent);
|
||||
}
|
||||
|
||||
@ -65,12 +65,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true);
|
||||
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal);
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal);
|
||||
AddUntilStep("other controls shown", () => designSection.CountdownSettings.IsPresent);
|
||||
|
||||
AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed);
|
||||
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.DoubleSpeed);
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.DoubleSpeed);
|
||||
AddUntilStep("other controls still shown", () => designSection.CountdownSettings.IsPresent);
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true);
|
||||
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal);
|
||||
AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal);
|
||||
|
||||
checkOffsetAfter("1", 1);
|
||||
checkOffsetAfter(string.Empty, 0);
|
||||
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddAssert($"displayed value is {expectedFinalValue}", () => designSection.CountdownOffset.Current.Value == expectedFinalValue.ToString(CultureInfo.InvariantCulture));
|
||||
AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.BeatmapInfo.CountdownOffset == expectedFinalValue);
|
||||
AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.CountdownOffset == expectedFinalValue);
|
||||
}
|
||||
|
||||
private partial class TestDesignSection : DesignSection
|
||||
|
@ -4,11 +4,13 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -19,6 +21,7 @@ using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
@ -27,6 +30,7 @@ using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
@ -98,44 +102,15 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType<Timeline>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
AddStep("enter setup mode", () => InputManager.Key(Key.F4));
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||
AddAssert("switch track to real track", () =>
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
|
||||
string temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
string extractedFolder = $"{temp}_extracted";
|
||||
Directory.CreateDirectory(extractedFolder);
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")));
|
||||
|
||||
// ensure audio file is copied to beatmap as "audio.mp3" rather than original filename.
|
||||
Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3");
|
||||
|
||||
return success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(temp);
|
||||
Directory.Delete(extractedFolder, true);
|
||||
}
|
||||
});
|
||||
AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3"));
|
||||
|
||||
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
||||
AddUntilStep("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||
|
||||
AddStep("test play", () => Editor.TestGameplay());
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
|
||||
AddStep("confirm save", () => InputManager.Key(Key.Number1));
|
||||
|
||||
AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen());
|
||||
|
||||
AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
||||
@ -154,6 +129,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
|
||||
AddStep("add effect point", () => EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true }));
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
|
||||
{
|
||||
new HitCircle
|
||||
@ -200,6 +176,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
|
||||
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
|
||||
});
|
||||
AddAssert("created difficulty has effect points", () =>
|
||||
{
|
||||
var effectPoint = EditorBeatmap.ControlPointInfo.EffectPoints.Single();
|
||||
return effectPoint.Time == 500 && effectPoint.KiaiMode && effectPoint.ScrollSpeedBindable.IsDefault;
|
||||
});
|
||||
AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
|
||||
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
|
||||
@ -219,6 +200,104 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateNewDifficultyWithScrollSpeed_SameRuleset()
|
||||
{
|
||||
string firstDifficultyName = Guid.NewGuid().ToString();
|
||||
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo));
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
|
||||
AddStep("add effect points", () =>
|
||||
{
|
||||
EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 });
|
||||
EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.1 });
|
||||
EditorBeatmap.ControlPointInfo.Add(750, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.15 });
|
||||
EditorBeatmap.ControlPointInfo.Add(1000, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.2 });
|
||||
EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 });
|
||||
});
|
||||
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != firstDifficultyName;
|
||||
});
|
||||
|
||||
AddAssert("created difficulty has timing point", () =>
|
||||
{
|
||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
|
||||
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
|
||||
});
|
||||
|
||||
AddAssert("created difficulty has effect points", () =>
|
||||
{
|
||||
return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[]
|
||||
{
|
||||
new EffectControlPoint { Time = 250, KiaiMode = false, ScrollSpeed = 0.05 },
|
||||
new EffectControlPoint { Time = 500, KiaiMode = true, ScrollSpeed = 0.1 },
|
||||
new EffectControlPoint { Time = 750, KiaiMode = true, ScrollSpeed = 0.15 },
|
||||
new EffectControlPoint { Time = 1000, KiaiMode = false, ScrollSpeed = 0.2 },
|
||||
new EffectControlPoint { Time = 1500, KiaiMode = false, ScrollSpeed = 0.3 },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateNewDifficultyWithScrollSpeed_DifferentRuleset()
|
||||
{
|
||||
string firstDifficultyName = Guid.NewGuid().ToString();
|
||||
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo));
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
|
||||
AddStep("add effect points", () =>
|
||||
{
|
||||
EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 });
|
||||
EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.1 });
|
||||
EditorBeatmap.ControlPointInfo.Add(750, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.15 });
|
||||
EditorBeatmap.ControlPointInfo.Add(1000, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.2 });
|
||||
EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 });
|
||||
});
|
||||
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != firstDifficultyName;
|
||||
});
|
||||
|
||||
AddAssert("created difficulty has timing point", () =>
|
||||
{
|
||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
|
||||
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
|
||||
});
|
||||
|
||||
AddAssert("created difficulty has effect points", () =>
|
||||
{
|
||||
// since this difficulty is on another ruleset, scroll speed specifications are completely reset,
|
||||
// therefore discarding some effect points in the process due to being redundant.
|
||||
return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[]
|
||||
{
|
||||
new EffectControlPoint { Time = 500, KiaiMode = true },
|
||||
new EffectControlPoint { Time = 1000, KiaiMode = false },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCopyDifficulty()
|
||||
{
|
||||
@ -530,5 +609,228 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleBackgroundFile()
|
||||
{
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg"));
|
||||
|
||||
createNewDifficulty();
|
||||
createNewDifficulty();
|
||||
|
||||
switchToDifficulty(1);
|
||||
|
||||
AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg"));
|
||||
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg"));
|
||||
|
||||
switchToDifficulty(0);
|
||||
|
||||
AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg"));
|
||||
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg"));
|
||||
|
||||
AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg"));
|
||||
AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg"));
|
||||
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg"));
|
||||
AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundFileChangesPreserveOnEncode()
|
||||
{
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg"));
|
||||
|
||||
createNewDifficulty();
|
||||
createNewDifficulty();
|
||||
|
||||
switchToDifficulty(0);
|
||||
|
||||
AddAssert("set different background on all diff", () => setBackgroundDifferentExtension(applyToAllDifficulties: true, expected: "bg.jpeg"));
|
||||
AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpeg"));
|
||||
AddAssert("all diff encode same background", () =>
|
||||
{
|
||||
return Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b =>
|
||||
{
|
||||
var files = new RealmFileStore(Realm, Dependencies.Get<GameHost>().Storage);
|
||||
using var store = new RealmBackedResourceStore<BeatmapSetInfo>(b.BeatmapSet!.ToLive(Realm), files.Store, Realm);
|
||||
string[] osu = Encoding.UTF8.GetString(store.Get(b.File!.Filename)).Split(Environment.NewLine);
|
||||
Assert.That(osu, Does.Contain("0,0,\"bg.jpeg\",0,0"));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleAudioFile()
|
||||
{
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3"));
|
||||
|
||||
createNewDifficulty();
|
||||
createNewDifficulty();
|
||||
|
||||
switchToDifficulty(1);
|
||||
|
||||
AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3"));
|
||||
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3"));
|
||||
|
||||
switchToDifficulty(0);
|
||||
|
||||
AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3"));
|
||||
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3"));
|
||||
|
||||
AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3"));
|
||||
AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3"));
|
||||
AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3"));
|
||||
AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3" || f.Filename == "audio (2).mp3"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleBackgroundFiles()
|
||||
{
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg"));
|
||||
|
||||
createNewDifficulty();
|
||||
|
||||
AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg");
|
||||
AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg"));
|
||||
AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg");
|
||||
|
||||
switchToDifficulty(0);
|
||||
|
||||
AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg");
|
||||
AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg"));
|
||||
AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg"));
|
||||
AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleAudioFiles()
|
||||
{
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3"));
|
||||
|
||||
createNewDifficulty();
|
||||
|
||||
AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3");
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
|
||||
AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3"));
|
||||
AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3");
|
||||
|
||||
switchToDifficulty(0);
|
||||
|
||||
AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3");
|
||||
AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3"));
|
||||
AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3"));
|
||||
AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3"));
|
||||
}
|
||||
|
||||
private void createNewDifficulty()
|
||||
{
|
||||
string? currentDifficulty = null;
|
||||
|
||||
AddStep("save", () => Editor.Save());
|
||||
AddStep("create new difficulty", () =>
|
||||
{
|
||||
currentDifficulty = EditorBeatmap.BeatmapInfo.DifficultyName;
|
||||
Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != currentDifficulty;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for editor load", () => Editor.IsLoaded);
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
|
||||
}
|
||||
|
||||
private void switchToDifficulty(int index)
|
||||
{
|
||||
AddStep("save", () => Editor.Save());
|
||||
AddStep($"switch to difficulty #{index + 1}", () =>
|
||||
Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(index)));
|
||||
|
||||
AddUntilStep("wait for editor load", () => Editor.IsLoaded);
|
||||
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
|
||||
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
|
||||
}
|
||||
|
||||
private bool setBackground(bool applyToAllDifficulties, string expected)
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
|
||||
return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder =>
|
||||
{
|
||||
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeBackgroundImage(
|
||||
new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpg")),
|
||||
applyToAllDifficulties);
|
||||
|
||||
Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected));
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
private bool setBackgroundDifferentExtension(bool applyToAllDifficulties, string expected)
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
|
||||
return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder =>
|
||||
{
|
||||
File.Move(
|
||||
Path.Combine(extractedFolder, @"machinetop_background.jpg"),
|
||||
Path.Combine(extractedFolder, @"machinetop_background.jpeg"));
|
||||
|
||||
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeBackgroundImage(
|
||||
new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpeg")),
|
||||
applyToAllDifficulties);
|
||||
|
||||
Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected));
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
private bool setAudio(bool applyToAllDifficulties, string expected)
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
|
||||
return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder =>
|
||||
{
|
||||
bool success = setup.ChildrenOfType<ResourcesSection>().First().ChangeAudioTrack(
|
||||
new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")),
|
||||
applyToAllDifficulties);
|
||||
|
||||
Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected));
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
private bool setFile(string archivePath, Func<string, bool> func)
|
||||
{
|
||||
string temp = archivePath;
|
||||
|
||||
string extractedFolder = $"{temp}_extracted";
|
||||
Directory.CreateDirectory(extractedFolder);
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
return func(extractedFolder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(temp);
|
||||
Directory.Delete(extractedFolder, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("Set beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().Value = 16);
|
||||
AddStep("Set timeline zoom", () =>
|
||||
{
|
||||
originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
|
||||
originalTimelineZoom = EditorBeatmap.TimelineZoom;
|
||||
|
||||
var timeline = Editor.ChildrenOfType<Timeline>().Single();
|
||||
InputManager.MoveMouseTo(timeline);
|
||||
@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddAssert("Ensure timeline zoom changed", () =>
|
||||
{
|
||||
changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom;
|
||||
changedTimelineZoom = EditorBeatmap.TimelineZoom;
|
||||
return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom);
|
||||
});
|
||||
|
||||
SaveEditor();
|
||||
|
||||
AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||
AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
|
||||
AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom);
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16);
|
||||
AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom);
|
||||
AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 });
|
||||
beatmap.ControlPointInfo.Add(80000, new EffectControlPoint { KiaiMode = true });
|
||||
beatmap.ControlPointInfo.Add(110000, new EffectControlPoint { KiaiMode = false });
|
||||
beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 };
|
||||
beatmap.Bookmarks = new[] { 75000, 125000 };
|
||||
beatmap.Breaks.Add(new ManualBreakPeriod(90000, 120000));
|
||||
|
||||
editorBeatmap = new EditorBeatmap(beatmap);
|
||||
|
@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
double originalSpacing = 0;
|
||||
|
||||
AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.BeatmapInfo.DistanceSpacing);
|
||||
AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.DistanceSpacing);
|
||||
|
||||
AddStep("hold ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("hold alt", () => InputManager.PressKey(Key.LAlt));
|
||||
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt));
|
||||
AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
|
||||
AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5);
|
||||
AddAssert("distance spacing increased by 0.5", () => editorBeatmap.DistanceSpacing == originalSpacing + 0.5);
|
||||
}
|
||||
|
||||
public partial class EditorBeatmapContainer : PopoverContainer
|
||||
|
@ -527,8 +527,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
|
||||
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
|
||||
|
||||
void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
|
||||
void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
|
||||
void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}",
|
||||
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
|
||||
|
||||
void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}",
|
||||
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -781,15 +784,39 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT);
|
||||
dismissPopover();
|
||||
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
assertNoChanges();
|
||||
|
||||
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]));
|
||||
AddStep("select first object", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Clear();
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
|
||||
});
|
||||
assertNoChanges();
|
||||
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
AddStep("select second object", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Clear();
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]);
|
||||
});
|
||||
assertNoChanges();
|
||||
|
||||
AddStep("select first object", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Clear();
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
|
||||
});
|
||||
assertNoChanges();
|
||||
|
||||
void assertNoChanges()
|
||||
{
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
|
||||
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL);
|
||||
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
|
||||
}
|
||||
}
|
||||
|
||||
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
|
||||
@ -883,11 +910,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
return h.Samples.All(o => o.Volume == volume);
|
||||
});
|
||||
|
||||
private void hitObjectNodeHasSampleVolume(int objectIndex, int nodeIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has volume {volume}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].All(o => o.Volume == volume);
|
||||
});
|
||||
private void hitObjectNodeHasSampleVolume(int objectIndex, int nodeIndex, int volume) => AddAssert(
|
||||
$"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has volume {volume}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].All(o => o.Volume == volume);
|
||||
});
|
||||
|
||||
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
|
||||
{
|
||||
@ -944,29 +972,33 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||
});
|
||||
|
||||
private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].Select(s => s.Name).SequenceEqual(samples);
|
||||
});
|
||||
private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert(
|
||||
$"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].Select(s => s.Name).SequenceEqual(samples);
|
||||
});
|
||||
|
||||
private void hitObjectNodeHasSampleBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has bank {bank}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].All(o => o.Bank == bank);
|
||||
});
|
||||
private void hitObjectNodeHasSampleBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has bank {bank}",
|
||||
() =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].All(o => o.Bank == bank);
|
||||
});
|
||||
|
||||
private void hitObjectNodeHasSampleNormalBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has normal bank {bank}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||
});
|
||||
private void hitObjectNodeHasSampleNormalBank(int objectIndex, int nodeIndex, string bank) => AddAssert(
|
||||
$"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has normal bank {bank}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||
});
|
||||
|
||||
private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has addition bank {bank}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||
});
|
||||
private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, string bank) => AddAssert(
|
||||
$"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has addition bank {bank}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||
});
|
||||
|
||||
private void editorTimeIs(double time) => AddAssert($"editor time is {time}", () => Precision.AlmostEquals(EditorClock.CurrentTimeAccurate, time, 1));
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo = { AudioLeadIn = leadIn }
|
||||
AudioLeadIn = leadIn
|
||||
});
|
||||
|
||||
checkFirstFrameTime(expectedStartTime);
|
||||
@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} "
|
||||
+ $"FirstHitObjectTime: {FirstHitObjectTime} "
|
||||
+ $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} "
|
||||
+ $"LeadInTime: {Beatmap.Value.Beatmap.AudioLeadIn} "
|
||||
+ $"FirstFrameClockTime: {FirstFrameClockTime}"
|
||||
});
|
||||
}
|
||||
|
@ -136,10 +136,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
// Add intro time to test quick retry skipping (TestQuickRetry).
|
||||
workingBeatmap.BeatmapInfo.AudioLeadIn = 60000;
|
||||
workingBeatmap.Beatmap.AudioLeadIn = 60000;
|
||||
|
||||
// Set up data for testing disclaimer display.
|
||||
workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false;
|
||||
workingBeatmap.Beatmap.EpilepsyWarning = epilepsyWarning ?? false;
|
||||
workingBeatmap.BeatmapInfo.Status = onlineStatus ?? BeatmapOnlineStatus.Ranked;
|
||||
|
||||
Beatmap.Value = workingBeatmap;
|
||||
|
@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
string? filePath = null;
|
||||
|
||||
// Files starting with _ are temporary, created by CreateFileSafely call.
|
||||
AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null);
|
||||
AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith('_')), () => Is.Not.Null);
|
||||
AddUntilStep("filesize is non-zero", () =>
|
||||
{
|
||||
try
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo = { AudioLeadIn = 60000 }
|
||||
AudioLeadIn = 60000
|
||||
});
|
||||
|
||||
AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType<SkipOverlay>().First().IsButtonVisible);
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("load storyboard with only video", () =>
|
||||
{
|
||||
// LegacyStoryboardDecoder doesn't parse WidescreenStoryboard, so it is set manually
|
||||
loadStoryboard("storyboard_only_video.osu", s => s.BeatmapInfo.WidescreenStoryboard = false);
|
||||
loadStoryboard("storyboard_only_video.osu", s => s.Beatmap.WidescreenStoryboard = false);
|
||||
});
|
||||
|
||||
AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f));
|
||||
|
@ -10,11 +10,13 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Login;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Tests.Visual.Online;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK.Input;
|
||||
@ -31,6 +33,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[Cached(typeof(LocalUserStatisticsProvider))]
|
||||
private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -170,6 +175,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("enter code", () => loginOverlay.ChildrenOfType<OsuTextBox>().First().Text = "88800088");
|
||||
assertAPIState(APIState.Online);
|
||||
|
||||
AddStep("feed statistics", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||
AddStep("click on flag", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<UpdateableFlag>().First());
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
@ -73,5 +75,57 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
((StarFountain)Children[1]).Shoot(-1);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayStarFountainsSetting()
|
||||
{
|
||||
Bindable<bool> starFountainsEnabled = null!;
|
||||
|
||||
AddStep("load configuration", () =>
|
||||
{
|
||||
var config = new OsuConfigManager(LocalStorage);
|
||||
starFountainsEnabled = config.GetBindable<bool>(OsuSetting.StarFountains);
|
||||
});
|
||||
|
||||
AddStep("make fountains", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new KiaiGameplayFountains.GameplayStarFountain
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
X = 75,
|
||||
},
|
||||
new KiaiGameplayFountains.GameplayStarFountain
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -75,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("enable KiaiStarEffects", () => starFountainsEnabled.Value = true);
|
||||
AddRepeatStep("activate fountains (enabled)", () =>
|
||||
{
|
||||
((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1);
|
||||
((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1);
|
||||
}, 100);
|
||||
|
||||
AddStep("disable KiaiStarEffects", () => starFountainsEnabled.Value = false);
|
||||
AddRepeatStep("attempt to activate fountains (disabled)", () =>
|
||||
{
|
||||
((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1);
|
||||
((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1);
|
||||
}, 100);
|
||||
|
||||
AddStep("re-enable KiaiStarEffects", () => starFountainsEnabled.Value = true);
|
||||
AddRepeatStep("activate fountains (re-enabled)", () =>
|
||||
{
|
||||
((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1);
|
||||
((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Gain", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Loss", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Tiny increase in PP", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("No change 1", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Was null", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("Became null", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Mods
|
||||
protected override TestPlayer CreateModPlayer(Ruleset ruleset)
|
||||
{
|
||||
var player = base.CreateModPlayer(ruleset);
|
||||
player.RestartRequested = _ => restartRequested = true;
|
||||
player.PrepareLoaderForRestart = _ => restartRequested = true;
|
||||
return player;
|
||||
}
|
||||
|
||||
|
@ -406,13 +406,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests spectating with a beatmap that has a high <see cref="BeatmapInfo.AudioLeadIn"/> value.
|
||||
/// Tests spectating with a beatmap that has a high <see cref="IBeatmap.AudioLeadIn"/> value.
|
||||
///
|
||||
/// This test is not intended not to check the correct initial time value, but only to guard against
|
||||
/// gameplay potentially getting stuck in a stopped state due to lead in time being present.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000);
|
||||
public void TestAudioLeadIn() => testLeadIn(b => b.Beatmap.AudioLeadIn = 2000);
|
||||
|
||||
/// <summary>
|
||||
/// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element).
|
||||
|
@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
|
||||
|
||||
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0);
|
||||
AddUntilStep("kick buttons not visible", () => !this.ChildrenOfType<ParticipantPanel.KickButton>().Any(d => d.IsPresent));
|
||||
|
||||
AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id));
|
||||
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
});
|
||||
AddStep("create IPC sender channels", () =>
|
||||
{
|
||||
ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPort = OsuGame.IPC_PORT });
|
||||
ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPipeName = OsuGame.IPC_PIPE_NAME });
|
||||
osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
|
||||
archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost);
|
||||
});
|
||||
|
@ -354,6 +354,23 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("retry count is 1", () => player.RestartCount == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastScoreNullAfterExitingPlayer()
|
||||
{
|
||||
AddUntilStep("wait for last play null", getLastPlay, () => Is.Null);
|
||||
|
||||
var getOriginalPlayer = playToCompletion();
|
||||
|
||||
AddStep("attempt to retry", () => getOriginalPlayer().ChildrenOfType<HotkeyRetryOverlay>().First().Action());
|
||||
AddUntilStep("wait for last play matches player", getLastPlay, () => Is.EqualTo(getOriginalPlayer().Score.ScoreInfo));
|
||||
|
||||
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player);
|
||||
AddStep("exit player", () => (Game.ScreenStack.CurrentScreen as Player)?.Exit());
|
||||
AddUntilStep("wait for last play null", getLastPlay, () => Is.Null);
|
||||
|
||||
ScoreInfo getLastPlay() => Game.Dependencies.Get<SessionStatics>().Get<ScoreInfo>(Static.LastLocalUserScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetryImmediatelyAfterCompletion()
|
||||
{
|
||||
|
@ -457,6 +457,61 @@ namespace osu.Game.Tests.Visual.Online
|
||||
waitForChannel1Visible();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPublicChannelsSortedByName()
|
||||
{
|
||||
// Intentionally join back to front.
|
||||
AddStep("Show overlay with channel 2", () =>
|
||||
{
|
||||
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel2);
|
||||
chatOverlay.Show();
|
||||
});
|
||||
AddUntilStep("second channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel2);
|
||||
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||
AddUntilStep("first channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1);
|
||||
|
||||
AddStep("message in channel 2", () =>
|
||||
{
|
||||
testChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } });
|
||||
});
|
||||
AddUntilStep("first channel still at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1);
|
||||
|
||||
ChannelListItem getFirstVisiblePublicChannel() =>
|
||||
chatOverlay.ChildrenOfType<ChannelList>().Single().PublicChannelGroup.ItemFlow.FlowingChildren.OfType<ChannelListItem>().First(item => item.Channel.Type == ChannelType.Public);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPrivateChannelsSortedByRecent()
|
||||
{
|
||||
Channel pmChannel1 = createPrivateChannel();
|
||||
Channel pmChannel2 = createPrivateChannel();
|
||||
|
||||
joinChannel(pmChannel1);
|
||||
joinChannel(pmChannel2);
|
||||
|
||||
AddStep("Show overlay", () => chatOverlay.Show());
|
||||
|
||||
AddUntilStep("first channel is at top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1);
|
||||
|
||||
AddStep("message in channel 2", () =>
|
||||
{
|
||||
pmChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } });
|
||||
});
|
||||
|
||||
AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel2);
|
||||
|
||||
AddStep("message in channel 1", () =>
|
||||
{
|
||||
pmChannel1.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } });
|
||||
});
|
||||
|
||||
AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1);
|
||||
|
||||
ChannelListItem getFirstVisiblePMChannel() =>
|
||||
chatOverlay.ChildrenOfType<ChannelList>().Single().PrivateChannelGroup.ItemFlow.FlowingChildren.OfType<ChannelListItem>().First(item => item.Channel.Type == ChannelType.PM);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardNewChannel()
|
||||
{
|
||||
|
@ -0,0 +1,179 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneLocalUserStatisticsProvider : OsuTestScene
|
||||
{
|
||||
private LocalUserStatisticsProvider statisticsProvider = null!;
|
||||
|
||||
private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("clear statistics", () => serverSideStatistics.Clear());
|
||||
|
||||
setUser(1000);
|
||||
|
||||
AddStep("setup provider", () =>
|
||||
{
|
||||
OsuTextFlowContainer text;
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = r =>
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case GetUserRequest userRequest:
|
||||
int userId = int.Parse(userRequest.Lookup);
|
||||
string rulesetName = userRequest.Ruleset!.ShortName;
|
||||
var response = new APIUser
|
||||
{
|
||||
Id = userId,
|
||||
Statistics = tryGetStatistics(userId, rulesetName)
|
||||
};
|
||||
|
||||
userRequest.TriggerSuccess(response);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Clear();
|
||||
Add(statisticsProvider = new LocalUserStatisticsProvider());
|
||||
Add(text = new OsuTextFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
statisticsProvider.StatisticsUpdated += update =>
|
||||
{
|
||||
text.Clear();
|
||||
|
||||
foreach (var ruleset in Dependencies.Get<RulesetStore>().AvailableRulesets)
|
||||
{
|
||||
text.AddText(statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics
|
||||
? $"{ruleset.Name} statistics: (total score: {statistics.TotalScore})"
|
||||
: $"{ruleset.Name} statistics: (null)");
|
||||
text.NewLine();
|
||||
}
|
||||
|
||||
text.AddText($"latest update: {update.Ruleset}"
|
||||
+ $" ({(update.OldStatistics?.TotalScore.ToString() ?? "null")} -> {update.NewStatistics.TotalScore})");
|
||||
};
|
||||
|
||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialStatistics()
|
||||
{
|
||||
AddAssert("osu statistics populated", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000));
|
||||
AddAssert("taiko statistics populated", () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(3_000_000));
|
||||
AddAssert("catch statistics populated", () => statisticsProvider.GetStatisticsFor(new CatchRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(2_000_000));
|
||||
AddAssert("mania statistics populated", () => statisticsProvider.GetStatisticsFor(new ManiaRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserChanges()
|
||||
{
|
||||
setUser(1001);
|
||||
|
||||
AddStep("update statistics for user 1000", () =>
|
||||
{
|
||||
serverSideStatistics[(1000, "osu")] = new UserStatistics { TotalScore = 5_000_000 };
|
||||
serverSideStatistics[(1000, "taiko")] = new UserStatistics { TotalScore = 6_000_000 };
|
||||
});
|
||||
|
||||
AddAssert("statistics matches user 1001 in osu",
|
||||
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||
() => Is.EqualTo(4_000_000));
|
||||
|
||||
AddAssert("statistics matches user 1001 in taiko",
|
||||
() => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
|
||||
() => Is.EqualTo(3_000_000));
|
||||
|
||||
setUser(1000, false);
|
||||
|
||||
AddAssert("statistics matches user 1000 in osu",
|
||||
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||
() => Is.EqualTo(5_000_000));
|
||||
|
||||
AddAssert("statistics matches user 1000 in taiko",
|
||||
() => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
|
||||
() => Is.EqualTo(6_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRefetchStatistics()
|
||||
{
|
||||
UserStatisticsUpdate? update = null;
|
||||
|
||||
setUser(1001);
|
||||
|
||||
AddStep("update statistics server side",
|
||||
() => serverSideStatistics[(1001, "osu")] = new UserStatistics { TotalScore = 9_000_000 });
|
||||
|
||||
AddAssert("statistics match old score",
|
||||
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||
() => Is.EqualTo(4_000_000));
|
||||
|
||||
AddStep("setup event", () =>
|
||||
{
|
||||
update = null;
|
||||
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||
});
|
||||
|
||||
AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo));
|
||||
AddUntilStep("statistics update raised",
|
||||
() => update?.NewStatistics.TotalScore,
|
||||
() => Is.EqualTo(9_000_000));
|
||||
AddAssert("statistics match new score",
|
||||
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
|
||||
() => Is.EqualTo(9_000_000));
|
||||
|
||||
void onStatisticsUpdated(UserStatisticsUpdate u) => update = u;
|
||||
}
|
||||
|
||||
private UserStatistics tryGetStatistics(int userId, string rulesetName)
|
||||
=> serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics();
|
||||
|
||||
private void setUser(int userId, bool generateStatistics = true)
|
||||
{
|
||||
AddStep($"set local user to {userId}", () =>
|
||||
{
|
||||
if (generateStatistics)
|
||||
{
|
||||
serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 };
|
||||
serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 };
|
||||
serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 };
|
||||
serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 };
|
||||
}
|
||||
|
||||
((DummyAPIAccess)API).LocalUser.Value = new APIUser { Id = userId };
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -11,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
@ -24,17 +23,20 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[TestFixture]
|
||||
public partial class TestSceneUserPanel : OsuTestScene
|
||||
{
|
||||
private readonly Bindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
private readonly Bindable<UserActivity?> activity = new Bindable<UserActivity?>();
|
||||
private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();
|
||||
|
||||
private UserGridPanel boundPanel1;
|
||||
private TestUserListPanel boundPanel2;
|
||||
private UserGridPanel boundPanel1 = null!;
|
||||
private TestUserListPanel boundPanel2 = null!;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
[Cached(typeof(LocalUserStatisticsProvider))]
|
||||
private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider();
|
||||
|
||||
[Resolved]
|
||||
private IRulesetStore rulesetStore { get; set; }
|
||||
private IRulesetStore rulesetStore { get; set; } = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
@ -42,7 +44,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
activity.Value = null;
|
||||
status.Value = null;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
Remove(statisticsProvider, false);
|
||||
Clear();
|
||||
Add(statisticsProvider);
|
||||
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -108,7 +114,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||
}) { Width = 300 }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
boundPanel1.Status.BindTo(status);
|
||||
boundPanel1.Activity.BindTo(activity);
|
||||
@ -162,24 +168,21 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("update statistics", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics
|
||||
statisticsProvider.UpdateStatistics(new UserStatistics
|
||||
{
|
||||
GlobalRank = RNG.Next(100000),
|
||||
CountryRank = RNG.Next(100000)
|
||||
});
|
||||
}, Ruleset.Value);
|
||||
});
|
||||
AddStep("set statistics to something big", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics
|
||||
statisticsProvider.UpdateStatistics(new UserStatistics
|
||||
{
|
||||
GlobalRank = RNG.Next(1_000_000, 100_000_000),
|
||||
CountryRank = RNG.Next(1_000_000, 100_000_000)
|
||||
});
|
||||
});
|
||||
AddStep("set statistics to empty", () =>
|
||||
{
|
||||
API.UpdateStatistics(new UserStatistics());
|
||||
}, Ruleset.Value);
|
||||
});
|
||||
AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||
}
|
||||
|
||||
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!);
|
||||
@ -201,5 +204,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
|
||||
}
|
||||
|
||||
public partial class TestUserStatisticsProvider : LocalUserStatisticsProvider
|
||||
{
|
||||
public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action<UserStatisticsUpdate>? callback = null)
|
||||
=> base.UpdateStatistics(newStatistics, ruleset, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
|
||||
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
|
||||
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
|
||||
AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v));
|
||||
AddSliderStep("playcount", 0, 1500, 1, v => update(s => s.PlayCount = v));
|
||||
AddStep("create", () =>
|
||||
{
|
||||
Clear();
|
||||
@ -66,8 +66,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestPlayCountRankingTier()
|
||||
{
|
||||
AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Bronze);
|
||||
AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(31) == RankingTier.Silver);
|
||||
AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(29) == RankingTier.Bronze);
|
||||
AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Silver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
protected override bool UseOnlineAPI => false;
|
||||
|
||||
private LocalUserStatisticsProvider statisticsProvider = null!;
|
||||
private UserStatisticsWatcher watcher = null!;
|
||||
|
||||
[Resolved]
|
||||
@ -107,7 +108,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("create watcher", () =>
|
||||
{
|
||||
Child = watcher = new UserStatisticsWatcher();
|
||||
Clear();
|
||||
Add(statisticsProvider = new LocalUserStatisticsProvider());
|
||||
Add(watcher = new UserStatisticsWatcher(statisticsProvider));
|
||||
});
|
||||
}
|
||||
|
||||
@ -123,7 +126,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -146,7 +149,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
// note ordering - in this test processing completes *before* the registration is added.
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
@ -164,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -191,7 +194,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -212,7 +215,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
@ -241,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 6_000_000);
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
|
||||
@ -259,15 +262,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
UserStatisticsUpdate? update = null;
|
||||
ScoreBasedUserStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddUntilStep("update received", () => update != null);
|
||||
AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
AddAssert("statistics values are correct", () => statisticsProvider.GetStatisticsFor(ruleset)!.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
}
|
||||
|
||||
private int nextUserId = 2000;
|
||||
@ -289,7 +291,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<UserStatisticsUpdate> onUpdateReady) =>
|
||||
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<ScoreBasedUserStatisticsUpdate> onUpdateReady) =>
|
||||
AddStep("register for updates", () =>
|
||||
{
|
||||
watcher.RegisterForStatisticsUpdateAfter(
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
private const int scores_per_result = 10;
|
||||
private const int real_user_position = 200;
|
||||
|
||||
private TestResultsScreen resultsScreen = null!;
|
||||
private ResultsScreen resultsScreen = null!;
|
||||
|
||||
private int lowestScoreId; // Score ID of the lowest score in the list.
|
||||
private int highestScoreId; // Score ID of the highest score in the list.
|
||||
@ -68,11 +69,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowWithUserScore()
|
||||
public void TestShowUserScore()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||
|
||||
createResults(() => userScore);
|
||||
createResultsWithScore(() => userScore);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded);
|
||||
@ -81,11 +82,24 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowNullUserScore()
|
||||
public void TestShowUserBest()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.UserID == userScore.UserID).State == PanelState.Expanded);
|
||||
AddAssert($"score panel position is {real_user_position}",
|
||||
() => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.UserID == userScore.UserID).ScorePosition.Value == real_user_position);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowNonUserScores()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler());
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
||||
@ -96,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(true, userScore));
|
||||
|
||||
createResults(() => userScore);
|
||||
createResultsWithScore(() => userScore);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1);
|
||||
@ -104,11 +118,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowNullUserScoreWithDelay()
|
||||
public void TestShowNonUserScoresWithDelay()
|
||||
{
|
||||
AddStep("bind delayed handler", () => bindHandler(true));
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
||||
@ -119,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind delayed handler", () => bindHandler(true));
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
@ -127,13 +141,16 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
int beforePanelCount = 0;
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
|
||||
AddAssert("right loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("right loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,29 +159,36 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind delayed handler with scores", () => bindHandler(delayed: true));
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
int beforePanelCount = 0;
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
|
||||
AddAssert("right loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("right loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("bind delayed handler with no scores", () => bindHandler(delayed: true, noScores: true));
|
||||
AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
|
||||
AddAssert("right loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("count not increased", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("right loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||
|
||||
AddAssert("no placeholders shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.Zero);
|
||||
}
|
||||
|
||||
@ -173,7 +197,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||
|
||||
createResults(() => userScore);
|
||||
createResultsWithScore(() => userScore);
|
||||
waitForDisplay();
|
||||
|
||||
AddStep("bind delayed handler", () => bindHandler(true));
|
||||
@ -183,30 +207,36 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
int beforePanelCount = 0;
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("scroll to left", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
|
||||
AddStep("scroll to left", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
|
||||
|
||||
AddAssert("left loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("left loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the <see cref="TestUserBestResultsScreen"/> with no scores provided by the API.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestShowWithNoScores()
|
||||
public void TestShowUserBestWithNoScoresPresent()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(noScores: true));
|
||||
createResults();
|
||||
AddAssert("no scores visible", () => !resultsScreen.ScorePanelList.GetScorePanels().Any());
|
||||
createUserBestResults();
|
||||
AddAssert("no scores visible", () => !resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Any());
|
||||
AddAssert("placeholder shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private void createResults(Func<ScoreInfo>? getScore = null)
|
||||
private void createResultsWithScore(Func<ScoreInfo> getScore)
|
||||
{
|
||||
AddStep("load results", () =>
|
||||
{
|
||||
LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
LoadScreen(resultsScreen = new TestScoreResultsScreen(getScore(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}));
|
||||
@ -215,14 +245,27 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded);
|
||||
}
|
||||
|
||||
private void createUserBestResults()
|
||||
{
|
||||
AddStep("load results", () =>
|
||||
{
|
||||
LoadScreen(resultsScreen = new TestUserBestResultsScreen(1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}, 2));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded);
|
||||
}
|
||||
|
||||
private void waitForDisplay()
|
||||
{
|
||||
AddUntilStep("wait for scores loaded", () =>
|
||||
requestComplete
|
||||
// request handler may need to fire more than once to get scores.
|
||||
&& totalCount > 0
|
||||
&& resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount
|
||||
&& resultsScreen.ScorePanelList.AllPanelsVisible);
|
||||
&& resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Count() == totalCount
|
||||
&& resultsScreen.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
AddWaitStep("wait for display", 5);
|
||||
}
|
||||
|
||||
@ -231,6 +274,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
// pre-check for requests we should be handling (as they are scheduled below).
|
||||
switch (request)
|
||||
{
|
||||
case ShowPlaylistScoreRequest:
|
||||
case ShowPlaylistUserScoreRequest:
|
||||
case IndexPlaylistScoresRequest:
|
||||
break;
|
||||
@ -253,7 +297,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
switch (request)
|
||||
{
|
||||
case ShowPlaylistUserScoreRequest s:
|
||||
case ShowPlaylistScoreRequest s:
|
||||
if (userScore == null)
|
||||
triggerFail(s);
|
||||
else
|
||||
@ -261,6 +305,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
break;
|
||||
|
||||
case ShowPlaylistUserScoreRequest u:
|
||||
if (userScore == null)
|
||||
triggerFail(u);
|
||||
else
|
||||
triggerSuccess(u, createUserResponse(userScore));
|
||||
|
||||
break;
|
||||
|
||||
case IndexPlaylistScoresRequest i:
|
||||
triggerSuccess(i, createIndexResponse(i, noScores));
|
||||
break;
|
||||
@ -314,7 +366,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
MaxCombo = userScore.MaxCombo,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Id = 2 + i,
|
||||
Username = $"peppy{i}",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
@ -329,7 +381,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
MaxCombo = userScore.MaxCombo,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Id = 2 + i,
|
||||
Username = $"peppy{i}",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
@ -363,7 +415,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
MaxCombo = 1000,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Id = 2 + i,
|
||||
Username = $"peppy{i}",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
@ -410,18 +462,22 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestResultsScreen : PlaylistItemUserResultsScreen
|
||||
private partial class TestScoreResultsScreen : PlaylistItemScoreResultsScreen
|
||||
{
|
||||
public new LoadingSpinner LeftSpinner => base.LeftSpinner;
|
||||
public new LoadingSpinner CentreSpinner => base.CentreSpinner;
|
||||
public new LoadingSpinner RightSpinner => base.RightSpinner;
|
||||
public new ScorePanelList ScorePanelList => base.ScorePanelList;
|
||||
|
||||
public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem)
|
||||
public TestScoreResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem)
|
||||
: base(score, roomId, playlistItem)
|
||||
{
|
||||
AllowRetry = true;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestUserBestResultsScreen : PlaylistItemUserBestResultsScreen
|
||||
{
|
||||
public TestUserBestResultsScreen(int roomId, PlaylistItem playlistItem, int userId)
|
||||
: base(roomId, playlistItem, userId)
|
||||
{
|
||||
AllowRetry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,67 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
private const double track_length = 10000;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
|
||||
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapSetInfo? importedSet;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var set in r.All<BeatmapSetInfo>())
|
||||
{
|
||||
foreach (var b in set.Beatmaps)
|
||||
{
|
||||
// These will all have a virtual track length of 1000, see WorkingBeatmap.GetVirtualTrack().
|
||||
b.Length = track_length - 1000;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatusUpdateOnEnter()
|
||||
{
|
||||
@ -37,5 +84,66 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf<RoomStatusEnded>);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloseButtonGoesAwayAfterGracePeriod()
|
||||
{
|
||||
Room room = null!;
|
||||
PlaylistsRoomSubScreen roomScreen = null!;
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
RoomManager.AddRoom(room = new Room
|
||||
{
|
||||
Name = @"Test Room",
|
||||
Host = api.LocalUser.Value,
|
||||
Category = RoomCategory.Normal,
|
||||
StartDate = DateTimeOffset.Now.AddMinutes(-5).AddSeconds(3),
|
||||
EndDate = DateTimeOffset.Now.AddMinutes(30)
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddAssert("close button present", () => roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
|
||||
AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
|
||||
}
|
||||
|
||||
[TestCase(120_000, true)] // Definitely enough time.
|
||||
[TestCase(45_000, true)] // Enough time.
|
||||
[TestCase(35_000, false)] // Not enough time to complete beatmap after lenience.
|
||||
[TestCase(20_000, false)] // Not enough time.
|
||||
[TestCase(5_000, false)] // Not enough time to complete beatmap before lenience.
|
||||
[TestCase(37_500, true, 2)] // Enough time to complete beatmap after mods are applied.
|
||||
public void TestReadyButtonEnablementPeriod(int offsetMs, bool enabled, double rate = 1)
|
||||
{
|
||||
Room room = null!;
|
||||
PlaylistsRoomSubScreen roomScreen = null!;
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
RoomManager.AddRoom(room = new Room
|
||||
{
|
||||
Name = @"Test Room",
|
||||
Host = api.LocalUser.Value,
|
||||
Category = RoomCategory.Normal,
|
||||
StartDate = DateTimeOffset.Now,
|
||||
EndDate = DateTimeOffset.Now.AddMilliseconds(offsetMs),
|
||||
Playlist =
|
||||
[
|
||||
new PlaylistItem(importedSet!.Beatmaps[0])
|
||||
{
|
||||
RequiredMods = rate == 1
|
||||
? []
|
||||
: [new APIMod(new OsuModDoubleTime { SpeedChange = { Value = rate } })]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddUntilStep("ready button enabled", () => roomScreen.ChildrenOfType<PlaylistsReadyButton>().SingleOrDefault()?.Enabled.Value, () => Is.EqualTo(enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
|
||||
private void displayUpdate(UserStatistics before, UserStatistics after) =>
|
||||
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new UserStatisticsUpdate(new ScoreInfo(), before, after));
|
||||
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
|
||||
}
|
||||
}
|
||||
|
@ -91,12 +91,12 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
UserStatisticsWatcher userStatisticsWatcher = null!;
|
||||
ScoreInfo score = null!;
|
||||
|
||||
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher()));
|
||||
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher(new LocalUserStatisticsProvider())));
|
||||
AddStep("set user statistics update", () =>
|
||||
{
|
||||
score = TestResources.CreateTestScoreInfo();
|
||||
score.OnlineID = 1234;
|
||||
((Bindable<UserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
|
||||
((Bindable<ScoreBasedUserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new ScoreBasedUserStatisticsUpdate(score,
|
||||
new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Score = { Value = score },
|
||||
DisplayedUserStatisticsUpdate =
|
||||
{
|
||||
Value = new UserStatisticsUpdate(score, new UserStatistics
|
||||
Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
|
@ -8,14 +8,14 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
@ -28,25 +28,31 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneBeatmapRecommendations : OsuGameTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private IRulesetStore rulesetStore { get; set; }
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("populate ruleset statistics", () =>
|
||||
{
|
||||
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
||||
|
||||
rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
|
||||
((DummyAPIAccess)API).HandleRequest = r =>
|
||||
{
|
||||
rulesetStatistics[rulesetInfo.ShortName] = new UserStatistics
|
||||
switch (r)
|
||||
{
|
||||
PP = getNecessaryPP(rulesetInfo.OnlineID)
|
||||
};
|
||||
});
|
||||
case GetUserRequest userRequest:
|
||||
userRequest.TriggerSuccess(new APIUser
|
||||
{
|
||||
Id = 99,
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
PP = getNecessaryPP(userRequest.Ruleset?.OnlineID ?? 0)
|
||||
}
|
||||
});
|
||||
|
||||
API.LocalUser.Value.RulesetsStatistics = rulesetStatistics;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
decimal getNecessaryPP(int? rulesetID)
|
||||
|
@ -101,7 +101,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuMenuItem(@"Another nested option")
|
||||
{
|
||||
Items = new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem(@"Sub-One"),
|
||||
new OsuMenuItem(@"Sub-Two"),
|
||||
new OsuMenuItem(@"Sub-Three"),
|
||||
}
|
||||
},
|
||||
new OsuMenuItem(@"Choose me please"),
|
||||
|
@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("click delete option", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete"));
|
||||
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase)));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
@ -89,8 +90,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
new FormFileSelector
|
||||
{
|
||||
Caption = "Audio file",
|
||||
PlaceholderText = "Select an audio file",
|
||||
Caption = "File selector",
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormBeatmapFileSelector(true)
|
||||
{
|
||||
Caption = "File selector with intermediate choice dialog",
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormColourPalette
|
||||
{
|
||||
|
@ -12,9 +12,9 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<CodeAnalysisRuleSet>tests.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<GlobalAnalyzerConfigFiles Include="CodeAnalysis.tests.globalconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
||||
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
|
||||
<Rule Id="CA2007" Action="None" />
|
||||
</Rules>
|
||||
</RuleSet>
|
@ -115,6 +115,32 @@ namespace osu.Game.Beatmaps
|
||||
return mostCommon.beatLength;
|
||||
}
|
||||
|
||||
public double AudioLeadIn { get; set; }
|
||||
|
||||
public float StackLeniency { get; set; } = 0.7f;
|
||||
|
||||
public bool SpecialStyle { get; set; }
|
||||
|
||||
public bool LetterboxInBreaks { get; set; }
|
||||
|
||||
public bool WidescreenStoryboard { get; set; } = true;
|
||||
|
||||
public bool EpilepsyWarning { get; set; }
|
||||
|
||||
public bool SamplesMatchPlaybackRate { get; set; }
|
||||
|
||||
public double DistanceSpacing { get; set; } = 1.0;
|
||||
|
||||
public int GridSize { get; set; }
|
||||
|
||||
public double TimelineZoom { get; set; } = 1.0;
|
||||
|
||||
public CountdownType Countdown { get; set; } = CountdownType.None;
|
||||
|
||||
public int CountdownOffset { get; set; }
|
||||
|
||||
public int[] Bookmarks { get; set; } = Array.Empty<int>();
|
||||
|
||||
IBeatmap IBeatmap.Clone() => Clone();
|
||||
|
||||
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
|
||||
|
@ -73,6 +73,19 @@ namespace osu.Game.Beatmaps
|
||||
beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList();
|
||||
beatmap.Breaks = original.Breaks;
|
||||
beatmap.UnhandledEventLines = original.UnhandledEventLines;
|
||||
beatmap.AudioLeadIn = original.AudioLeadIn;
|
||||
beatmap.StackLeniency = original.StackLeniency;
|
||||
beatmap.SpecialStyle = original.SpecialStyle;
|
||||
beatmap.LetterboxInBreaks = original.LetterboxInBreaks;
|
||||
beatmap.WidescreenStoryboard = original.WidescreenStoryboard;
|
||||
beatmap.EpilepsyWarning = original.EpilepsyWarning;
|
||||
beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate;
|
||||
beatmap.DistanceSpacing = original.DistanceSpacing;
|
||||
beatmap.GridSize = original.GridSize;
|
||||
beatmap.TimelineZoom = original.TimelineZoom;
|
||||
beatmap.Countdown = original.Countdown;
|
||||
beatmap.CountdownOffset = original.CountdownOffset;
|
||||
beatmap.Bookmarks = original.Bookmarks;
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
@ -428,17 +428,7 @@ namespace osu.Game.Beatmaps
|
||||
Hash = hash,
|
||||
DifficultyName = decodedInfo.DifficultyName,
|
||||
OnlineID = decodedInfo.OnlineID,
|
||||
AudioLeadIn = decodedInfo.AudioLeadIn,
|
||||
StackLeniency = decodedInfo.StackLeniency,
|
||||
SpecialStyle = decodedInfo.SpecialStyle,
|
||||
LetterboxInBreaks = decodedInfo.LetterboxInBreaks,
|
||||
WidescreenStoryboard = decodedInfo.WidescreenStoryboard,
|
||||
EpilepsyWarning = decodedInfo.EpilepsyWarning,
|
||||
SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate,
|
||||
DistanceSpacing = decodedInfo.DistanceSpacing,
|
||||
BeatDivisor = decodedInfo.BeatDivisor,
|
||||
GridSize = decodedInfo.GridSize,
|
||||
TimelineZoom = decodedInfo.TimelineZoom,
|
||||
MD5Hash = memoryStream.ComputeMD5Hash(),
|
||||
EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration),
|
||||
TotalObjectCount = decoded.HitObjects.Count
|
||||
|
@ -6,14 +6,12 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Scoring;
|
||||
using Realms;
|
||||
|
||||
@ -136,60 +134,18 @@ namespace osu.Game.Beatmaps
|
||||
Status = BeatmapOnlineStatus.None;
|
||||
}
|
||||
|
||||
#region Properties we may not want persisted (but also maybe no harm?)
|
||||
|
||||
public double AudioLeadIn { get; set; }
|
||||
|
||||
public float StackLeniency { get; set; } = 0.7f;
|
||||
|
||||
public bool SpecialStyle { get; set; }
|
||||
|
||||
public bool LetterboxInBreaks { get; set; }
|
||||
|
||||
public bool WidescreenStoryboard { get; set; } = true;
|
||||
|
||||
public bool EpilepsyWarning { get; set; }
|
||||
|
||||
public bool SamplesMatchPlaybackRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time at which this beatmap was last played by the local user.
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastPlayed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ratio of distance travelled per time unit.
|
||||
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap
|
||||
/// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider.
|
||||
///
|
||||
/// This is only a hint property, used by the editor in <see cref="IDistanceSnapProvider"/> implementations. It does not directly affect the beatmap or gameplay.
|
||||
/// </remarks>
|
||||
public double DistanceSpacing { get; set; } = 1.0;
|
||||
|
||||
public int BeatDivisor { get; set; } = 4;
|
||||
|
||||
public int GridSize { get; set; }
|
||||
|
||||
public double TimelineZoom { get; set; } = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// The time in milliseconds when last exiting the editor with this beatmap loaded.
|
||||
/// </summary>
|
||||
public double? EditorTimestamp { get; set; }
|
||||
|
||||
[Ignored]
|
||||
public CountdownType Countdown { get; set; } = CountdownType.None;
|
||||
|
||||
/// <summary>
|
||||
/// The number of beats to move the countdown backwards (compared to its default location).
|
||||
/// </summary>
|
||||
public int CountdownOffset { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public bool Equals(BeatmapInfo? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
@ -275,9 +231,6 @@ namespace osu.Game.Beatmaps
|
||||
[Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")]
|
||||
public int? MaxCombo { get; set; }
|
||||
|
||||
[Ignored]
|
||||
public int[] Bookmarks { get; set; } = Array.Empty<int>();
|
||||
|
||||
public int BeatmapVersion;
|
||||
|
||||
public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone();
|
||||
|
@ -15,6 +15,7 @@ using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
@ -154,9 +155,20 @@ namespace osu.Game.Beatmaps
|
||||
DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty")
|
||||
};
|
||||
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
|
||||
|
||||
foreach (var timingPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.TimingPoints)
|
||||
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
|
||||
|
||||
foreach (var effectPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.EffectPoints)
|
||||
{
|
||||
var clonedEffectPoint = (EffectControlPoint)effectPoint.DeepClone();
|
||||
|
||||
if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset))
|
||||
clonedEffectPoint.ScrollSpeedBindable.SetDefault();
|
||||
|
||||
newBeatmap.ControlPointInfo.Add(clonedEffectPoint.Time, clonedEffectPoint);
|
||||
}
|
||||
|
||||
return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin);
|
||||
}
|
||||
|
||||
@ -408,7 +420,7 @@ namespace osu.Game.Beatmaps
|
||||
// user requested abort
|
||||
return;
|
||||
|
||||
var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase)));
|
||||
var video = b.Files.FirstOrDefault(f => SupportedExtensions.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
@ -559,7 +571,11 @@ namespace osu.Game.Beatmaps
|
||||
// If we seem to be missing files, now is a good time to re-fetch.
|
||||
bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0;
|
||||
|
||||
if (refetch || beatmapInfo.IsManaged || missingFiles)
|
||||
if (beatmapInfo.IsManaged)
|
||||
{
|
||||
beatmapInfo = beatmapInfo.Detach();
|
||||
}
|
||||
else if (refetch || missingFiles)
|
||||
{
|
||||
Guid id = beatmapInfo.ID;
|
||||
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
||||
|
@ -9,9 +9,11 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -21,18 +23,63 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public partial class DifficultyRecommender : Component
|
||||
{
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private readonly LocalUserStatisticsProvider statisticsProvider;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||
private Bindable<RulesetInfo> gameRuleset { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
private readonly Dictionary<string, double> recommendedDifficultyMapping = new Dictionary<string, double>();
|
||||
|
||||
/// <returns>
|
||||
/// Rulesets ordered descending by their respective recommended difficulties.
|
||||
/// The currently selected ruleset will always be first.
|
||||
/// </returns>
|
||||
private IEnumerable<string> orderedRulesets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LoadState < LoadState.Ready || gameRuleset.Value == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return recommendedDifficultyMapping
|
||||
.OrderByDescending(pair => pair.Value)
|
||||
.Select(pair => pair.Key)
|
||||
.Where(r => !r.Equals(gameRuleset.Value.ShortName, StringComparison.Ordinal))
|
||||
.Prepend(gameRuleset.Value.ShortName);
|
||||
}
|
||||
}
|
||||
|
||||
public DifficultyRecommender(LocalUserStatisticsProvider statisticsProvider)
|
||||
{
|
||||
this.statisticsProvider = statisticsProvider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
api.LocalUser.BindValueChanged(_ => populateValues(), true);
|
||||
foreach (var ruleset in rulesets.AvailableRulesets)
|
||||
{
|
||||
if (statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics)
|
||||
updateMapping(ruleset, statistics);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
|
||||
}
|
||||
|
||||
private void onStatisticsUpdated(UserStatisticsUpdate update) => updateMapping(update.Ruleset, update.NewStatistics);
|
||||
|
||||
private void updateMapping(RulesetInfo ruleset, UserStatistics statistics)
|
||||
{
|
||||
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
||||
recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -64,35 +111,12 @@ namespace osu.Game.Beatmaps
|
||||
return null;
|
||||
}
|
||||
|
||||
private void populateValues()
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (api.LocalUser.Value.RulesetsStatistics == null)
|
||||
return;
|
||||
if (statisticsProvider.IsNotNull())
|
||||
statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
|
||||
|
||||
foreach (var kvp in api.LocalUser.Value.RulesetsStatistics)
|
||||
{
|
||||
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
||||
recommendedDifficultyMapping[kvp.Key] = Math.Pow((double)(kvp.Value.PP ?? 0), 0.4) * 0.195;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// Rulesets ordered descending by their respective recommended difficulties.
|
||||
/// The currently selected ruleset will always be first.
|
||||
/// </returns>
|
||||
private IEnumerable<string> orderedRulesets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LoadState < LoadState.Ready || ruleset.Value == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return recommendedDifficultyMapping
|
||||
.OrderByDescending(pair => pair.Value)
|
||||
.Select(pair => pair.Key)
|
||||
.Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal))
|
||||
.Prepend(ruleset.Value.ShortName);
|
||||
}
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -50,7 +51,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
|
||||
/// Whether beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
|
||||
/// </summary>
|
||||
public bool ApplyOffsets = true;
|
||||
|
||||
@ -81,7 +82,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
|
||||
parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
|
||||
applyLegacyDefaults(this.beatmap.BeatmapInfo);
|
||||
ApplyLegacyDefaults(this.beatmap);
|
||||
|
||||
base.ParseStreamInto(stream, beatmap);
|
||||
|
||||
@ -189,9 +190,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
/// This method's intention is to restore those legacy defaults.
|
||||
/// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29
|
||||
/// </summary>
|
||||
private static void applyLegacyDefaults(BeatmapInfo beatmapInfo)
|
||||
internal static void ApplyLegacyDefaults(Beatmap beatmap)
|
||||
{
|
||||
beatmapInfo.WidescreenStoryboard = false;
|
||||
beatmap.WidescreenStoryboard = false;
|
||||
}
|
||||
|
||||
protected override void ParseLine(Beatmap beatmap, Section section, string line)
|
||||
@ -243,7 +244,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"AudioLeadIn":
|
||||
beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value);
|
||||
beatmap.AudioLeadIn = Parsing.ParseInt(pair.Value);
|
||||
break;
|
||||
|
||||
case @"PreviewTime":
|
||||
@ -260,7 +261,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"StackLeniency":
|
||||
beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value);
|
||||
beatmap.StackLeniency = Parsing.ParseFloat(pair.Value);
|
||||
break;
|
||||
|
||||
case @"Mode":
|
||||
@ -268,31 +269,31 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"LetterboxInBreaks":
|
||||
beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1;
|
||||
beatmap.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"SpecialStyle":
|
||||
beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1;
|
||||
beatmap.SpecialStyle = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"WidescreenStoryboard":
|
||||
beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
|
||||
beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"EpilepsyWarning":
|
||||
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
|
||||
beatmap.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"SamplesMatchPlaybackRate":
|
||||
beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
|
||||
beatmap.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"Countdown":
|
||||
beatmap.BeatmapInfo.Countdown = Enum.Parse<CountdownType>(pair.Value);
|
||||
beatmap.Countdown = Enum.Parse<CountdownType>(pair.Value);
|
||||
break;
|
||||
|
||||
case @"CountdownOffset":
|
||||
beatmap.BeatmapInfo.CountdownOffset = Parsing.ParseInt(pair.Value);
|
||||
beatmap.CountdownOffset = Parsing.ParseInt(pair.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -304,7 +305,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
switch (pair.Key)
|
||||
{
|
||||
case @"Bookmarks":
|
||||
beatmap.BeatmapInfo.Bookmarks = pair.Value.Split(',').Select(v =>
|
||||
beatmap.Bookmarks = pair.Value.Split(',').Select(v =>
|
||||
{
|
||||
bool result = int.TryParse(v, out int val);
|
||||
return new { result, val };
|
||||
@ -312,7 +313,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"DistanceSpacing":
|
||||
beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value));
|
||||
beatmap.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value));
|
||||
break;
|
||||
|
||||
case @"BeatDivisor":
|
||||
@ -320,11 +321,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"GridSize":
|
||||
beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value);
|
||||
beatmap.GridSize = Parsing.ParseInt(pair.Value);
|
||||
break;
|
||||
|
||||
case @"TimelineZoom":
|
||||
beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value));
|
||||
beatmap.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -446,7 +447,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
||||
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
||||
// video extensions and handle similar to a background if it doesn't match.
|
||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||
if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
lineSupportedByEncoder = true;
|
||||
|
@ -79,14 +79,14 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.WriteLine("[General]");
|
||||
|
||||
if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.AudioLeadIn}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.Countdown}"));
|
||||
writer.WriteLine(FormattableString.Invariant(
|
||||
$"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.LetterboxInBreaks ? '1' : '0')}"));
|
||||
// if (beatmap.BeatmapInfo.UseSkinSprites)
|
||||
// writer.WriteLine(@"UseSkinSprites: 1");
|
||||
// if (b.AlwaysShowPlayfield)
|
||||
@ -95,14 +95,14 @@ namespace osu.Game.Beatmaps.Formats
|
||||
// writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition);
|
||||
// if (!string.IsNullOrEmpty(b.SkinPreference))
|
||||
// writer.WriteLine(@"SkinPreference:" + b.SkinPreference);
|
||||
if (beatmap.BeatmapInfo.EpilepsyWarning)
|
||||
if (beatmap.EpilepsyWarning)
|
||||
writer.WriteLine(@"EpilepsyWarning: 1");
|
||||
if (beatmap.BeatmapInfo.CountdownOffset > 0)
|
||||
writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}"));
|
||||
if (beatmap.CountdownOffset > 0)
|
||||
writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.CountdownOffset}"));
|
||||
if (onlineRulesetID == 3)
|
||||
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
|
||||
if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
|
||||
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}"));
|
||||
if (beatmap.SamplesMatchPlaybackRate)
|
||||
writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
|
||||
}
|
||||
|
||||
@ -110,12 +110,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
writer.WriteLine("[Editor]");
|
||||
|
||||
if (beatmap.BeatmapInfo.Bookmarks.Length > 0)
|
||||
writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.BeatmapInfo.DistanceSpacing}"));
|
||||
if (beatmap.Bookmarks.Length > 0)
|
||||
writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.Bookmarks)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.TimelineZoom}"));
|
||||
}
|
||||
|
||||
private void handleMetadata(TextWriter writer)
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Commands;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -37,6 +38,17 @@ namespace osu.Game.Beatmaps.Formats
|
||||
SetFallbackDecoder<Storyboard>(() => new LegacyStoryboardDecoder());
|
||||
}
|
||||
|
||||
protected override Storyboard CreateTemplateObject()
|
||||
{
|
||||
var sb = base.CreateTemplateObject();
|
||||
|
||||
var beatmap = new Beatmap();
|
||||
LegacyBeatmapDecoder.ApplyLegacyDefaults(beatmap);
|
||||
sb.Beatmap = beatmap;
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard)
|
||||
{
|
||||
this.storyboard = storyboard;
|
||||
@ -72,6 +84,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
case "UseSkinSprites":
|
||||
storyboard.UseSkinSprites = pair.Value == "1";
|
||||
break;
|
||||
|
||||
case @"WidescreenStoryboard":
|
||||
storyboard.Beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +128,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
//
|
||||
// This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video
|
||||
// (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451).
|
||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
|
||||
if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
|
||||
break;
|
||||
|
||||
storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset));
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
@ -69,6 +70,45 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
double GetMostCommonBeatLength();
|
||||
|
||||
double AudioLeadIn { get; internal set; }
|
||||
|
||||
float StackLeniency { get; internal set; }
|
||||
|
||||
bool SpecialStyle { get; internal set; }
|
||||
|
||||
bool LetterboxInBreaks { get; internal set; }
|
||||
|
||||
bool WidescreenStoryboard { get; internal set; }
|
||||
|
||||
bool EpilepsyWarning { get; internal set; }
|
||||
|
||||
bool SamplesMatchPlaybackRate { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ratio of distance travelled per time unit.
|
||||
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap
|
||||
/// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider.
|
||||
///
|
||||
/// This is only a hint property, used by the editor in <see cref="IDistanceSnapProvider"/> implementations. It does not directly affect the beatmap or gameplay.
|
||||
/// </remarks>
|
||||
double DistanceSpacing { get; internal set; }
|
||||
|
||||
int GridSize { get; internal set; }
|
||||
|
||||
double TimelineZoom { get; internal set; }
|
||||
|
||||
CountdownType Countdown { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of beats to move the countdown backwards (compared to its default location).
|
||||
/// </summary>
|
||||
int CountdownOffset { get; internal set; }
|
||||
|
||||
int[] Bookmarks { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow-clone of this beatmap and returns it.
|
||||
/// </summary>
|
||||
|
@ -62,7 +62,12 @@ namespace osu.Game.Beatmaps
|
||||
#region Resource getters
|
||||
|
||||
protected virtual Waveform GetWaveform() => new Waveform(null);
|
||||
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
||||
|
||||
protected virtual Storyboard GetStoryboard() => new Storyboard
|
||||
{
|
||||
BeatmapInfo = BeatmapInfo,
|
||||
Beatmap = Beatmap,
|
||||
};
|
||||
|
||||
protected abstract IBeatmap GetBeatmap();
|
||||
public abstract Texture GetBackground();
|
||||
|
@ -138,6 +138,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.LightenDuringBreaks, true);
|
||||
|
||||
SetDefault(OsuSetting.HitLighting, true);
|
||||
SetDefault(OsuSetting.StarFountains, true);
|
||||
|
||||
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
|
||||
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
||||
@ -214,6 +215,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.EditorContractSidebars, false);
|
||||
|
||||
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
||||
SetDefault(OsuSetting.AlwaysRequireHoldingForPause, false);
|
||||
}
|
||||
|
||||
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
||||
@ -413,6 +415,7 @@ namespace osu.Game.Configuration
|
||||
NotifyOnPrivateMessage,
|
||||
UIHoldActivationDelay,
|
||||
HitLighting,
|
||||
StarFountains,
|
||||
MenuBackgroundSource,
|
||||
GameplayDisableWinKey,
|
||||
SeasonalBackgroundMode,
|
||||
@ -444,5 +447,6 @@ namespace osu.Game.Configuration
|
||||
EditorRotationOrigin,
|
||||
EditorTimelineShowBreaks,
|
||||
EditorAdjustExistingObjectsOnTimingChanges,
|
||||
AlwaysRequireHoldingForPause
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
@ -77,7 +78,8 @@ namespace osu.Game.Configuration
|
||||
TouchInputActive,
|
||||
|
||||
/// <summary>
|
||||
/// Stores the local user's last score (can be completed or aborted).
|
||||
/// Contains the local user's last score (can be completed or aborted) after exiting <see cref="Player"/>.
|
||||
/// Will be cleared to <c>null</c> when leaving <see cref="PlayerLoader"/>.
|
||||
/// </summary>
|
||||
LastLocalUserScore,
|
||||
|
||||
|
@ -94,8 +94,9 @@ namespace osu.Game.Database
|
||||
/// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances.
|
||||
/// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction
|
||||
/// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user.
|
||||
/// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm.
|
||||
/// </summary>
|
||||
private const int schema_version = 43;
|
||||
private const int schema_version = 44;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
|
@ -248,29 +248,30 @@ namespace osu.Game.Database
|
||||
return new RealmLive<T>(realmObject, realm);
|
||||
}
|
||||
|
||||
#pragma warning disable RS0030 // mentioning banned symbols in documentation
|
||||
/// <summary>
|
||||
/// Register a callback to be invoked each time this <see cref="T:Realms.IRealmCollection`1" /> changes.
|
||||
/// Register a callback to be invoked each time this <see cref="IRealmCollection{T}" /> changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This adds osu! specific thread and managed state safety checks on top of <see cref="IRealmCollection{T}.SubscribeForNotifications"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The first callback will be invoked with the initial <see cref="T:Realms.IRealmCollection`1" /> after the asynchronous query completes,
|
||||
/// The first callback will be invoked with the initial <see cref="IRealmCollection{T}" /> after the asynchronous query completes,
|
||||
/// and then called again after each write transaction which changes either any of the objects in the collection, or
|
||||
/// which objects are in the collection. The <c>changes</c> parameter will
|
||||
/// be <c>null</c> the first time the callback is invoked with the initial results. For each call after that,
|
||||
/// it will contain information about which rows in the results were added, removed or modified.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If a write transaction did not modify any objects in this <see cref="T:Realms.IRealmCollection`1" />, the callback is not invoked at all.
|
||||
/// If a write transaction did not modify any objects in this <see cref="IRealmCollection{T}" />, the callback is not invoked at all.
|
||||
/// If an error occurs the callback will be invoked with <c>null</c> for the <c>sender</c> parameter and a non-<c>null</c> <c>error</c>.
|
||||
/// Currently the only errors that can occur are when opening the <see cref="T:Realms.Realm" /> on the background worker thread.
|
||||
/// Currently the only errors that can occur are when opening the <see cref="Realm" /> on the background worker thread.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// At the time when the block is called, the <see cref="T:Realms.IRealmCollection`1" /> object will be fully evaluated
|
||||
/// At the time when the block is called, the <see cref="IRealmCollection{T}" /> object will be fully evaluated
|
||||
/// and up-to-date, and as long as you do not perform a write transaction on the same thread
|
||||
/// or explicitly call <see cref="M:Realms.Realm.Refresh" />, accessing it will never perform blocking work.
|
||||
/// or explicitly call <see cref="Realm.Refresh" />, accessing it will never perform blocking work.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Notifications are delivered via the standard event loop, and so can't be delivered while the event loop is blocked by other activity.
|
||||
@ -279,13 +280,14 @@ namespace osu.Game.Database
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="collection">The <see cref="IRealmCollection{T}"/> to observe for changes.</param>
|
||||
/// <param name="callback">The callback to be invoked with the updated <see cref="T:Realms.IRealmCollection`1" />.</param>
|
||||
/// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}" />.</param>
|
||||
/// <returns>
|
||||
/// A subscription token. It must be kept alive for as long as you want to receive change notifications.
|
||||
/// To stop receiving notifications, call <see cref="M:System.IDisposable.Dispose" />.
|
||||
/// To stop receiving notifications, call <see cref="IDisposable.Dispose" />.
|
||||
/// </returns>
|
||||
/// <seealso cref="M:Realms.CollectionExtensions.SubscribeForNotifications``1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0})" />
|
||||
/// <seealso cref="M:Realms.CollectionExtensions.SubscribeForNotifications``1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0})" />
|
||||
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IList{T}, NotificationCallbackDelegate{T})" />
|
||||
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IQueryable{T}, NotificationCallbackDelegate{T})" />
|
||||
#pragma warning restore RS0030
|
||||
public static IDisposable QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> callback)
|
||||
where T : RealmObjectBase
|
||||
{
|
||||
|
@ -245,8 +245,8 @@ namespace osu.Game.Database
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
|
||||
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||
score.Rank = computeRank(score, scoreProcessor);
|
||||
score.Accuracy = ComputeAccuracy(score, scoreProcessor);
|
||||
score.Rank = ComputeRank(score, scoreProcessor);
|
||||
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap);
|
||||
}
|
||||
|
||||
@ -269,8 +269,8 @@ namespace osu.Game.Database
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
|
||||
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||
score.Rank = computeRank(score, scoreProcessor);
|
||||
score.Accuracy = ComputeAccuracy(score, scoreProcessor);
|
||||
score.Rank = ComputeRank(score, scoreProcessor);
|
||||
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
|
||||
}
|
||||
|
||||
@ -313,7 +313,8 @@ namespace osu.Game.Database
|
||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||
/// <returns>The standardised total score.</returns>
|
||||
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty,
|
||||
LegacyScoreAttributes attributes)
|
||||
{
|
||||
if (!score.IsLegacyScore)
|
||||
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||
@ -620,24 +621,31 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||
public static double ComputeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||
=> ComputeAccuracy(scoreInfo.Statistics, scoreInfo.MaximumStatistics, scoreProcessor);
|
||||
|
||||
public static double ComputeAccuracy(IReadOnlyDictionary<HitResult, int> statistics, IReadOnlyDictionary<HitResult, int> maximumStatistics, ScoreProcessor scoreProcessor)
|
||||
{
|
||||
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
int baseScore = statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
int maxBaseScore = maximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
|
||||
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
|
||||
}
|
||||
|
||||
public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor());
|
||||
public static ScoreRank ComputeRank(ScoreInfo scoreInfo) =>
|
||||
ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor());
|
||||
|
||||
private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||
public static ScoreRank ComputeRank(ScoreInfo scoreInfo, ScoreProcessor processor) =>
|
||||
ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, processor);
|
||||
|
||||
public static ScoreRank ComputeRank(double accuracy, IReadOnlyDictionary<HitResult, int> statistics, IList<Mod> mods, ScoreProcessor scoreProcessor)
|
||||
{
|
||||
var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics);
|
||||
var rank = scoreProcessor.RankFromScore(accuracy, statistics);
|
||||
|
||||
foreach (var mod in scoreInfo.Mods.OfType<IApplicableToScoreProcessor>())
|
||||
rank = mod.AdjustRank(rank, scoreInfo.Accuracy);
|
||||
foreach (var mod in mods.OfType<IApplicableToScoreProcessor>())
|
||||
rank = mod.AdjustRank(rank, accuracy);
|
||||
|
||||
return rank;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Extensions
|
||||
public static string ToCamelCase(this string input)
|
||||
{
|
||||
string word = input.ToPascalCase();
|
||||
return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word;
|
||||
return word.Length > 0 ? char.ToLowerInvariant(word[0]) + word.Substring(1) : word;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -11,16 +11,8 @@ namespace osu.Game.Graphics.Cursor
|
||||
[Cached(typeof(OsuContextMenuContainer))]
|
||||
public partial class OsuContextMenuContainer : ContextMenuContainer
|
||||
{
|
||||
[Cached]
|
||||
private OsuContextMenuSamples samples = new OsuContextMenuSamples();
|
||||
|
||||
private OsuContextMenu menu = null!;
|
||||
|
||||
public OsuContextMenuContainer()
|
||||
{
|
||||
AddInternal(samples);
|
||||
}
|
||||
|
||||
protected override Menu CreateMenu() => menu = new OsuContextMenu(true);
|
||||
|
||||
public void CloseMenu()
|
||||
|
@ -23,6 +23,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
DialogCancel,
|
||||
|
||||
[Description("dialog-ok")]
|
||||
DialogOk
|
||||
DialogOk,
|
||||
|
||||
[Description("menu-open")]
|
||||
MenuOpen,
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private const int fade_duration = 250;
|
||||
|
||||
[Resolved]
|
||||
private OsuContextMenuSamples samples { get; set; } = null!;
|
||||
private OsuMenuSamples menuSamples { get; set; } = null!;
|
||||
|
||||
// todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed.
|
||||
private bool wasOpened;
|
||||
@ -47,15 +47,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override void AnimateOpen()
|
||||
{
|
||||
wasOpened = true;
|
||||
this.FadeIn(fade_duration, Easing.OutQuint);
|
||||
|
||||
if (playClickSample)
|
||||
samples.PlayClickSample();
|
||||
if (!playClickSample)
|
||||
return;
|
||||
|
||||
if (!wasOpened)
|
||||
samples.PlayOpenSample();
|
||||
|
||||
wasOpened = true;
|
||||
menuSamples.PlayClickSample();
|
||||
menuSamples.PlayOpenSample();
|
||||
}
|
||||
|
||||
protected override void AnimateClose()
|
||||
@ -63,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
this.FadeOut(fade_duration, Easing.OutQuint);
|
||||
|
||||
if (wasOpened)
|
||||
samples.PlayCloseSample();
|
||||
menuSamples.PlayCloseSample();
|
||||
|
||||
wasOpened = false;
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class OsuContextMenuSamples : Component
|
||||
{
|
||||
private Sample sampleClick;
|
||||
private Sample sampleOpen;
|
||||
private Sample sampleClose;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleClick = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
|
||||
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
|
||||
}
|
||||
|
||||
public void PlayClickSample() => Scheduler.AddOnce(playClickSample);
|
||||
private void playClickSample() => sampleClick.Play();
|
||||
|
||||
public void PlayOpenSample() => Scheduler.AddOnce(playOpenSample);
|
||||
private void playOpenSample() => sampleOpen.Play();
|
||||
|
||||
public void PlayCloseSample() => Scheduler.AddOnce(playCloseSample);
|
||||
private void playCloseSample() => sampleClose.Play();
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -20,12 +18,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class OsuMenu : Menu
|
||||
{
|
||||
private Sample sampleOpen;
|
||||
private Sample sampleClose;
|
||||
|
||||
// todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed.
|
||||
private bool wasOpened;
|
||||
|
||||
[Resolved]
|
||||
private OsuMenuSamples menuSamples { get; set; } = null!;
|
||||
|
||||
public OsuMenu(Direction direction, bool topLevelMenu = false)
|
||||
: base(direction, topLevelMenu)
|
||||
{
|
||||
@ -33,13 +31,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
MaskingContainer.CornerRadius = 4;
|
||||
ItemsContainer.Padding = new MarginPadding(5);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
|
||||
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
|
||||
OnSubmenuOpen += _ => { menuSamples?.PlaySubOpenSample(); };
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -64,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override void AnimateOpen()
|
||||
{
|
||||
if (!TopLevelMenu && !wasOpened)
|
||||
sampleOpen?.Play();
|
||||
menuSamples?.PlayOpenSample();
|
||||
|
||||
this.FadeIn(300, Easing.OutQuint);
|
||||
wasOpened = true;
|
||||
@ -73,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override void AnimateClose()
|
||||
{
|
||||
if (!TopLevelMenu && wasOpened)
|
||||
sampleClose?.Play();
|
||||
menuSamples?.PlayCloseSample();
|
||||
|
||||
this.FadeOut(300, Easing.OutQuint);
|
||||
wasOpened = false;
|
||||
|
70
osu.Game/Graphics/UserInterface/OsuMenuSamples.cs
Normal file
70
osu.Game/Graphics/UserInterface/OsuMenuSamples.cs
Normal file
@ -0,0 +1,70 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class OsuMenuSamples : Component
|
||||
{
|
||||
private Sample sampleClick;
|
||||
private Sample sampleOpen;
|
||||
private Sample sampleSubOpen;
|
||||
private Sample sampleClose;
|
||||
|
||||
private bool triggerOpen;
|
||||
private bool triggerSubOpen;
|
||||
private bool triggerClose;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleClick = audio.Samples.Get(@"UI/menu-open-select");
|
||||
sampleOpen = audio.Samples.Get(@"UI/menu-open");
|
||||
sampleSubOpen = audio.Samples.Get(@"UI/menu-sub-open");
|
||||
sampleClose = audio.Samples.Get(@"UI/menu-close");
|
||||
}
|
||||
|
||||
public void PlayClickSample()
|
||||
{
|
||||
Scheduler.AddOnce(playClickSample);
|
||||
}
|
||||
|
||||
public void PlayOpenSample()
|
||||
{
|
||||
triggerOpen = true;
|
||||
Scheduler.AddOnce(resolvePlayback);
|
||||
}
|
||||
|
||||
public void PlaySubOpenSample()
|
||||
{
|
||||
triggerSubOpen = true;
|
||||
Scheduler.AddOnce(resolvePlayback);
|
||||
}
|
||||
|
||||
public void PlayCloseSample()
|
||||
{
|
||||
triggerClose = true;
|
||||
Scheduler.AddOnce(resolvePlayback);
|
||||
}
|
||||
|
||||
private void playClickSample() => sampleClick.Play();
|
||||
|
||||
private void resolvePlayback()
|
||||
{
|
||||
if (triggerSubOpen)
|
||||
sampleSubOpen?.Play();
|
||||
else if (triggerOpen)
|
||||
sampleOpen?.Play();
|
||||
else if (triggerClose)
|
||||
sampleClose?.Play();
|
||||
|
||||
triggerOpen = triggerSubOpen = triggerClose = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -242,20 +242,26 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException();
|
||||
|
||||
protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath) => new FileChooserPopover(handledExtensions, current, chooserPath);
|
||||
|
||||
public Popover GetPopover()
|
||||
{
|
||||
var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath);
|
||||
var popover = CreatePopover(handledExtensions, Current, initialChooserPath);
|
||||
popoverState.UnbindBindings();
|
||||
popoverState.BindTo(popover.State);
|
||||
return popover;
|
||||
}
|
||||
|
||||
private partial class FileChooserPopover : OsuPopover
|
||||
protected partial class FileChooserPopover : OsuPopover
|
||||
{
|
||||
protected override string PopInSampleName => "UI/overlay-big-pop-in";
|
||||
protected override string PopOutSampleName => "UI/overlay-big-pop-out";
|
||||
|
||||
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath)
|
||||
private readonly Bindable<FileInfo?> current = new Bindable<FileInfo?>();
|
||||
|
||||
protected OsuFileSelector FileSelector;
|
||||
|
||||
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath)
|
||||
: base(false)
|
||||
{
|
||||
Child = new Container
|
||||
@ -264,12 +270,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
// simplest solution to avoid underlying text to bleed through the bottom border
|
||||
// https://github.com/ppy/osu/pull/30005#issuecomment-2378884430
|
||||
Padding = new MarginPadding { Bottom = 1 },
|
||||
Child = new OsuFileSelector(chooserPath, handledExtensions)
|
||||
Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CurrentFile = { BindTarget = currentFile }
|
||||
},
|
||||
};
|
||||
|
||||
this.current.BindTo(current);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -292,6 +299,19 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
FileSelector.CurrentFile.ValueChanged += f =>
|
||||
{
|
||||
if (f.NewValue != null)
|
||||
OnFileSelected(f.NewValue);
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual void OnFileSelected(FileInfo file) => current.Value = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2.FileSelection;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@ -96,24 +97,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsuGameBase.VIDEO_EXTENSIONS.Contains(File.Extension.ToLowerInvariant()))
|
||||
string extension = File.Extension.ToLowerInvariant();
|
||||
|
||||
if (SupportedExtensions.VIDEO_EXTENSIONS.Contains(extension))
|
||||
return FontAwesome.Regular.FileVideo;
|
||||
|
||||
switch (File.Extension)
|
||||
{
|
||||
case @".ogg":
|
||||
case @".mp3":
|
||||
case @".wav":
|
||||
return FontAwesome.Regular.FileAudio;
|
||||
if (SupportedExtensions.AUDIO_EXTENSIONS.Contains(extension))
|
||||
return FontAwesome.Regular.FileAudio;
|
||||
|
||||
case @".jpg":
|
||||
case @".jpeg":
|
||||
case @".png":
|
||||
return FontAwesome.Regular.FileImage;
|
||||
if (SupportedExtensions.IMAGE_EXTENSIONS.Contains(extension))
|
||||
return FontAwesome.Regular.FileImage;
|
||||
|
||||
default:
|
||||
return FontAwesome.Regular.File;
|
||||
}
|
||||
return FontAwesome.Regular.File;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,10 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.EditorAddBookmark),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.B }, GlobalAction.EditorRemoveClosestBookmark),
|
||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousBookmark),
|
||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextBookmark),
|
||||
};
|
||||
|
||||
private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[]
|
||||
@ -476,6 +480,18 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))]
|
||||
EditorCycleGridType,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorAddBookmark))]
|
||||
EditorAddBookmark,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorRemoveClosestBookmark))]
|
||||
EditorRemoveClosestBookmark,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousBookmark))]
|
||||
EditorSeekToPreviousBookmark,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextBookmark))]
|
||||
EditorSeekToNextBookmark,
|
||||
}
|
||||
|
||||
public enum GlobalActionCategory
|
||||
|
@ -9,21 +9,6 @@ namespace osu.Game.Localisation
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings";
|
||||
|
||||
/// <summary>
|
||||
/// "Debug"
|
||||
/// </summary>
|
||||
public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug");
|
||||
|
||||
/// <summary>
|
||||
/// "Show log overlay"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay");
|
||||
|
||||
/// <summary>
|
||||
/// "Bypass front-to-back render pass"
|
||||
/// </summary>
|
||||
public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass");
|
||||
|
||||
/// <summary>
|
||||
/// "Import files"
|
||||
/// </summary>
|
||||
@ -34,16 +19,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString RunLatencyCertifier => new TranslatableString(getKey(@"run_latency_certifier"), @"Run latency certifier");
|
||||
|
||||
/// <summary>
|
||||
/// "Memory"
|
||||
/// </summary>
|
||||
public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory");
|
||||
|
||||
/// <summary>
|
||||
/// "Clear all caches"
|
||||
/// </summary>
|
||||
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +198,21 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ClickToSelectBackground => new TranslatableString(getKey(@"click_to_select_background"), @"Click to select a background image");
|
||||
|
||||
/// <summary>
|
||||
/// "Apply this change to all difficulties?"
|
||||
/// </summary>
|
||||
public static LocalisableString ApplicationScopeSelectionTitle => new TranslatableString(getKey(@"application_scope_selection_title"), @"Apply this change to all difficulties?");
|
||||
|
||||
/// <summary>
|
||||
/// "Apply to all difficulties"
|
||||
/// </summary>
|
||||
public static LocalisableString ApplyToAllDifficulties => new TranslatableString(getKey(@"apply_to_all_difficulties"), @"Apply to all difficulties");
|
||||
|
||||
/// <summary>
|
||||
/// "Only apply to this difficulty"
|
||||
/// </summary>
|
||||
public static LocalisableString ApplyToThisDifficulty => new TranslatableString(getKey(@"apply_to_this_difficulty"), @"Only apply to this difficulty");
|
||||
|
||||
/// <summary>
|
||||
/// "Ruleset ({0})"
|
||||
/// </summary>
|
||||
|
@ -154,6 +154,36 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks");
|
||||
|
||||
/// <summary>
|
||||
/// "Bookmarks"
|
||||
/// </summary>
|
||||
public static LocalisableString Bookmarks => new TranslatableString(getKey(@"bookmarks"), @"Bookmarks");
|
||||
|
||||
/// <summary>
|
||||
/// "Add bookmark"
|
||||
/// </summary>
|
||||
public static LocalisableString AddBookmark => new TranslatableString(getKey(@"add_bookmark"), @"Add bookmark");
|
||||
|
||||
/// <summary>
|
||||
/// "Remove closest bookmark"
|
||||
/// </summary>
|
||||
public static LocalisableString RemoveClosestBookmark => new TranslatableString(getKey(@"remove_closest_bookmark"), @"Remove closest bookmark");
|
||||
|
||||
/// <summary>
|
||||
/// "Seek to previous bookmark"
|
||||
/// </summary>
|
||||
public static LocalisableString SeekToPreviousBookmark => new TranslatableString(getKey(@"seek_to_previous_bookmark"), @"Seek to previous bookmark");
|
||||
|
||||
/// <summary>
|
||||
/// "Seek to next bookmark"
|
||||
/// </summary>
|
||||
public static LocalisableString SeekToNextBookmark => new TranslatableString(getKey(@"seek_to_next_bookmark"), @"Seek to next bookmark");
|
||||
|
||||
/// <summary>
|
||||
/// "Reset bookmarks"
|
||||
/// </summary>
|
||||
public static LocalisableString ResetBookmarks => new TranslatableString(getKey(@"reset_bookmarks"), @"Reset bookmarks");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
}
|
@ -74,6 +74,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low");
|
||||
|
||||
/// <summary>
|
||||
/// "Star fountains"
|
||||
/// </summary>
|
||||
public static LocalisableString StarFountains => new TranslatableString(getKey(@"star_fountains"), @"Star fountains");
|
||||
|
||||
/// <summary>
|
||||
/// "Always show key overlay"
|
||||
/// </summary>
|
||||
@ -89,6 +94,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString AlwaysShowHoldForMenuButton => new TranslatableString(getKey(@"always_show_hold_for_menu_button"), @"Always show hold for menu button");
|
||||
|
||||
/// <summary>
|
||||
/// "Require holding key to pause gameplay"
|
||||
/// </summary>
|
||||
public static LocalisableString AlwaysRequireHoldForMenu => new TranslatableString(getKey(@"require_holding_key_to_pause_gameplay"), @"Require holding key to pause gameplay");
|
||||
|
||||
/// <summary>
|
||||
/// "Always play first combo break sound"
|
||||
/// </summary>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user