1
0
Fork 0
mirror of https://github.com/ppy/osu-tools.git synced 2025-06-07 23:07:01 +09:00

Merge branch 'master' into buyaspacecube/combined-profile-4-merge

This commit is contained in:
StanR 2025-03-17 01:37:39 +05:00
commit 534b01c409
49 changed files with 561 additions and 271 deletions

View file

@ -1,6 +1,17 @@
# EditorConfig is awesome: http://editorconfig.org
root = true
[*.{csproj,props,targets}]
charset = utf-8-bom
end_of_line = crlf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[g_*.cs]
generated_code = true
[*.cs]
end_of_line = crlf
insert_final_newline = true
@ -8,6 +19,9 @@ indent_style = space
indent_size = 4
trim_trailing_whitespace = true
#license header
file_header_template = Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text.
#Roslyn naming styles
#PascalCase for public and protected members
@ -121,7 +135,7 @@ dotnet_style_qualification_for_event = false:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
csharp_style_var_when_type_is_apparent = true:none
csharp_style_var_for_built_in_types = true:none
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_elsewhere = true:silent
#Style - modifiers
@ -165,7 +179,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:warning
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_deconstructed_variable_declaration = false:silent
#Style - other C# 7.x features
dotnet_style_prefer_inferred_tuple_names = true:warning
@ -176,19 +190,20 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_index_operator = false:silent
csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
# Suppress: EC112
csharp_style_namespace_declarations = block_scoped:warning
#Private method is unused
dotnet_diagnostic.IDE0051.severity = silent
#Private member is unused
dotnet_diagnostic.IDE0052.severity = silent
#Style - C# 12 features
csharp_style_prefer_primary_constructors = false
#Rules for disposable
dotnet_diagnostic.IDE0067.severity = none
dotnet_diagnostic.IDE0068.severity = none
dotnet_diagnostic.IDE0069.severity = none
[*.{yaml,yml}]
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
dotnet_diagnostic.OLOC001.words_in_name = 5
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

1
.gitattributes vendored
View file

@ -15,6 +15,7 @@ App.config text eol=crlf
*.cmd text eol=crlf
*.snippet text eol=crlf
*.manifest text eol=crlf
*.licenseheader text eol=crlf
# Check out with lf (UNIX) line endings
*.sh text eol=lf

113
.gitignore vendored
View file

@ -10,12 +10,8 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
### Cake ###
tools/**
build/tools/**
# Build results
bin/[Dd]ebug/
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
@ -102,7 +98,6 @@ $tf/
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
inspectcode
# JustCode is a .NET coding add-in
.JustCode
@ -196,6 +191,7 @@ ClientBin/
*.publishsettings
node_modules/
orleans.codegen.cs
Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
@ -251,21 +247,102 @@ paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/.idea.osu.Tools/.idea/*.xml
.idea/.idea.osu.Tools/.idea/codeStyles/*.xml
.idea/.idea.osu.Tools/.idea/dataSources/*.xml
.idea/.idea.osu.Tools/.idea/dictionaries/*.xml
.idea/.idea.osu.Tools/*.iml
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
Staging/
# Cake #
/tools/**
/build/tools/**
/build/temp/**
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/*/.idea/projectSettingsUpdater.xml
.idea/*/.idea/encodings.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# fastlane
fastlane/report.xml
# inspectcode
inspectcodereport.xml
inspectcode
# BenchmarkDotNet
/BenchmarkDotNet.Artifacts
*.GeneratedMSBuildEditorConfig.editorconfig
# Fody (pulled in by Realm) - schema file
FodyWeavers.xsd
.idea/.idea.osu.Desktop/.idea/misc.xml
.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml
PerformanceCalculator/cache/

View file

@ -0,0 +1,20 @@
M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> instead.
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead.
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: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.
M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead.

View 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

View file

@ -36,7 +36,7 @@ namespace PerformanceCalculator
protected T GetJsonFromApi<T>(string request, HttpMethod method = null, Dictionary<string, string> parameters = null)
{
using var req = new JsonWebRequest<T>($"{Program.ENDPOINT_CONFIGURATION.APIEndpointUrl}/api/v2/{request}");
using var req = new JsonWebRequest<T>($"{Program.ENDPOINT_CONFIGURATION.APIUrl}/api/v2/{request}");
req.Method = method ?? HttpMethod.Get;
req.AddHeader("x-api-version", api_version.ToString(CultureInfo.InvariantCulture));
req.AddHeader(System.Net.HttpRequestHeader.Authorization.ToString(), $"Bearer {apiAccessToken}");
@ -54,7 +54,7 @@ namespace PerformanceCalculator
private void getAccessToken()
{
using var req = new JsonWebRequest<dynamic>($"{Program.ENDPOINT_CONFIGURATION.APIEndpointUrl}/oauth/token")
using var req = new JsonWebRequest<dynamic>($"{Program.ENDPOINT_CONFIGURATION.APIUrl}/oauth/token")
{
Method = HttpMethod.Post
};

View file

@ -71,7 +71,7 @@ namespace PerformanceCalculator.Difficulty
{
var document = new Document();
foreach (var error in resultSet.Errors)
foreach (string error in resultSet.Errors)
document.Children.Add(new Span(error), "\n");
if (resultSet.Errors.Count > 0)
document.Children.Add("\n");

View file

@ -67,7 +67,7 @@ namespace PerformanceCalculator.Difficulty
{
var document = new Document();
foreach (var error in resultSet.Errors)
foreach (string error in resultSet.Errors)
document.Children.Add(new Span(error), "\n");
if (resultSet.Errors.Count > 0)
document.Children.Add("\n");
@ -85,7 +85,7 @@ namespace PerformanceCalculator.Difficulty
// Headers
if (firstResult)
{
foreach (var column in new[] { "Beatmap", "Mods", "Accuracy score", "Combo score", "Bonus score ratio", "Mod multiplier" })
foreach (string column in new[] { "Beatmap", "Mods", "Accuracy score", "Combo score", "Bonus score ratio", "Mod multiplier" })
{
grid.Columns.Add(GridLength.Auto);
grid.Children.Add(new Cell(column));
@ -121,7 +121,7 @@ namespace PerformanceCalculator.Difficulty
var attributes = simulator.Simulate(beatmap, playableBeatmap);
var conversionInfo = LegacyBeatmapConversionDifficultyInfo.FromBeatmap(playableBeatmap);
var modMultiplier = simulator.GetLegacyScoreMultiplier(mods, conversionInfo);
double modMultiplier = simulator.GetLegacyScoreMultiplier(mods, conversionInfo);
return new Result
{
@ -142,9 +142,9 @@ namespace PerformanceCalculator.Difficulty
var availableMods = ruleset.CreateAllMods().ToList();
foreach (var modString in Mods)
foreach (string modString in Mods)
{
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.CurrentCultureIgnoreCase));
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.OrdinalIgnoreCase));
if (newMod == null)
throw new ArgumentException($"Invalid mod provided: {modString}");

View file

@ -97,9 +97,9 @@ namespace PerformanceCalculator.Difficulty
var availableMods = ruleset.CreateAllMods().ToList();
var mods = new List<Mod>();
foreach (var modString in Mods)
foreach (string modString in Mods)
{
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.CurrentCultureIgnoreCase));
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.OrdinalIgnoreCase));
if (newMod == null)
throw new ArgumentException($"Invalid mod provided: {modString}");

View file

@ -7,10 +7,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Humanizer;
using McMaster.Extensions.CommandLineUtils;
using Newtonsoft.Json;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -66,7 +66,7 @@ namespace PerformanceCalculator.Difficulty
foreach (var (settingsSource, propertyInfo) in sourceProperties)
{
var bindable = propertyInfo.GetValue(mod);
object? bindable = propertyInfo.GetValue(mod);
Debug.Assert(bindable != null);
@ -75,7 +75,7 @@ namespace PerformanceCalculator.Difficulty
yield return new
{
Name = propertyInfo.Name.Underscore(),
Name = propertyInfo.Name.ToSnakeCase(),
Type = getJsonType(netType),
Label = settingsSource.Label.ToString(),
Description = settingsSource.Description.ToString(),

View file

@ -33,7 +33,7 @@ namespace PerformanceCalculator.Leaderboard
public override void Execute()
{
var rulesetApiName = LegacyHelper.GetRulesetShortNameFromId(Ruleset ?? 0);
string rulesetApiName = LegacyHelper.GetRulesetShortNameFromId(Ruleset ?? 0);
var leaderboard = GetJsonFromApi<GetTopUsersResponse>($"rankings/{rulesetApiName}/performance?cursor[page]={LeaderboardPage - 1}");
var calculatedPlayers = new List<LeaderboardPlayerInfo>();
@ -78,7 +78,7 @@ namespace PerformanceCalculator.Leaderboard
double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play);
//todo: implement properly. this is pretty damn wrong.
var playcountBonusPP = (totalLivePP - nonBonusLivePP);
double playcountBonusPP = (totalLivePP - nonBonusLivePP);
totalLocalPP += playcountBonusPP;
calculatedPlayers.Add(new LeaderboardPlayerInfo
@ -94,7 +94,7 @@ namespace PerformanceCalculator.Leaderboard
if (OutputJson)
{
var json = JsonConvert.SerializeObject(calculatedPlayers);
string json = JsonConvert.SerializeObject(calculatedPlayers);
Console.Write(json);
}

View file

@ -8,10 +8,10 @@
<ItemGroup>
<PackageReference Include="Alba.CsConsoleFormat" Version="1.0.0" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="ppy.osu.Game" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.227.0" />
</ItemGroup>
</Project>

View file

@ -88,7 +88,7 @@ namespace PerformanceCalculator
var ppAttributeValues = JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject(result.PerformanceAttributes)) ?? new Dictionary<string, object>();
foreach (var attrib in ppAttributeValues)
document.Children.Add(FormatDocumentLine(attrib.Key.Humanize().ToLower(), FormattableString.Invariant($"{attrib.Value:N2}")));
document.Children.Add(FormatDocumentLine(attrib.Key.Humanize().ToLower(CultureInfo.InvariantCulture), FormattableString.Invariant($"{attrib.Value:N2}")));
AddSectionHeader(document, "Difficulty attributes");
@ -118,9 +118,9 @@ namespace PerformanceCalculator
{
ConsoleRenderer.RenderDocumentToText(document, new TextRenderTarget(writer), new Rect(0, 0, 250, Size.Infinity));
var str = writer.GetStringBuilder().ToString();
string str = writer.GetStringBuilder().ToString();
var lines = str.Split('\n');
string[] lines = str.Split('\n');
for (int i = 0; i < lines.Length; i++)
lines[i] = lines[i].TrimEnd();
str = string.Join('\n', lines);
@ -143,7 +143,7 @@ namespace PerformanceCalculator
var mods = new List<Mod>();
foreach (var acronym in acronyms)
foreach (string acronym in acronyms)
{
APIMod mod = new APIMod { Acronym = acronym };

View file

@ -58,7 +58,7 @@ namespace PerformanceCalculator
return new ProcessorWorkingBeatmap(fileOrId);
}
if (!int.TryParse(fileOrId, out var beatmapId))
if (!int.TryParse(fileOrId, out int beatmapId))
throw new ArgumentException("Could not parse provided beatmap ID.");
string cachePath = Path.Combine("cache", $"{beatmapId}.osu");
@ -66,7 +66,7 @@ namespace PerformanceCalculator
if (!File.Exists(cachePath))
{
Console.WriteLine($"Downloading {beatmapId}.osu...");
new FileWebRequest(cachePath, $"{Program.ENDPOINT_CONFIGURATION.WebsiteRootUrl}/osu/{beatmapId}").Perform();
new FileWebRequest(cachePath, $"{Program.ENDPOINT_CONFIGURATION.WebsiteUrl}/osu/{beatmapId}").Perform();
}
return new ProcessorWorkingBeatmap(cachePath, beatmapId);

View file

@ -32,7 +32,7 @@ namespace PerformanceCalculator.Profile
var displayPlays = new List<UserPlayInfo>();
var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? 0);
var rulesetApiName = LegacyHelper.GetRulesetShortNameFromId(Ruleset ?? 0);
string rulesetApiName = LegacyHelper.GetRulesetShortNameFromId(Ruleset ?? 0);
Console.WriteLine("Getting user data...");
var userData = GetJsonFromApi<APIUser>($"users/{ProfileName}/{ruleset.ShortName}");
@ -81,15 +81,15 @@ namespace PerformanceCalculator.Profile
double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play.LivePP);
//todo: implement properly. this is pretty damn wrong.
var playcountBonusPP = (totalLivePP - nonBonusLivePP);
double playcountBonusPP = (totalLivePP - nonBonusLivePP);
totalLocalPP += playcountBonusPP;
double totalDiffPP = totalLocalPP - totalLivePP;
if (OutputJson)
{
var json = JsonConvert.SerializeObject(new
string json = JsonConvert.SerializeObject(new
{
Username = userData.Username,
userData.Username,
LivePp = totalLivePP,
LocalPp = totalLocalPP,
PlaycountPp = playcountBonusPP,

View file

@ -40,7 +40,7 @@ namespace PerformanceCalculator.Simulate
private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, double accuracy, int countMiss, int? countMeh, int? countGood)
{
var maxCombo = beatmap.GetMaxCombo();
int maxCombo = beatmap.GetMaxCombo();
int maxTinyDroplets = beatmap.HitObjects.OfType<JuiceStream>().Sum(s => s.NestedHitObjects.OfType<TinyDroplet>().Count());
int maxDroplets = beatmap.HitObjects.OfType<JuiceStream>().Sum(s => s.NestedHitObjects.OfType<Droplet>().Count()) - maxTinyDroplets;
int maxFruits = beatmap.HitObjects.Sum(h => h is Fruit ? 1 : (h as JuiceStream)?.NestedHitObjects.Count(n => n is Fruit) ?? 0);

View file

@ -41,7 +41,7 @@ namespace PerformanceCalculator.Simulate
private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, double accuracy, int countMiss, int? countMeh, int? countOk, int? countGood, int? countGreat)
{
// One judgement per normal note. Two judgements per hold note (head + tail).
var totalHits = beatmap.HitObjects.Count + beatmap.HitObjects.Count(ho => ho is HoldNote);
int totalHits = beatmap.HitObjects.Count + beatmap.HitObjects.Count(ho => ho is HoldNote);
if (countMeh != null || countOk != null || countGood != null || countGreat != null)
{
@ -59,7 +59,7 @@ namespace PerformanceCalculator.Simulate
}
// Let Great=Perfect=6, Good=4, Ok=2, Meh=1, Miss=0. The total should be this.
var targetTotal = (int)Math.Round(accuracy * totalHits * 6);
int targetTotal = (int)Math.Round(accuracy * totalHits * 6);
// Start by assuming every non miss is a meh
// This is how much increase is needed by the rest

View file

@ -62,7 +62,7 @@ namespace PerformanceCalculator.Simulate
{
int countGreat;
var totalResultCount = beatmap.HitObjects.Count;
int totalResultCount = beatmap.HitObjects.Count;
if (countMeh != null || countGood != null)
{
@ -153,27 +153,26 @@ namespace PerformanceCalculator.Simulate
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics)
{
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Ok];
var countMeh = statistics[HitResult.Meh];
var countMiss = statistics[HitResult.Miss];
int countGreat = statistics[HitResult.Great];
int countGood = statistics[HitResult.Ok];
int countMeh = statistics[HitResult.Meh];
int countMiss = statistics[HitResult.Miss];
double total = 6 * countGreat + 2 * countGood + countMeh;
double max = 6 * (countGreat + countGood + countMeh + countMiss);
if (statistics.ContainsKey(HitResult.SliderTailHit))
if (statistics.TryGetValue(HitResult.SliderTailHit, out int countSliderTailHit))
{
var countSliders = beatmap.HitObjects.Count(x => x is Slider);
var countSliderTailHit = statistics[HitResult.SliderTailHit];
int countSliders = beatmap.HitObjects.Count(x => x is Slider);
total += 3 * countSliderTailHit;
max += 3 * countSliders;
}
if (statistics.ContainsKey(HitResult.LargeTickMiss))
if (statistics.TryGetValue(HitResult.LargeTickMiss, out int countLargeTickMiss))
{
var countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat));
var countLargeTickHit = countLargeTicks - statistics[HitResult.LargeTickMiss];
int countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat));
int countLargeTickHit = countLargeTicks - countLargeTickMiss;
total += 0.6 * countLargeTickHit;
max += 0.6 * countLargeTicks;

View file

@ -66,7 +66,7 @@ namespace PerformanceCalculator.Simulate
var mods = ParseMods(ruleset, Mods, ModOptions);
var beatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
var beatmapMaxCombo = beatmap.GetMaxCombo();
int beatmapMaxCombo = beatmap.GetMaxCombo();
var statistics = GenerateHitResults(beatmap, mods);
var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo)
{

View file

@ -34,7 +34,7 @@ namespace PerformanceCalculator.Simulate
private static Dictionary<HitResult, int> generateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countGood)
{
var totalResultCount = beatmap.GetMaxCombo();
int totalResultCount = beatmap.GetMaxCombo();
int countGreat;
@ -45,7 +45,7 @@ namespace PerformanceCalculator.Simulate
else
{
// Let Great=2, Good=1, Miss=0. The total should be this.
var targetTotal = (int)Math.Round(accuracy * totalResultCount * 2);
int targetTotal = (int)Math.Round(accuracy * totalResultCount * 2);
countGreat = targetTotal - (totalResultCount - countMiss);
countGood = totalResultCount - countGreat - countMiss;
@ -62,10 +62,10 @@ namespace PerformanceCalculator.Simulate
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics)
{
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Ok];
var countMiss = statistics[HitResult.Miss];
var total = countGreat + countGood + countMiss;
int countGreat = statistics[HitResult.Great];
int countGood = statistics[HitResult.Ok];
int countMiss = statistics[HitResult.Miss];
int total = countGreat + countGood + countMiss;
return (double)((2 * countGreat) + countGood) / (2 * total);
}

View file

@ -35,21 +35,21 @@ namespace PerformanceCalculatorGUI
{
if (token == null)
{
await getAccessToken();
await getAccessToken().ConfigureAwait(false);
Debug.Assert(token != null);
}
using var req = new JsonWebRequest<T>($"{ENDPOINT_CONFIGURATION.APIEndpointUrl}/api/v2/{request}");
using var req = new JsonWebRequest<T>($"{ENDPOINT_CONFIGURATION.APIUrl}/api/v2/{request}");
req.AddHeader("x-api-version", api_version.ToString(CultureInfo.InvariantCulture));
req.AddHeader(System.Net.HttpRequestHeader.Authorization.ToString(), $"Bearer {token.AccessToken}");
await req.PerformAsync();
await req.PerformAsync().ConfigureAwait(false);
return req.ResponseObject;
}
private async Task getAccessToken()
{
using var req = new JsonWebRequest<OAuthToken>($"{ENDPOINT_CONFIGURATION.APIEndpointUrl}/oauth/token")
using var req = new JsonWebRequest<OAuthToken>($"{ENDPOINT_CONFIGURATION.APIUrl}/oauth/token")
{
Method = HttpMethod.Post
};
@ -58,7 +58,7 @@ namespace PerformanceCalculatorGUI
req.AddParameter("client_secret", clientSecretBindable.Value);
req.AddParameter("grant_type", "client_credentials");
req.AddParameter("scope", "public");
await req.PerformAsync();
await req.PerformAsync().ConfigureAwait(false);
token = req.ResponseObject;
}

View file

@ -60,9 +60,9 @@ namespace PerformanceCalculatorGUI.Components
currentData = data;
var split = data.Split('\n');
string[] split = data.Split('\n');
foreach (var line in split)
foreach (string line in split)
textContainer.Add(new OsuSpriteText { Text = line });
}

View file

@ -141,7 +141,7 @@ namespace PerformanceCalculatorGUI.Components
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = colourProvider.Light1,
Text = Score.PositionChange.Value.ToString()
Text = $"{Score.PositionChange.Value:+0;-0;-}"
}
},
new Container

View file

@ -78,7 +78,7 @@ namespace PerformanceCalculatorGUI.Components
protected sealed override CountryCode GetCountryCode(LeaderboardUser item) => item.User.CountryCode;
protected sealed override Drawable CreateFlagContent(LeaderboardUser item)
protected sealed override Drawable[] CreateFlagContent(LeaderboardUser item)
{
var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE, italics: true))
{
@ -87,7 +87,7 @@ namespace PerformanceCalculatorGUI.Components
TextAnchor = Anchor.CentreLeft
};
username.AddUserLink(item.User);
return username;
return [username];
}
private partial class DifferenceText : OsuSpriteText

View file

@ -5,14 +5,15 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables.Cards;
namespace PerformanceCalculatorGUI.Components;
public partial class ScreenSelectionButtonIcon : IconPill
namespace PerformanceCalculatorGUI.Components
{
public ScreenSelectionButtonIcon(IconUsage? icon = null)
: base(icon ?? FontAwesome.Solid.List)
public partial class ScreenSelectionButtonIcon : IconPill
{
}
public ScreenSelectionButtonIcon(IconUsage? icon = null)
: base(icon ?? FontAwesome.Solid.List)
{
}
public override LocalisableString TooltipText => string.Empty;
public override LocalisableString TooltipText => string.Empty;
}
}

View file

@ -76,7 +76,7 @@ namespace PerformanceCalculatorGUI.Components
{
// this is ugly, but it works
var graphToggleBindable = new Bindable<bool>();
var graphNum = i;
int graphNum = i;
graphToggleBindable.BindValueChanged(state =>
{
if (state.NewValue)
@ -111,7 +111,7 @@ namespace PerformanceCalculatorGUI.Components
Width = 200,
Current = { BindTarget = graphToggleBindable, Default = true, Value = true },
LabelText = skills[i].GetType().Name,
TextColour = skillColours[i]
TextColour = skillColours[i % skillColours.Length]
}
}
});
@ -186,7 +186,7 @@ namespace PerformanceCalculatorGUI.Components
private void addStrainBars(Skill[] skills, List<float[]> strainLists)
{
var strainMaxValue = strainLists.Max(list => list.Max());
float strainMaxValue = strainLists.Max(list => list.Max());
for (int i = 0; i < skills.Length; i++)
{
@ -196,7 +196,7 @@ namespace PerformanceCalculatorGUI.Components
{
RelativeSizeAxes = Axes.Both,
Alpha = graphAlpha,
Colour = skillColours[i],
Colour = skillColours[i % skillColours.Length],
Child = new StrainBarGraph
{
RelativeSizeAxes = Axes.Both,
@ -223,7 +223,7 @@ namespace PerformanceCalculatorGUI.Components
for (int i = 0; i < nBars; i++)
{
var strainTime = TimeSpan.FromMilliseconds(TimeUntilFirstStrain.Value + lastStrainTime * i / nBars);
var tooltipText = $"~{strainTime:mm\\:ss\\.ff}";
string tooltipText = $"~{strainTime:mm\\:ss\\.ff}";
tooltipList.Add(tooltipText);
}
@ -248,13 +248,13 @@ namespace PerformanceCalculatorGUI.Components
foreach (var skill in skills)
{
var strains = ((StrainSkill)skill).GetCurrentStrainPeaks().ToArray();
double[] strains = ((StrainSkill)skill).GetCurrentStrainPeaks().ToArray();
var skillStrainList = new List<float>();
for (int i = 0; i < strains.Length; i++)
{
var strain = strains[i];
double strain = strains[i];
skillStrainList.Add(((float)strain));
}
@ -281,7 +281,7 @@ namespace PerformanceCalculatorGUI.Components
{
Clear();
foreach (var val in value)
foreach (float val in value)
{
float length = MaxValue ?? value.Max();
if (length != 0)
@ -324,7 +324,7 @@ namespace PerformanceCalculatorGUI.Components
{
Clear();
foreach (var tooltip in value)
foreach (string tooltip in value)
{
float size = value.Count();
if (size != 0)

View file

@ -11,15 +11,13 @@ namespace PerformanceCalculatorGUI.Components.TextBoxes
{
private partial class FractionalNumberBox : OsuTextBox
{
protected override bool AllowIme => false;
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character) || character == CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
protected override void OnUserTextAdded(string added)
{
base.OnUserTextAdded(added);
var textToParse = Text;
string textToParse = Text;
if (string.IsNullOrEmpty(Text))
{
@ -41,7 +39,7 @@ namespace PerformanceCalculatorGUI.Components.TextBoxes
protected override void OnUserTextRemoved(string removed)
{
var textToParse = Text;
string textToParse = Text;
if (string.IsNullOrEmpty(Text))
{

View file

@ -15,7 +15,7 @@ namespace PerformanceCalculatorGUI.Components.TextBoxes
{
base.OnUserTextAdded(added);
var textToParse = Text;
string textToParse = Text;
if (string.IsNullOrEmpty(Text))
{
@ -37,7 +37,7 @@ namespace PerformanceCalculatorGUI.Components.TextBoxes
protected override void OnUserTextRemoved(string removed)
{
var textToParse = Text;
string textToParse = Text;
if (string.IsNullOrEmpty(Text))
{

View file

@ -62,12 +62,6 @@ namespace PerformanceCalculatorGUI.Components
Action = () => { host.OpenUrlExternally($"https://osu.ppy.sh/u/{User.Id}"); };
}
protected override void LoadComplete()
{
Status.UnbindAll();
Activity.UnbindAll();
}
protected override Drawable CreateLayout()
{
var layout = new Container
@ -163,7 +157,14 @@ namespace PerformanceCalculatorGUI.Components
}
}
}
}
},
// this is like COMPLETELY screwed but is required to have this not die of death because `ExtendedUserPanel` expects `LastVisitMessage` to not be null
// and that is created and assigned to by `CreateStatusMessage()`.
// the card API is SHOCKING and needs to be taken behind the barn and old yeller'd five years ago.
// it is NOT A GOOD IDEA to have A PROTECTED OVERRIDABLE METHOD THAT CAN MAKE READING OTHER PROTECTED MEMBERS DANGEROUS!!!!
// or that A PROTECTED METHOD which you can FORGET TO CALL should be RESPONSIBLE FOR SETTING UP A PROTECTED FIELD!!!!
CreateStatusMessage(false).With(wtf => wtf.Alpha = 0),
CreateStatusIcon().With(wtf => wtf.Alpha = 0),
}
};

View file

@ -10,23 +10,24 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
namespace PerformanceCalculatorGUI;
public class ExtendedCatchDifficultyCalculator : CatchDifficultyCalculator, IExtendedDifficultyCalculator
namespace PerformanceCalculatorGUI
{
private Skill[] skills;
public ExtendedCatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
public class ExtendedCatchDifficultyCalculator : CatchDifficultyCalculator, IExtendedDifficultyCalculator
{
}
private Skill[] skills;
public Skill[] GetSkills() => skills;
public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray();
public ExtendedCatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
this.skills = skills;
return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
public Skill[] GetSkills() => skills;
public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray();
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
this.skills = skills;
return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
}
}
}

View file

@ -10,23 +10,24 @@ using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty;
namespace PerformanceCalculatorGUI;
public class ExtendedOsuDifficultyCalculator : OsuDifficultyCalculator, IExtendedDifficultyCalculator
namespace PerformanceCalculatorGUI
{
private Skill[] skills;
public ExtendedOsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
public class ExtendedOsuDifficultyCalculator : OsuDifficultyCalculator, IExtendedDifficultyCalculator
{
}
private Skill[] skills;
public Skill[] GetSkills() => skills;
public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray();
public ExtendedOsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
this.skills = skills;
return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
public Skill[] GetSkills() => skills;
public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray();
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
this.skills = skills;
return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
}
}
}

View file

@ -10,23 +10,24 @@ using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty;
namespace PerformanceCalculatorGUI;
public class ExtendedTaikoDifficultyCalculator : TaikoDifficultyCalculator, IExtendedDifficultyCalculator
namespace PerformanceCalculatorGUI
{
private Skill[] skills;
public ExtendedTaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
public class ExtendedTaikoDifficultyCalculator : TaikoDifficultyCalculator, IExtendedDifficultyCalculator
{
}
private Skill[] skills;
public Skill[] GetSkills() => skills;
public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray();
public ExtendedTaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
this.skills = skills;
return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
public Skill[] GetSkills() => skills;
public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray();
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
this.skills = skills;
return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
}
}
}

View file

@ -5,10 +5,11 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
namespace PerformanceCalculatorGUI;
public interface IExtendedDifficultyCalculator
namespace PerformanceCalculatorGUI
{
Skill[] GetSkills();
DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate);
public interface IExtendedDifficultyCalculator
{
Skill[] GetSkills();
DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate);
}
}

View file

@ -6,10 +6,10 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2024.1023.0" />
<PackageReference Include="ppy.osu.Game" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.227.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.227.0" />
</ItemGroup>
</Project>

View file

@ -66,7 +66,7 @@ namespace PerformanceCalculatorGUI
return new ProcessorWorkingBeatmap(fileOrId, null, audioManager);
}
if (!int.TryParse(fileOrId, out var beatmapId))
if (!int.TryParse(fileOrId, out int beatmapId))
throw new ArgumentException("Could not parse provided beatmap ID.");
cachePath = Path.Combine(cachePath, $"{beatmapId}.osu");
@ -77,7 +77,7 @@ namespace PerformanceCalculatorGUI
try
{
new FileWebRequest(cachePath, $"{APIManager.ENDPOINT_CONFIGURATION.WebsiteRootUrl}/osu/{beatmapId}").Perform();
new FileWebRequest(cachePath, $"{APIManager.ENDPOINT_CONFIGURATION.WebsiteUrl}/osu/{beatmapId}").Perform();
}
catch (WebException)
{

View file

@ -3,10 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
@ -19,7 +16,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Skinning;
namespace PerformanceCalculatorGUI
{
@ -81,7 +77,7 @@ namespace PerformanceCalculatorGUI
{
int countGreat;
var totalResultCount = beatmap.HitObjects.Count;
int totalResultCount = beatmap.HitObjects.Count;
if (countMeh != null || countGood != null)
{
@ -172,7 +168,7 @@ namespace PerformanceCalculatorGUI
private static Dictionary<HitResult, int> generateTaikoHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countGood)
{
var totalResultCount = beatmap.HitObjects.OfType<Hit>().Count();
int totalResultCount = beatmap.HitObjects.OfType<Hit>().Count();
int countGreat;
@ -183,7 +179,7 @@ namespace PerformanceCalculatorGUI
else
{
// Let Great=2, Good=1, Miss=0. The total should be this.
var targetTotal = (int)Math.Round(accuracy * totalResultCount * 2);
int targetTotal = (int)Math.Round(accuracy * totalResultCount * 2);
countGreat = targetTotal - (totalResultCount - countMiss);
countGood = totalResultCount - countGreat - countMiss;
@ -200,7 +196,7 @@ namespace PerformanceCalculatorGUI
private static Dictionary<HitResult, int> generateCatchHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood)
{
var maxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet));
int maxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet));
int maxTinyDroplets = beatmap.HitObjects.OfType<JuiceStream>().Sum(s => s.NestedHitObjects.OfType<TinyDroplet>().Count());
int maxDroplets = beatmap.HitObjects.OfType<JuiceStream>().Sum(s => s.NestedHitObjects.OfType<Droplet>().Count()) - maxTinyDroplets;
@ -231,14 +227,14 @@ namespace PerformanceCalculatorGUI
private static Dictionary<HitResult, int> generateManiaHitResults(double accuracy, IBeatmap beatmap, int countMiss)
{
var totalResultCount = beatmap.HitObjects.Count;
int totalResultCount = beatmap.HitObjects.Count;
// Let Great=6, Good=2, Meh=1, Miss=0. The total should be this.
var targetTotal = (int)Math.Round(accuracy * totalResultCount * 6);
int targetTotal = (int)Math.Round(accuracy * totalResultCount * 6);
// Start by assuming every non miss is a meh
// This is how much increase is needed by greats and goods
var delta = targetTotal - (totalResultCount - countMiss);
int delta = targetTotal - (totalResultCount - countMiss);
// Each great increases total by 5 (great-meh=5)
int countGreat = delta / 5;
@ -272,27 +268,26 @@ namespace PerformanceCalculatorGUI
private static double getOsuAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics)
{
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Ok];
var countMeh = statistics[HitResult.Meh];
var countMiss = statistics[HitResult.Miss];
int countGreat = statistics[HitResult.Great];
int countGood = statistics[HitResult.Ok];
int countMeh = statistics[HitResult.Meh];
int countMiss = statistics[HitResult.Miss];
double total = 6 * countGreat + 2 * countGood + countMeh;
double max = 6 * (countGreat + countGood + countMeh + countMiss);
if (statistics.ContainsKey(HitResult.SliderTailHit))
if (statistics.TryGetValue(HitResult.SliderTailHit, out int countSliderTailHit))
{
var countSliders = beatmap.HitObjects.Count(x => x is Slider);
var countSliderTailHit = statistics[HitResult.SliderTailHit];
int countSliders = beatmap.HitObjects.Count(x => x is Slider);
total += 3 * countSliderTailHit;
max += 3 * countSliders;
}
if (statistics.ContainsKey(HitResult.LargeTickMiss))
if (statistics.TryGetValue(HitResult.LargeTickMiss, out int countLargeTicksMiss))
{
var countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat));
var countLargeTickHit = countLargeTicks - statistics[HitResult.LargeTickMiss];
int countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat));
int countLargeTickHit = countLargeTicks - countLargeTicksMiss;
total += 0.6 * countLargeTickHit;
max += 0.6 * countLargeTicks;
@ -303,10 +298,10 @@ namespace PerformanceCalculatorGUI
private static double getTaikoAccuracy(Dictionary<HitResult, int> statistics)
{
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Ok];
var countMiss = statistics[HitResult.Miss];
var total = countGreat + countGood + countMiss;
int countGreat = statistics[HitResult.Great];
int countGood = statistics[HitResult.Ok];
int countMiss = statistics[HitResult.Miss];
int total = countGreat + countGood + countMiss;
return (double)((2 * countGreat) + countGood) / (2 * total);
}
@ -321,35 +316,17 @@ namespace PerformanceCalculatorGUI
private static double getManiaAccuracy(Dictionary<HitResult, int> statistics)
{
var countPerfect = statistics[HitResult.Perfect];
var countGreat = statistics[HitResult.Great];
var countGood = statistics[HitResult.Good];
var countOk = statistics[HitResult.Ok];
var countMeh = statistics[HitResult.Meh];
var countMiss = statistics[HitResult.Miss];
var total = countPerfect + countGreat + countGood + countOk + countMeh + countMiss;
int countPerfect = statistics[HitResult.Perfect];
int countGreat = statistics[HitResult.Great];
int countGood = statistics[HitResult.Good];
int countOk = statistics[HitResult.Ok];
int countMeh = statistics[HitResult.Meh];
int countMiss = statistics[HitResult.Miss];
int total = countPerfect + countGreat + countGood + countOk + countMeh + countMiss;
return (double)
((6 * (countPerfect + countGreat)) + (4 * countGood) + (2 * countOk) + countMeh) /
(6 * total);
}
private class EmptyWorkingBeatmap : WorkingBeatmap
{
public EmptyWorkingBeatmap()
: base(new BeatmapInfo(), null)
{
}
protected override IBeatmap GetBeatmap() => throw new NotImplementedException();
public override Texture GetBackground() => throw new NotImplementedException();
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
protected override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
}

View file

@ -196,7 +196,7 @@ namespace PerformanceCalculatorGUI.Screens
{
Schedule(() => loadingLayer.Text.Value = "Getting leaderboard...");
var leaderboard = await apiManager.GetJsonFromApi<APIScoresCollection>($@"beatmaps/{beatmapIdTextBox.Current.Value}/scores?scope=global&mode={ruleset.Value.ShortName}");
var leaderboard = await apiManager.GetJsonFromApi<APIScoresCollection>($@"beatmaps/{beatmapIdTextBox.Current.Value}/scores?scope=global&mode={ruleset.Value.ShortName}").ConfigureAwait(false);
var plays = new List<SoloScoreInfo>();
@ -236,9 +236,11 @@ namespace PerformanceCalculatorGUI.Screens
var difficultyAttributes = difficultyCalculator.Calculate(mods);
var performanceCalculator = rulesetInstance.CreatePerformanceCalculator();
if (performanceCalculator == null)
continue;
var perfAttributes = await performanceCalculator?.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token)!;
score.PP = perfAttributes?.Total ?? 0.0;
var perfAttributes = await performanceCalculator.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token).ConfigureAwait(false);
score.PP = perfAttributes.Total;
plays.Add(score);
}

View file

@ -239,7 +239,7 @@ namespace PerformanceCalculatorGUI.Screens
{
Schedule(() => loadingLayer.Text.Value = "Getting leaderboard...");
var leaderboard = await apiManager.GetJsonFromApi<GetTopUsersResponse>($"rankings/{ruleset.Value.ShortName}/performance?cursor[page]={pageTextBox.Value.Value - 1}");
var leaderboard = await apiManager.GetJsonFromApi<GetTopUsersResponse>($"rankings/{ruleset.Value.ShortName}/performance?cursor[page]={pageTextBox.Value.Value - 1}").ConfigureAwait(false);
var calculatedPlayers = new List<LeaderboardUser>();
var calculatedScores = new List<ExtendedScore>();
@ -253,7 +253,7 @@ namespace PerformanceCalculatorGUI.Screens
Schedule(() => loadingLayer.Text.Value = $"Calculating {player.User.Username} top scores...");
var playerData = await calculatePlayer(player, token);
var playerData = await calculatePlayer(player, token).ConfigureAwait(false);
calculatedPlayers.Add(new LeaderboardUser
{
@ -301,7 +301,7 @@ namespace PerformanceCalculatorGUI.Screens
var plays = new List<ExtendedScore>();
var apiScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.User.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100");
var apiScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.User.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100").ConfigureAwait(false);
var rulesetInstance = ruleset.Value.CreateInstance();
@ -323,7 +323,7 @@ namespace PerformanceCalculatorGUI.Screens
var difficultyAttributes = difficultyCalculator.Calculate(mods);
var performanceCalculator = rulesetInstance.CreatePerformanceCalculator();
var livePp = score.PP ?? 0.0;
double? livePp = score.PP;
var perfAttributes = performanceCalculator?.Calculate(parsedScore.ScoreInfo, difficultyAttributes);
score.PP = perfAttributes?.Total ?? 0.0;
@ -346,17 +346,17 @@ namespace PerformanceCalculatorGUI.Screens
catch (OperationCanceledException) { }
var localOrdered = plays.OrderByDescending(x => x.SoloScore.PP).ToList();
var liveOrdered = plays.OrderByDescending(x => x.LivePP).ToList();
var liveOrdered = plays.OrderByDescending(x => x.LivePP ?? 0.0).ToList();
int index = 0;
decimal totalLocalPP = (decimal)(localOrdered.Select(x => x.SoloScore.PP).Sum(play => Math.Pow(0.95, index++) * play) ?? 0.0);
decimal totalLivePP = player.PP ?? (decimal)0.0;
index = 0;
decimal nonBonusLivePP = (decimal)liveOrdered.Select(x => x.LivePP).Sum(play => Math.Pow(0.95, index++) * play);
decimal nonBonusLivePP = (decimal)liveOrdered.Select(x => x.LivePP ?? 0.0).Sum(play => Math.Pow(0.95, index++) * play);
//todo: implement properly. this is pretty damn wrong.
var playcountBonusPP = (totalLivePP - nonBonusLivePP);
decimal playcountBonusPP = (totalLivePP - nonBonusLivePP);
totalLocalPP += playcountBonusPP;
return new UserLeaderboardData

View file

@ -4,12 +4,14 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing;
@ -17,8 +19,10 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
namespace PerformanceCalculatorGUI.Screens.ObjectInspection
@ -28,6 +32,9 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
[Resolved]
private Bindable<IReadOnlyList<Mod>> appliedMods { get; set; }
[Resolved]
private Track track { get; set; }
private SpriteText hitObjectTypeText;
private FillFlowContainer flowContainer;
@ -72,7 +79,7 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
Colour = colors.Background6,
RelativeSizeAxes = Axes.Both
},
hitObjectTypeText = new SpriteText
hitObjectTypeText = new OsuSpriteText
{
Font = new FontUsage(size: 30),
Padding = new MarginPadding(10)
@ -120,25 +127,26 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
private void drawOsuValues(OsuDifficultyHitObject hitObject)
{
var hidden = appliedMods.Value.Any(x => x is ModHidden);
bool hidden = appliedMods.Value.Any(x => x is ModHidden);
flowContainer.AddRange(new[]
{
new ObjectInspectorDifficultyValue("Position", (hitObject.BaseObject as OsuHitObject)!.StackedPosition),
new ObjectInspectorDifficultyValue("Strain Time", hitObject.StrainTime),
new ObjectInspectorDifficultyValue("Doubletapness", hitObject.GetDoubletapness((OsuDifficultyHitObject)hitObject.Next(0))),
new ObjectInspectorDifficultyValue("Lazy Jump Dist", hitObject.LazyJumpDistance),
new ObjectInspectorDifficultyValue("Min Jump Dist", hitObject.MinimumJumpDistance),
new ObjectInspectorDifficultyValue("Min Jump Time", hitObject.MinimumJumpTime),
new ObjectInspectorDifficultyValue("Aim Difficulty", AimEvaluator.EvaluateDifficultyOf(hitObject, true)),
new ObjectInspectorDifficultyValue("Aim Difficulty (w/o sliders)", AimEvaluator.EvaluateDifficultyOf(hitObject, false)),
new ObjectInspectorDifficultyValue("Speed Difficulty", SpeedEvaluator.EvaluateDifficultyOf(hitObject)),
new ObjectInspectorDifficultyValue("Rhythm Diff", RhythmEvaluator.EvaluateDifficultyOf(hitObject)),
new ObjectInspectorDifficultyValue("Speed Difficulty", SpeedEvaluator.EvaluateDifficultyOf(hitObject, appliedMods.Value)),
new ObjectInspectorDifficultyValue("Rhythm Diff", osu.Game.Rulesets.Osu.Difficulty.Evaluators.RhythmEvaluator.EvaluateDifficultyOf(hitObject)),
new ObjectInspectorDifficultyValue(hidden ? "FLHD Difficulty" : "Flashlight Diff", FlashlightEvaluator.EvaluateDifficultyOf(hitObject, hidden)),
});
if (hitObject.Angle is not null)
flowContainer.Add(new ObjectInspectorDifficultyValue("Angle", double.RadiansToDegrees(hitObject.Angle.Value)));
flowContainer.Add(new ObjectInspectorDifficultyValue("Lazy Jump Dist", hitObject.LazyJumpDistance));
flowContainer.Add(new ObjectInspectorDifficultyValue("Min Jump Dist", hitObject.MinimumJumpDistance));
flowContainer.Add(new ObjectInspectorDifficultyValue("Min Jump Time", hitObject.MinimumJumpTime));
if (hitObject.BaseObject is Slider)
{
flowContainer.AddRange(new Drawable[]
@ -158,14 +166,27 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
private void drawTaikoValues(TaikoDifficultyHitObject hitObject)
{
double rhythmDifficulty =
osu.Game.Rulesets.Taiko.Difficulty.Evaluators.RhythmEvaluator.EvaluateDifficultyOf(hitObject, 2 * hitObject.BaseObject.HitWindows.WindowFor(HitResult.Great) / track.Rate);
flowContainer.AddRange(new[]
{
new ObjectInspectorDifficultyValue("Delta Time", hitObject.DeltaTime),
new ObjectInspectorDifficultyValue("Effective BPM", hitObject.EffectiveBPM),
new ObjectInspectorDifficultyValue("Rhythm Ratio", hitObject.RhythmData.Ratio),
new ObjectInspectorDifficultyValue("Colour Difficulty", ColourEvaluator.EvaluateDifficultyOf(hitObject)),
new ObjectInspectorDifficultyValue("Stamina Difficulty", StaminaEvaluator.EvaluateDifficultyOf(hitObject)),
new ObjectInspectorDifficultyValue("Rhythm Difficulty", hitObject.Rhythm.Difficulty),
new ObjectInspectorDifficultyValue("Rhythm Ratio", hitObject.Rhythm.Ratio),
new ObjectInspectorDifficultyValue("Rhythm Difficulty", rhythmDifficulty),
});
if (hitObject.BaseObject is Hit hit)
{
flowContainer.AddRange(new[]
{
new ObjectInspectorDifficultyValue($"Mono ({hit.Type}) Index", hitObject.MonoIndex),
new ObjectInspectorDifficultyValue("Note Index", hitObject.NoteIndex),
});
}
}
private void drawCatchValues(CatchDifficultyHitObject hitObject)

View file

@ -87,6 +87,7 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
clock = new EditorClock(playableBeatmap, beatDivisor);
clock.ChangeSource(processorBeatmap.Track);
dependencies.CacheAs(clock);
dependencies.CacheAs(processorBeatmap.Track);
editorBeatmap = new EditorBeatmap(playableBeatmap);
dependencies.CacheAs(editorBeatmap);

View file

@ -3,8 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace PerformanceCalculatorGUI.Screens.ObjectInspection
@ -45,12 +45,12 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
{
new Drawable[]
{
new SpriteText
new OsuSpriteText
{
Text = label,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold)
},
new SpriteText
new OsuSpriteText
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,

View file

@ -90,7 +90,7 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
using (hitObject.BeginAbsoluteSequence(hitObject.StartTimeBindable.Value))
{
var hitObjectDuration = hitObject.HitObject.GetEndTime() - hitObject.StartTimeBindable.Value;
double hitObjectDuration = hitObject.HitObject.GetEndTime() - hitObject.StartTimeBindable.Value;
hitObject.Delay(hitObjectDuration)
.FadeTo(0.25f, 200f, Easing.Out)

View file

@ -67,6 +67,8 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
{
}
protected override bool TryMoveBlueprints(DragEvent e, IList<(SelectionBlueprint<HitObject> blueprint, Vector2[] originalSnapPositions)> blueprints) => false;
protected partial class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer
{
protected override Container<SelectionBlueprint<HitObject>> Content { get; }

View file

@ -284,7 +284,7 @@ namespace PerformanceCalculatorGUI.Screens
{
Schedule(() => loadingLayer.Text.Value = $"Getting {username} user data...");
var player = await apiManager.GetJsonFromApi<APIUser>($"users/{username}/{ruleset.Value.ShortName}");
var player = await apiManager.GetJsonFromApi<APIUser>($"users/{username}/{ruleset.Value.ShortName}").ConfigureAwait(false);
players.Add(player);
currentUsers = currentUsers.Append(player.Username).ToArray();
@ -302,12 +302,12 @@ namespace PerformanceCalculatorGUI.Screens
Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username} top scores...");
var apiScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100");
var apiScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100").ConfigureAwait(false);
if (includePinnedCheckbox.Current.Value)
{
var pinnedScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/pinned?mode={ruleset.Value.ShortName}&limit=100");
apiScores = apiScores.Concat(pinnedScores.Where(p => !apiScores.Any(b => b.ID == p.ID))).ToList();
var pinnedScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/pinned?mode={ruleset.Value.ShortName}&limit=100").ConfigureAwait(false);
apiScores = apiScores.Concat(pinnedScores.Where(p => !apiScores.Any(b => b.ID == p.ID)).ToArray()).ToList();
}
foreach (var score in apiScores)
@ -328,10 +328,12 @@ namespace PerformanceCalculatorGUI.Screens
var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working);
var difficultyAttributes = difficultyCalculator.Calculate(mods);
var performanceCalculator = rulesetInstance.CreatePerformanceCalculator();
if (performanceCalculator == null)
continue;
double? livePp = score.PP;
var perfAttributes = await performanceCalculator?.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token)!;
score.PP = perfAttributes?.Total ?? 0.0;
var perfAttributes = await performanceCalculator.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token).ConfigureAwait(false);
score.PP = perfAttributes.Total;
var extendedScore = new ExtendedScore(score, livePp, perfAttributes);
plays.Add(extendedScore);
@ -382,17 +384,17 @@ namespace PerformanceCalculatorGUI.Screens
var player = players.First();
decimal totalLocalPP = 0;
for (var i = 0; i < localOrdered.Count; i++)
for (int i = 0; i < localOrdered.Count; i++)
totalLocalPP += (decimal)(Math.Pow(0.95, i) * (localOrdered[i].SoloScore.PP ?? 0));
decimal totalLivePP = player.Statistics.PP ?? (decimal)0.0;
decimal nonBonusLivePP = 0;
for (var i = 0; i < liveOrdered.Count; i++)
for (int i = 0; i < liveOrdered.Count; i++)
nonBonusLivePP += (decimal)(Math.Pow(0.95, i) * liveOrdered[i].LivePP ?? 0);
//todo: implement properly. this is pretty damn wrong.
var playcountBonusPP = (totalLivePP - nonBonusLivePP);
decimal playcountBonusPP = (totalLivePP - nonBonusLivePP);
totalLocalPP += playcountBonusPP;
Schedule(() =>

View file

@ -1022,7 +1022,7 @@ namespace PerformanceCalculatorGUI.Screens
{
try
{
var scoreInfo = await apiManager.GetJsonFromApi<SoloScoreInfo>($"scores/{scoreId}");
var scoreInfo = await apiManager.GetJsonFromApi<SoloScoreInfo>($"scores/{scoreId}").ConfigureAwait(false);
Schedule(() =>
{

View file

@ -13,7 +13,7 @@ This is part of a group of projects which are used in live deployments where the
# Requirements
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed.
- These instructions assume you have the the [CLI git client](https://git-scm.com/) installed, but any other GUI client such as GitKraken will suffice.
# Getting Started
@ -32,8 +32,21 @@ Most relevant code is in the main [ppy/osu](https://github.com/ppy/osu) reposito
git clone https://github.com/ppy/osu-tools
git clone https://github.com/ppy/osu
```
- If you're planning to work on the difficulty and/or performance calculation changes you will need to use the `pp-dev` branch of the `osu` repository
```shell
cd osu
git checkout -b pp-dev origin/pp-dev
```
- Run the `./UseLocalOsu.ps1` powershell script (or `./UseLocalOsu.sh`) to use your local copy of ppy/osu
### Code analysis
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
## I want to run someone else's changes
- Clone all relevant repos into the same directory
@ -53,11 +66,9 @@ git checkout -b branch_name smoogi/branch_name
# Contributing
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests.
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. You might want to refer to the [contributing guidelines](https://github.com/ppy/osu/blob/master/CONTRIBUTING.md) in the main osu! repository to understand how to help in the most effective way possible.
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible.
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
# Licence

View file

@ -1,15 +1,35 @@
<!-- Contains required properties for osu!tools projects. -->
<!-- Contains required properties for osu!tools projects. -->
<Project>
<PropertyGroup Label="C#">
<LangVersion>8.0</LangVersion>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup Label="License">
<None Include="..\osu-tools.licenseheader">
<Link>osu-tools.licenseheader</Link>
</None>
</ItemGroup>
<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.Tools.globalconfig" />
</ItemGroup>
<PropertyGroup Label="Code Analysis">
<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="Project">
<Company>ppy Pty Ltd</Company>
<Copyright>Copyright (c) 2020 ppy Pty Ltd</Copyright>
<RepositoryUrl>https://github.com/ppy/osu-tools</RepositoryUrl>
<Copyright>Copyright (c) 2024 ppy Pty Ltd</Copyright>
</PropertyGroup>
</Project>

View file

@ -7,6 +7,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceCalculator", "Pe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceCalculatorGUI", "PerformanceCalculatorGUI\PerformanceCalculatorGUI.csproj", "{BB34B7BA-CB01-4F5F-9532-B070F3A2C52E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
osu.Tools.props = osu.Tools.props
osu.Tools.sln.DotSettings = osu.Tools.sln.DotSettings
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeAnalysis", "CodeAnalysis", "{FB156649-D457-4D1A-969C-D3A23FD31513}"
ProjectSection(SolutionItems) = preProject
CodeAnalysis\BannedSymbols.txt = CodeAnalysis\BannedSymbols.txt
CodeAnalysis\osu.Tools.globalconfig = CodeAnalysis\osu.Tools.globalconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View file

@ -15,10 +15,12 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeLocalFunctionBody/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMethodOrOperatorBody/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNamespaceBody/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeEvident/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeNotEvident/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue">WARNING</s:String>
<s:Boolean x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTrailingCommaInMultilineLists/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeMemberModifiers/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeModifiers/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignedValueIsNeverUsed/@EntryIndexedValue">HINT</s:String>
@ -65,8 +67,9 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CompareOfFloatsByEqualityOperator/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConditionalTernaryExpressionToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConstructorToMemberInitializers/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfDoToWhile/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingAssignment/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
@ -80,6 +83,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLocalFunction/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToPrimaryConstructor/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToStaticClass/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToUsingDeclaration/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertTypeCheckPatternToNullCheck/@EntryIndexedValue">DO_NOT_SHOW</s:String>
@ -149,7 +153,6 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyCanBeMadeInitOnly_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyCanBeMadeInitOnly_002ELocal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PublicConstructorInAbstractClass/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAnonymousTypePropertyName/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArgumentDefaultValue/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArrayCreationExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeParentheses/@EntryIndexedValue">WARNING</s:String>
@ -165,6 +168,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantImmediateDelegateInvocation/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLambdaSignatureParentheses/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantReadonlyModifier/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantTypeDeclarationBody/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantTypeSpecificationInDefaultExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLinebreak/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantSpace/@EntryIndexedValue">WARNING</s:String>
@ -251,6 +255,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedType_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseAwaitUsing/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseCollectionCountProperty/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseCollectionExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseConfigureAwaitFalseForAsyncDisposable/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInFormatString/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInInterpolation/@EntryIndexedValue">WARNING</s:String>
@ -263,6 +268,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UsePatternMatching/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseStringInterpolation/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseUtf8StringLiteral/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableCanBeMadeConst/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberCallInConstructor/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberNeverOverridden_002EGlobal/@EntryIndexedValue">HINT</s:String>
@ -335,12 +341,14 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DDKK/@EntryIndexedValue">DDKK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EF/@EntryIndexedValue">EF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FPS/@EntryIndexedValue">FPS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HP/@EntryIndexedValue">HP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSL/@EntryIndexedValue">HSL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSPA/@EntryIndexedValue">HSPA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSV/@EntryIndexedValue">HSV</s:String>
@ -352,6 +360,8 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPC/@EntryIndexedValue">IPC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=JIT/@EntryIndexedValue">JIT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KDDK/@EntryIndexedValue">KDDK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KKDD/@EntryIndexedValue">KKDD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LTRB/@EntryIndexedValue">LTRB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NS/@EntryIndexedValue">NS</s:String>
@ -370,6 +380,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QAT/@EntryIndexedValue">QAT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BNG/@EntryIndexedValue">BNG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WIP/@EntryIndexedValue">WIP</s:String>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&#xD;
@ -834,6 +845,7 @@ See the LICENCE file in the repository root for full licence text.&#xD;
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ENumerics_002E_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ESecurity_002ECryptography_002ERSA/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=TagLib_002EMpeg4_002EBox/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Vortice_002E_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
@ -841,6 +853,7 @@ See the LICENCE file in the repository root for full licence text.&#xD;
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
@ -1011,6 +1024,7 @@ private void load()
<s:Boolean x:Key="/Default/UserDictionary/Words/=Migratable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nightcore/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Omni/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=osump/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Overlined/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pausable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pippidon/@EntryIndexedValue">True</s:Boolean>
@ -1047,4 +1061,6 @@ private void load()
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unplayed/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unproxy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unranked/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=velopack/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Welford_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Zoomable/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>