Merge branch 'master' into remove-score-multiplier-on-custom-mod-settings

This commit is contained in:
Salman Ahmed 2022-07-19 19:21:26 +03:00
commit 4210ec6502
94 changed files with 2433 additions and 1709 deletions

View File

@ -19,3 +19,7 @@ P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResult
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
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.
M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead.

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.715.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.719.1" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -22,10 +22,12 @@
using osu.Framework.Input.Handlers.Joystick;
using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Input.Handlers.Touch;
using osu.Framework.Threading;
using osu.Game.IO;
using osu.Game.IPC;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.Settings.Sections.Input;
namespace osu.Desktop
@ -156,6 +158,9 @@ public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler hand
case JoystickHandler jh:
return new JoystickSettings(jh);
case TouchHandler th:
return new InputSection.HandlerSection(th);
default:
return base.CreateSettingsSubsectionFor(handler);
}

View File

@ -37,9 +37,15 @@ public static void Main(string[] args)
// See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
{
// If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider
// disabling it ourselves.
// We could also better detect compatibility mode if required:
// https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
"Your operating system is too old to run osu!",
"This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero);
"This version of osu! requires at least Windows 8.1 to run.\n"
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero);
return;
}

View File

@ -0,0 +1,166 @@
// 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 BenchmarkDotNet.Attributes;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Benchmarks
{
public class BenchmarkHitObject : BenchmarkTest
{
[Params(1, 100, 1000)]
public int Count { get; set; }
[Params(false, true)]
public bool WithBindableAccess { get; set; }
[Benchmark]
public HitCircle[] OsuCircle()
{
var circles = new HitCircle[Count];
for (int i = 0; i < Count; i++)
{
circles[i] = new HitCircle();
if (WithBindableAccess)
{
_ = circles[i].PositionBindable;
_ = circles[i].ScaleBindable;
_ = circles[i].ComboIndexBindable;
_ = circles[i].ComboOffsetBindable;
_ = circles[i].StackHeightBindable;
_ = circles[i].LastInComboBindable;
_ = circles[i].ComboIndexWithOffsetsBindable;
_ = circles[i].IndexInCurrentComboBindable;
_ = circles[i].SamplesBindable;
_ = circles[i].StartTimeBindable;
}
else
{
_ = circles[i].Position;
_ = circles[i].Scale;
_ = circles[i].ComboIndex;
_ = circles[i].ComboOffset;
_ = circles[i].StackHeight;
_ = circles[i].LastInCombo;
_ = circles[i].ComboIndexWithOffsets;
_ = circles[i].IndexInCurrentCombo;
_ = circles[i].Samples;
_ = circles[i].StartTime;
_ = circles[i].Position;
_ = circles[i].Scale;
_ = circles[i].ComboIndex;
_ = circles[i].ComboOffset;
_ = circles[i].StackHeight;
_ = circles[i].LastInCombo;
_ = circles[i].ComboIndexWithOffsets;
_ = circles[i].IndexInCurrentCombo;
_ = circles[i].Samples;
_ = circles[i].StartTime;
}
}
return circles;
}
[Benchmark]
public Hit[] TaikoHit()
{
var hits = new Hit[Count];
for (int i = 0; i < Count; i++)
{
hits[i] = new Hit();
if (WithBindableAccess)
{
_ = hits[i].TypeBindable;
_ = hits[i].IsStrongBindable;
_ = hits[i].SamplesBindable;
_ = hits[i].StartTimeBindable;
}
else
{
_ = hits[i].Type;
_ = hits[i].IsStrong;
_ = hits[i].Samples;
_ = hits[i].StartTime;
}
}
return hits;
}
[Benchmark]
public Fruit[] CatchFruit()
{
var fruit = new Fruit[Count];
for (int i = 0; i < Count; i++)
{
fruit[i] = new Fruit();
if (WithBindableAccess)
{
_ = fruit[i].OriginalXBindable;
_ = fruit[i].XOffsetBindable;
_ = fruit[i].ScaleBindable;
_ = fruit[i].ComboIndexBindable;
_ = fruit[i].HyperDashBindable;
_ = fruit[i].LastInComboBindable;
_ = fruit[i].ComboIndexWithOffsetsBindable;
_ = fruit[i].IndexInCurrentComboBindable;
_ = fruit[i].IndexInBeatmapBindable;
_ = fruit[i].SamplesBindable;
_ = fruit[i].StartTimeBindable;
}
else
{
_ = fruit[i].OriginalX;
_ = fruit[i].XOffset;
_ = fruit[i].Scale;
_ = fruit[i].ComboIndex;
_ = fruit[i].HyperDash;
_ = fruit[i].LastInCombo;
_ = fruit[i].ComboIndexWithOffsets;
_ = fruit[i].IndexInCurrentCombo;
_ = fruit[i].IndexInBeatmap;
_ = fruit[i].Samples;
_ = fruit[i].StartTime;
}
}
return fruit;
}
[Benchmark]
public Note[] ManiaNote()
{
var notes = new Note[Count];
for (int i = 0; i < Count; i++)
{
notes[i] = new Note();
if (WithBindableAccess)
{
_ = notes[i].ColumnBindable;
_ = notes[i].SamplesBindable;
_ = notes[i].StartTimeBindable;
}
else
{
_ = notes[i].Column;
_ = notes[i].Samples;
_ = notes[i].StartTime;
}
}
return notes;
}
}
}

View File

@ -19,7 +19,9 @@ public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInforma
{
public const float OBJECT_RADIUS = 64;
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
private HitObjectProperty<float> originalX;
public Bindable<float> OriginalXBindable => originalX.Bindable;
/// <summary>
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
@ -31,18 +33,20 @@ public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInforma
[JsonIgnore]
public float X
{
set => OriginalXBindable.Value = value;
set => originalX.Value = value;
}
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
private HitObjectProperty<float> xOffset;
public Bindable<float> XOffsetBindable => xOffset.Bindable;
/// <summary>
/// A random offset applied to the horizontal position, set by the beatmap processing.
/// </summary>
public float XOffset
{
get => XOffsetBindable.Value;
set => XOffsetBindable.Value = value;
get => xOffset.Value;
set => xOffset.Value = value;
}
/// <summary>
@ -54,8 +58,8 @@ public float XOffset
/// </remarks>
public float OriginalX
{
get => OriginalXBindable.Value;
set => OriginalXBindable.Value = value;
get => originalX.Value;
set => originalX.Value = value;
}
/// <summary>
@ -69,59 +73,71 @@ public float OriginalX
public double TimePreempt { get; set; } = 1000;
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
private HitObjectProperty<int> indexInBeatmap;
public Bindable<int> IndexInBeatmapBindable => indexInBeatmap.Bindable;
public int IndexInBeatmap
{
get => IndexInBeatmapBindable.Value;
set => IndexInBeatmapBindable.Value = value;
get => indexInBeatmap.Value;
set => indexInBeatmap.Value = value;
}
public virtual bool NewCombo { get; set; }
public int ComboOffset { get; set; }
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
private HitObjectProperty<int> indexInCurrentCombo;
public Bindable<int> IndexInCurrentComboBindable => indexInCurrentCombo.Bindable;
public int IndexInCurrentCombo
{
get => IndexInCurrentComboBindable.Value;
set => IndexInCurrentComboBindable.Value = value;
get => indexInCurrentCombo.Value;
set => indexInCurrentCombo.Value = value;
}
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
private HitObjectProperty<int> comboIndex;
public Bindable<int> ComboIndexBindable => comboIndex.Bindable;
public int ComboIndex
{
get => ComboIndexBindable.Value;
set => ComboIndexBindable.Value = value;
get => comboIndex.Value;
set => comboIndex.Value = value;
}
public Bindable<int> ComboIndexWithOffsetsBindable { get; } = new Bindable<int>();
private HitObjectProperty<int> comboIndexWithOffsets;
public Bindable<int> ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable;
public int ComboIndexWithOffsets
{
get => ComboIndexWithOffsetsBindable.Value;
set => ComboIndexWithOffsetsBindable.Value = value;
get => comboIndexWithOffsets.Value;
set => comboIndexWithOffsets.Value = value;
}
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
private HitObjectProperty<bool> lastInCombo;
public Bindable<bool> LastInComboBindable => lastInCombo.Bindable;
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>
public virtual bool LastInCombo
{
get => LastInComboBindable.Value;
set => LastInComboBindable.Value = value;
get => lastInCombo.Value;
set => lastInCombo.Value = value;
}
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
private HitObjectProperty<float> scale = new HitObjectProperty<float>(1);
public Bindable<float> ScaleBindable => scale.Bindable;
public float Scale
{
get => ScaleBindable.Value;
set => ScaleBindable.Value = value;
get => scale.Value;
set => scale.Value = value;
}
/// <summary>

View File

@ -5,6 +5,7 @@
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osuTK.Graphics;
@ -24,12 +25,14 @@ public abstract class PalpableCatchHitObject : CatchHitObject, IHasComboInformat
/// </summary>
public float DistanceToHyperDash { get; set; }
public readonly Bindable<bool> HyperDashBindable = new Bindable<bool>();
private HitObjectProperty<bool> hyperDash;
public Bindable<bool> HyperDashBindable => hyperDash.Bindable;
/// <summary>
/// Whether this fruit can initiate a hyperdash.
/// </summary>
public bool HyperDash => HyperDashBindable.Value;
public bool HyperDash => hyperDash.Value;
private CatchHitObject hyperDashTarget;

View File

@ -13,12 +13,14 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition
{
public readonly Bindable<int> ColumnBindable = new Bindable<int>();
private HitObjectProperty<int> column;
public Bindable<int> ColumnBindable => column.Bindable;
public virtual int Column
{
get => ColumnBindable.Value;
set => ColumnBindable.Value = value;
get => column.Value;
set => column.Value = value;
}
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();

View File

@ -17,18 +17,18 @@ public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.6972307565739273d, 206, "diffcalc-test")]
[TestCase(1.4484754139145539d, 45, "zero-length-sliders")]
[TestCase(6.6369583000323935d, 206, "diffcalc-test")]
[TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);
[TestCase(8.9382559208689809d, 206, "diffcalc-test")]
[TestCase(1.7548875851757628d, 45, "zero-length-sliders")]
[TestCase(8.8816128335486386d, 206, "diffcalc-test")]
[TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
[TestCase(6.6972307218715166d, 239, "diffcalc-test")]
[TestCase(1.4484754139145537d, 54, "zero-length-sliders")]
[TestCase(6.6369583000323935d, 239, "diffcalc-test")]
[TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());

View File

@ -108,13 +108,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
// Reward for % distance slowed down compared to previous, paying attention to not award overlap
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
// do not award overlap
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);
// Choose the largest bonus, multiplied by ratio.
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
velocityChangeBonus = overlapVelocityBuff * distRatio;
// Penalize for rhythm changes.
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModCinema : ModCinema<OsuHitObject>
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap), typeof(OsuModRepel) }).ToArray();
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });

View File

@ -3,11 +3,14 @@
#nullable disable
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModPerfect : ModPerfect
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
}
}

View File

@ -7,12 +7,12 @@
using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -36,12 +36,14 @@ public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPositi
public double TimePreempt = 600;
public double TimeFadeIn = 400;
public readonly Bindable<Vector2> PositionBindable = new Bindable<Vector2>();
private HitObjectProperty<Vector2> position;
public Bindable<Vector2> PositionBindable => position.Bindable;
public virtual Vector2 Position
{
get => PositionBindable.Value;
set => PositionBindable.Value = value;
get => position.Value;
set => position.Value = value;
}
public float X => Position.X;
@ -53,66 +55,80 @@ public virtual Vector2 Position
public Vector2 StackedEndPosition => EndPosition + StackOffset;
public readonly Bindable<int> StackHeightBindable = new Bindable<int>();
private HitObjectProperty<int> stackHeight;
public Bindable<int> StackHeightBindable => stackHeight.Bindable;
public int StackHeight
{
get => StackHeightBindable.Value;
set => StackHeightBindable.Value = value;
get => stackHeight.Value;
set => stackHeight.Value = value;
}
public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
public double Radius => OBJECT_RADIUS * Scale;
public readonly Bindable<float> ScaleBindable = new BindableFloat(1);
private HitObjectProperty<float> scale = new HitObjectProperty<float>(1);
public Bindable<float> ScaleBindable => scale.Bindable;
public float Scale
{
get => ScaleBindable.Value;
set => ScaleBindable.Value = value;
get => scale.Value;
set => scale.Value = value;
}
public virtual bool NewCombo { get; set; }
public readonly Bindable<int> ComboOffsetBindable = new Bindable<int>();
private HitObjectProperty<int> comboOffset;
public Bindable<int> ComboOffsetBindable => comboOffset.Bindable;
public int ComboOffset
{
get => ComboOffsetBindable.Value;
set => ComboOffsetBindable.Value = value;
get => comboOffset.Value;
set => comboOffset.Value = value;
}
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
private HitObjectProperty<int> indexInCurrentCombo;
public Bindable<int> IndexInCurrentComboBindable => indexInCurrentCombo.Bindable;
public virtual int IndexInCurrentCombo
{
get => IndexInCurrentComboBindable.Value;
set => IndexInCurrentComboBindable.Value = value;
get => indexInCurrentCombo.Value;
set => indexInCurrentCombo.Value = value;
}
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
private HitObjectProperty<int> comboIndex;
public Bindable<int> ComboIndexBindable => comboIndex.Bindable;
public virtual int ComboIndex
{
get => ComboIndexBindable.Value;
set => ComboIndexBindable.Value = value;
get => comboIndex.Value;
set => comboIndex.Value = value;
}
public Bindable<int> ComboIndexWithOffsetsBindable { get; } = new Bindable<int>();
private HitObjectProperty<int> comboIndexWithOffsets;
public Bindable<int> ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable;
public int ComboIndexWithOffsets
{
get => ComboIndexWithOffsetsBindable.Value;
set => ComboIndexWithOffsetsBindable.Value = value;
get => comboIndexWithOffsets.Value;
set => comboIndexWithOffsets.Value = value;
}
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
private HitObjectProperty<bool> lastInCombo;
public Bindable<bool> LastInComboBindable => lastInCombo.Bindable;
public bool LastInCombo
{
get => LastInComboBindable.Value;
set => LastInComboBindable.Value = value;
get => lastInCombo.Value;
set => lastInCombo.Value = value;
}
protected OsuHitObject()

View File

@ -35,13 +35,13 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (score.Mods.Any(m => m is ModNoFail))
multiplier *= 0.90;
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (score.Mods.Any(m => m is ModHidden))
multiplier *= 1.10;
multiplier *= 1.075;
if (score.Mods.Any(m => m is ModEasy))
multiplier *= 0.975;
double difficultyValue = computeDifficultyValue(score, taikoAttributes);
double accuracyValue = computeAccuracyValue(score, taikoAttributes);
@ -61,12 +61,15 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.175) - 4.0, 2.25) / 450.0;
double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0;
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
difficultyValue *= lengthBonus;
difficultyValue *= Math.Pow(0.985, countMiss);
difficultyValue *= Math.Pow(0.986, countMiss);
if (score.Mods.Any(m => m is ModEasy))
difficultyValue *= 0.980;
if (score.Mods.Any(m => m is ModHidden))
difficultyValue *= 1.025;
@ -74,7 +77,7 @@ private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
difficultyValue *= 1.05 * lengthBonus;
return difficultyValue * score.Accuracy;
return difficultyValue * Math.Pow(score.Accuracy, 1.5);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
@ -82,10 +85,16 @@ private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes a
if (attributes.GreatHitWindow <= 0)
return 0;
double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0;
double accuracyValue = Math.Pow(140.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 12.0) * 27;
// Bonus for many objects - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
accuracyValue *= lengthBonus;
// Slight HDFL Bonus for accuracy.
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>) && score.Mods.Any(m => m is ModHidden))
accuracyValue *= 1.10 * lengthBonus;
return accuracyValue;
}
private int totalHits => countGreat + countOk + countMeh + countMiss;

View File

@ -11,14 +11,16 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class BarLine : TaikoHitObject, IBarLine
{
private HitObjectProperty<bool> major;
public Bindable<bool> MajorBindable => major.Bindable;
public bool Major
{
get => MajorBindable.Value;
set => MajorBindable.Value = value;
get => major.Value;
set => major.Value = value;
}
public readonly Bindable<bool> MajorBindable = new BindableBool();
public override Judgement CreateJudgement() => new IgnoreJudgement();
}
}

View File

@ -7,6 +7,7 @@
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osuTK.Graphics;
@ -14,19 +15,21 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class Hit : TaikoStrongableHitObject, IHasDisplayColour
{
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
private HitObjectProperty<HitType> type;
public Bindable<Color4> DisplayColour { get; } = new Bindable<Color4>(COLOUR_CENTRE);
public Bindable<HitType> TypeBindable => type.Bindable;
/// <summary>
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
/// </summary>
public HitType Type
{
get => TypeBindable.Value;
set => TypeBindable.Value = value;
get => type.Value;
set => type.Value = value;
}
public Bindable<Color4> DisplayColour { get; } = new Bindable<Color4>(COLOUR_CENTRE);
public static readonly Color4 COLOUR_CENTRE = Color4Extensions.FromHex(@"bb1177");
public static readonly Color4 COLOUR_RIM = Color4Extensions.FromHex(@"2299bb");

View File

@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Globalization;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Extensions;
namespace osu.Game.Tests.Extensions
{
[TestFixture]
public class StringDehumanizeExtensionsTest
{
[Test]
[TestCase("single", "Single")]
[TestCase("example word", "ExampleWord")]
[TestCase("mixed Casing test", "MixedCasingTest")]
[TestCase("PascalCase", "PascalCase")]
[TestCase("camelCase", "CamelCase")]
[TestCase("snake_case", "SnakeCase")]
[TestCase("kebab-case", "KebabCase")]
[TestCase("i will not break in a different culture", "IWillNotBreakInADifferentCulture", "tr-TR")]
public void TestToPascalCase(string input, string expectedOutput, string? culture = null)
{
using (temporaryCurrentCulture(culture))
Assert.That(input.ToPascalCase(), Is.EqualTo(expectedOutput));
}
[Test]
[TestCase("single", "single")]
[TestCase("example word", "exampleWord")]
[TestCase("mixed Casing test", "mixedCasingTest")]
[TestCase("PascalCase", "pascalCase")]
[TestCase("camelCase", "camelCase")]
[TestCase("snake_case", "snakeCase")]
[TestCase("kebab-case", "kebabCase")]
[TestCase("I will not break in a different culture", "iWillNotBreakInADifferentCulture", "tr-TR")]
public void TestToCamelCase(string input, string expectedOutput, string? culture = null)
{
using (temporaryCurrentCulture(culture))
Assert.That(input.ToCamelCase(), Is.EqualTo(expectedOutput));
}
[Test]
[TestCase("single", "single")]
[TestCase("example word", "example_word")]
[TestCase("mixed Casing test", "mixed_casing_test")]
[TestCase("PascalCase", "pascal_case")]
[TestCase("camelCase", "camel_case")]
[TestCase("snake_case", "snake_case")]
[TestCase("kebab-case", "kebab_case")]
[TestCase("I will not break in a different culture", "i_will_not_break_in_a_different_culture", "tr-TR")]
public void TestToSnakeCase(string input, string expectedOutput, string? culture = null)
{
using (temporaryCurrentCulture(culture))
Assert.That(input.ToSnakeCase(), Is.EqualTo(expectedOutput));
}
[Test]
[TestCase("single", "single")]
[TestCase("example word", "example-word")]
[TestCase("mixed Casing test", "mixed-casing-test")]
[TestCase("PascalCase", "pascal-case")]
[TestCase("camelCase", "camel-case")]
[TestCase("snake_case", "snake-case")]
[TestCase("kebab-case", "kebab-case")]
[TestCase("I will not break in a different culture", "i-will-not-break-in-a-different-culture", "tr-TR")]
public void TestToKebabCase(string input, string expectedOutput, string? culture = null)
{
using (temporaryCurrentCulture(culture))
Assert.That(input.ToKebabCase(), Is.EqualTo(expectedOutput));
}
private IDisposable temporaryCurrentCulture(string? cultureName)
{
var storedCulture = CultureInfo.CurrentCulture;
if (cultureName != null)
CultureInfo.CurrentCulture = new CultureInfo(cultureName);
return new InvokeOnDisposal(() => CultureInfo.CurrentCulture = storedCulture);
}
}
}

View File

@ -62,9 +62,45 @@ public void TestAudioEqualityDifferentHash()
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
}
private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null)
[Test]
public void TestAudioEqualityBeatmapInfoSameHash()
{
beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3"));
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(2);
addAudioFile(beatmapSet);
var beatmap1 = beatmapSet.Beatmaps.First();
var beatmap2 = beatmapSet.Beatmaps.Last();
Assert.AreNotEqual(beatmap1, beatmap2);
Assert.IsTrue(beatmap1.AudioEquals(beatmap2));
}
[Test]
public void TestAudioEqualityBeatmapInfoDifferentHash()
{
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(2);
const string filename1 = "audio1.mp3";
const string filename2 = "audio2.mp3";
addAudioFile(beatmapSet, filename: filename1);
addAudioFile(beatmapSet, filename: filename2);
var beatmap1 = beatmapSet.Beatmaps.First();
var beatmap2 = beatmapSet.Beatmaps.Last();
Assert.AreNotEqual(beatmap1, beatmap2);
beatmap1.Metadata.AudioFile = filename1;
beatmap2.Metadata.AudioFile = filename2;
Assert.IsFalse(beatmap1.AudioEquals(beatmap2));
}
private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null, string filename = null)
{
beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, filename ?? "audio.mp3"));
}
[Test]

View File

@ -138,7 +138,7 @@ IEnumerable<BeatmapInfo> getBeatmaps(int count)
BPM = bpm,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo,
Metadata = metadata,
Metadata = metadata.DeepClone(),
Difficulty = new BeatmapDifficulty
{
OverallDifficulty = diff,

View File

@ -55,7 +55,7 @@ public void TestOnline()
Id = 3103765,
IsOnline = true,
Statistics = new UserStatistics { GlobalRank = 1111 },
Country = new Country { FlagName = "JP" },
CountryCode = CountryCode.JP,
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
},
new APIUser
@ -64,7 +64,7 @@ public void TestOnline()
Id = 2,
IsOnline = false,
Statistics = new UserStatistics { GlobalRank = 2222 },
Country = new Country { FlagName = "AU" },
CountryCode = CountryCode.AU,
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = true,
SupportLevel = 3,
@ -73,7 +73,7 @@ public void TestOnline()
{
Username = "Evast",
Id = 8195163,
Country = new Country { FlagName = "BY" },
CountryCode = CountryCode.BY,
CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
IsOnline = false,
LastVisit = DateTimeOffset.Now

View File

@ -23,7 +23,7 @@ public class TestSceneRankingsCountryFilter : OsuTestScene
public TestSceneRankingsCountryFilter()
{
var countryBindable = new Bindable<Country>();
var countryBindable = new Bindable<CountryCode>();
AddRange(new Drawable[]
{
@ -56,20 +56,12 @@ public TestSceneRankingsCountryFilter()
}
});
var country = new Country
{
FlagName = "BY",
FullName = "Belarus"
};
var unknownCountry = new Country
{
FlagName = "CK",
FullName = "Cook Islands"
};
const CountryCode country = CountryCode.BY;
const CountryCode unknown_country = CountryCode.CK;
AddStep("Set country", () => countryBindable.Value = country);
AddStep("Set null country", () => countryBindable.Value = null);
AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry);
AddStep("Set default country", () => countryBindable.Value = default);
AddStep("Set country with no flag", () => countryBindable.Value = unknown_country);
}
}
}

View File

@ -19,7 +19,7 @@ public class TestSceneRankingsHeader : OsuTestScene
public TestSceneRankingsHeader()
{
var countryBindable = new Bindable<Country>();
var countryBindable = new Bindable<CountryCode>();
var ruleset = new Bindable<RulesetInfo>();
var scope = new Bindable<RankingsScope>();
@ -30,21 +30,12 @@ public TestSceneRankingsHeader()
Ruleset = { BindTarget = ruleset }
});
var country = new Country
{
FlagName = "BY",
FullName = "Belarus"
};
var unknownCountry = new Country
{
FlagName = "CK",
FullName = "Cook Islands"
};
const CountryCode country = CountryCode.BY;
const CountryCode unknown_country = CountryCode.CK;
AddStep("Set country", () => countryBindable.Value = country);
AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry);
AddStep("Set country with no flag", () => countryBindable.Value = unknown_country);
}
}
}

View File

@ -21,7 +21,7 @@ public class TestSceneRankingsOverlay : OsuTestScene
private TestRankingsOverlay rankingsOverlay;
private readonly Bindable<Country> countryBindable = new Bindable<Country>();
private readonly Bindable<CountryCode> countryBindable = new Bindable<CountryCode>();
private readonly Bindable<RankingsScope> scope = new Bindable<RankingsScope>();
[SetUp]
@ -48,15 +48,15 @@ public void TestParentRulesetDecoupledAfterInitialShow()
public void TestFlagScopeDependency()
{
AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
AddAssert("Check country is Null", () => countryBindable.Value == null);
AddStep("Set country", () => countryBindable.Value = us_country);
AddAssert("Check country is default", () => countryBindable.IsDefault);
AddStep("Set country", () => countryBindable.Value = CountryCode.US);
AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
}
[Test]
public void TestShowCountry()
{
AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country));
AddStep("Show US", () => rankingsOverlay.ShowCountry(CountryCode.US));
}
private void loadRankingsOverlay()
@ -69,15 +69,9 @@ private void loadRankingsOverlay()
};
}
private static readonly Country us_country = new Country
{
FlagName = "US",
FullName = "United States"
};
private class TestRankingsOverlay : RankingsOverlay
{
public new Bindable<Country> Country => base.Country;
public new Bindable<CountryCode> Country => base.Country;
}
}
}

View File

@ -57,8 +57,7 @@ private void createCountryTable()
{
new CountryStatistics
{
Country = new Country { FlagName = "US", FullName = "United States" },
FlagName = "US",
Code = CountryCode.US,
ActiveUsers = 2_972_623,
PlayCount = 3_086_515_743,
RankedScore = 449_407_643_332_546,
@ -66,8 +65,7 @@ private void createCountryTable()
},
new CountryStatistics
{
Country = new Country { FlagName = "RU", FullName = "Russian Federation" },
FlagName = "RU",
Code = CountryCode.RU,
ActiveUsers = 1_609_989,
PlayCount = 1_637_052_841,
RankedScore = 221_660_827_473_004,
@ -86,7 +84,7 @@ private void createCountryTable()
User = new APIUser
{
Username = "first active user",
Country = new Country { FlagName = "JP" },
CountryCode = CountryCode.JP,
Active = true,
},
Accuracy = 0.9972,
@ -106,7 +104,7 @@ private void createCountryTable()
User = new APIUser
{
Username = "inactive user",
Country = new Country { FlagName = "AU" },
CountryCode = CountryCode.AU,
Active = false,
},
Accuracy = 0.9831,
@ -126,7 +124,7 @@ private void createCountryTable()
User = new APIUser
{
Username = "second active user",
Country = new Country { FlagName = "PL" },
CountryCode = CountryCode.PL,
Active = true,
},
Accuracy = 0.9584,

View File

@ -157,11 +157,7 @@ private APIScoresCollection createScores()
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
CountryCode = CountryCode.ES,
},
Mods = new[]
{
@ -184,11 +180,7 @@ private APIScoresCollection createScores()
{
Id = 4608074,
Username = @"Skycries",
Country = new Country
{
FullName = @"Brazil",
FlagName = @"BR",
},
CountryCode = CountryCode.BR,
},
Mods = new[]
{
@ -210,11 +202,7 @@ private APIScoresCollection createScores()
{
Id = 1014222,
Username = @"eLy",
Country = new Country
{
FullName = @"Japan",
FlagName = @"JP",
},
CountryCode = CountryCode.JP,
},
Mods = new[]
{
@ -235,11 +223,7 @@ private APIScoresCollection createScores()
{
Id = 1541390,
Username = @"Toukai",
Country = new Country
{
FullName = @"Canada",
FlagName = @"CA",
},
CountryCode = CountryCode.CA,
},
Mods = new[]
{
@ -259,11 +243,7 @@ private APIScoresCollection createScores()
{
Id = 7151382,
Username = @"Mayuri Hana",
Country = new Country
{
FullName = @"Thailand",
FlagName = @"TH",
},
CountryCode = CountryCode.TH,
},
Rank = ScoreRank.D,
PP = 160,
@ -302,11 +282,7 @@ private APIScoresCollection createScores()
{
Id = 7151382,
Username = @"Mayuri Hana",
Country = new Country
{
FullName = @"Thailand",
FlagName = @"TH",
},
CountryCode = CountryCode.TH,
},
Rank = ScoreRank.D,
PP = 160,

View File

@ -60,7 +60,7 @@ public void SetUp() => Schedule(() =>
{
Username = @"flyte",
Id = 3103765,
Country = new Country { FlagName = @"JP" },
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg",
Status = { Value = new UserStatusOnline() }
}) { Width = 300 },
@ -68,7 +68,7 @@ public void SetUp() => Schedule(() =>
{
Username = @"peppy",
Id = 2,
Country = new Country { FlagName = @"AU" },
CountryCode = CountryCode.AU,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = true,
SupportLevel = 3,
@ -77,7 +77,7 @@ public void SetUp() => Schedule(() =>
{
Username = @"Evast",
Id = 8195163,
Country = new Country { FlagName = @"BY" },
CountryCode = CountryCode.BY,
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
IsOnline = false,
LastVisit = DateTimeOffset.Now

View File

@ -24,7 +24,7 @@ public class TestSceneUserProfileOverlay : OsuTestScene
{
Username = @"Somebody",
Id = 1,
Country = new Country { FullName = @"Alien" },
CountryCode = CountryCode.Unknown,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
JoinDate = DateTimeOffset.Now.AddDays(-1),
LastVisit = DateTimeOffset.Now,
@ -82,7 +82,7 @@ protected override void LoadComplete()
Username = @"peppy",
Id = 2,
IsSupporter = true,
Country = new Country { FullName = @"Australia", FlagName = @"AU" },
CountryCode = CountryCode.AU,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg"
}));
@ -90,7 +90,7 @@ protected override void LoadComplete()
{
Username = @"flyte",
Id = 3103765,
Country = new Country { FullName = @"Japan", FlagName = @"JP" },
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
}));
@ -99,7 +99,7 @@ protected override void LoadComplete()
Username = @"BanchoBot",
Id = 3,
IsBot = true,
Country = new Country { FullName = @"Saint Helena", FlagName = @"SH" },
CountryCode = CountryCode.SH,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg"
}));

View File

@ -21,7 +21,7 @@ public class TestSceneUserProfileScores : OsuTestScene
{
public TestSceneUserProfileScores()
{
var firstScore = new APIScore
var firstScore = new SoloScoreInfo
{
PP = 1047.21,
Rank = ScoreRank.SH,
@ -34,7 +34,7 @@ public TestSceneUserProfileScores()
},
DifficultyName = "Extreme"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Mods = new[]
{
new APIMod { Acronym = new OsuModHidden().Acronym },
@ -44,7 +44,7 @@ public TestSceneUserProfileScores()
Accuracy = 0.9813
};
var secondScore = new APIScore
var secondScore = new SoloScoreInfo
{
PP = 134.32,
Rank = ScoreRank.A,
@ -57,7 +57,7 @@ public TestSceneUserProfileScores()
},
DifficultyName = "[4K] Regret"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Mods = new[]
{
new APIMod { Acronym = new OsuModHardRock().Acronym },
@ -66,7 +66,7 @@ public TestSceneUserProfileScores()
Accuracy = 0.998546
};
var thirdScore = new APIScore
var thirdScore = new SoloScoreInfo
{
PP = 96.83,
Rank = ScoreRank.S,
@ -79,11 +79,11 @@ public TestSceneUserProfileScores()
},
DifficultyName = "Insane"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Accuracy = 0.9726
};
var noPPScore = new APIScore
var noPPScore = new SoloScoreInfo
{
Rank = ScoreRank.B,
Beatmap = new APIBeatmap
@ -95,7 +95,7 @@ public TestSceneUserProfileScores()
},
DifficultyName = "[4K] Cataclysmic Hypernova"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Accuracy = 0.55879
};

View File

@ -140,11 +140,7 @@ private void showPersonalBestWithNullPosition()
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
CountryCode = CountryCode.ES,
},
});
}
@ -164,12 +160,8 @@ private void showPersonalBest()
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
},
CountryCode = CountryCode.ES,
}
});
}
@ -225,11 +217,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
CountryCode = CountryCode.ES,
},
},
new ScoreInfo
@ -246,11 +234,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 4608074,
Username = @"Skycries",
Country = new Country
{
FullName = @"Brazil",
FlagName = @"BR",
},
CountryCode = CountryCode.BR,
},
},
new ScoreInfo
@ -268,11 +252,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 1014222,
Username = @"eLy",
Country = new Country
{
FullName = @"Japan",
FlagName = @"JP",
},
CountryCode = CountryCode.JP,
},
},
new ScoreInfo
@ -290,11 +270,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 1541390,
Username = @"Toukai",
Country = new Country
{
FullName = @"Canada",
FlagName = @"CA",
},
CountryCode = CountryCode.CA,
},
},
new ScoreInfo
@ -312,11 +288,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 2243452,
Username = @"Satoruu",
Country = new Country
{
FullName = @"Venezuela",
FlagName = @"VE",
},
CountryCode = CountryCode.VE,
},
},
new ScoreInfo
@ -334,11 +306,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 2705430,
Username = @"Mooha",
Country = new Country
{
FullName = @"France",
FlagName = @"FR",
},
CountryCode = CountryCode.FR,
},
},
new ScoreInfo
@ -356,11 +324,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 7151382,
Username = @"Mayuri Hana",
Country = new Country
{
FullName = @"Thailand",
FlagName = @"TH",
},
CountryCode = CountryCode.TH,
},
},
new ScoreInfo
@ -378,11 +342,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 2051389,
Username = @"FunOrange",
Country = new Country
{
FullName = @"Canada",
FlagName = @"CA",
},
CountryCode = CountryCode.CA,
},
},
new ScoreInfo
@ -400,11 +360,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 6169483,
Username = @"-Hebel-",
Country = new Country
{
FullName = @"Mexico",
FlagName = @"MX",
},
CountryCode = CountryCode.MX,
},
},
new ScoreInfo
@ -422,11 +378,7 @@ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
Id = 6702666,
Username = @"prhtnsm",
Country = new Country
{
FullName = @"Germany",
FlagName = @"DE",
},
CountryCode = CountryCode.DE,
},
},
};

View File

@ -69,11 +69,7 @@ public TestSceneUserTopScoreContainer()
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
CountryCode = CountryCode.ES,
},
},
new ScoreInfo
@ -88,11 +84,7 @@ public TestSceneUserTopScoreContainer()
{
Id = 4608074,
Username = @"Skycries",
Country = new Country
{
FullName = @"Brazil",
FlagName = @"BR",
},
CountryCode = CountryCode.BR,
},
},
new ScoreInfo
@ -107,11 +99,7 @@ public TestSceneUserTopScoreContainer()
{
Id = 1541390,
Username = @"Toukai",
Country = new Country
{
FullName = @"Canada",
FlagName = @"CA",
},
CountryCode = CountryCode.CA,
},
}
};

View File

@ -4,13 +4,13 @@
#nullable disable
using System.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
@ -77,7 +77,7 @@ public void SetUp() => Schedule(() =>
};
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true);
control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToSnakeCase())) : "")}", true);
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);

View File

@ -0,0 +1,770 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Users;
namespace osu.Game.Tournament
{
public static class CountryExtensions
{
public static string GetAcronym(this CountryCode country)
{
switch (country)
{
case CountryCode.BD:
return "BGD";
case CountryCode.BE:
return "BEL";
case CountryCode.BF:
return "BFA";
case CountryCode.BG:
return "BGR";
case CountryCode.BA:
return "BIH";
case CountryCode.BB:
return "BRB";
case CountryCode.WF:
return "WLF";
case CountryCode.BL:
return "BLM";
case CountryCode.BM:
return "BMU";
case CountryCode.BN:
return "BRN";
case CountryCode.BO:
return "BOL";
case CountryCode.BH:
return "BHR";
case CountryCode.BI:
return "BDI";
case CountryCode.BJ:
return "BEN";
case CountryCode.BT:
return "BTN";
case CountryCode.JM:
return "JAM";
case CountryCode.BV:
return "BVT";
case CountryCode.BW:
return "BWA";
case CountryCode.WS:
return "WSM";
case CountryCode.BQ:
return "BES";
case CountryCode.BR:
return "BRA";
case CountryCode.BS:
return "BHS";
case CountryCode.JE:
return "JEY";
case CountryCode.BY:
return "BLR";
case CountryCode.BZ:
return "BLZ";
case CountryCode.RU:
return "RUS";
case CountryCode.RW:
return "RWA";
case CountryCode.RS:
return "SRB";
case CountryCode.TL:
return "TLS";
case CountryCode.RE:
return "REU";
case CountryCode.TM:
return "TKM";
case CountryCode.TJ:
return "TJK";
case CountryCode.RO:
return "ROU";
case CountryCode.TK:
return "TKL";
case CountryCode.GW:
return "GNB";
case CountryCode.GU:
return "GUM";
case CountryCode.GT:
return "GTM";
case CountryCode.GS:
return "SGS";
case CountryCode.GR:
return "GRC";
case CountryCode.GQ:
return "GNQ";
case CountryCode.GP:
return "GLP";
case CountryCode.JP:
return "JPN";
case CountryCode.GY:
return "GUY";
case CountryCode.GG:
return "GGY";
case CountryCode.GF:
return "GUF";
case CountryCode.GE:
return "GEO";
case CountryCode.GD:
return "GRD";
case CountryCode.GB:
return "GBR";
case CountryCode.GA:
return "GAB";
case CountryCode.SV:
return "SLV";
case CountryCode.GN:
return "GIN";
case CountryCode.GM:
return "GMB";
case CountryCode.GL:
return "GRL";
case CountryCode.GI:
return "GIB";
case CountryCode.GH:
return "GHA";
case CountryCode.OM:
return "OMN";
case CountryCode.TN:
return "TUN";
case CountryCode.JO:
return "JOR";
case CountryCode.HR:
return "HRV";
case CountryCode.HT:
return "HTI";
case CountryCode.HU:
return "HUN";
case CountryCode.HK:
return "HKG";
case CountryCode.HN:
return "HND";
case CountryCode.HM:
return "HMD";
case CountryCode.VE:
return "VEN";
case CountryCode.PR:
return "PRI";
case CountryCode.PS:
return "PSE";
case CountryCode.PW:
return "PLW";
case CountryCode.PT:
return "PRT";
case CountryCode.SJ:
return "SJM";
case CountryCode.PY:
return "PRY";
case CountryCode.IQ:
return "IRQ";
case CountryCode.PA:
return "PAN";
case CountryCode.PF:
return "PYF";
case CountryCode.PG:
return "PNG";
case CountryCode.PE:
return "PER";
case CountryCode.PK:
return "PAK";
case CountryCode.PH:
return "PHL";
case CountryCode.PN:
return "PCN";
case CountryCode.PL:
return "POL";
case CountryCode.PM:
return "SPM";
case CountryCode.ZM:
return "ZMB";
case CountryCode.EH:
return "ESH";
case CountryCode.EE:
return "EST";
case CountryCode.EG:
return "EGY";
case CountryCode.ZA:
return "ZAF";
case CountryCode.EC:
return "ECU";
case CountryCode.IT:
return "ITA";
case CountryCode.VN:
return "VNM";
case CountryCode.SB:
return "SLB";
case CountryCode.ET:
return "ETH";
case CountryCode.SO:
return "SOM";
case CountryCode.ZW:
return "ZWE";
case CountryCode.SA:
return "SAU";
case CountryCode.ES:
return "ESP";
case CountryCode.ER:
return "ERI";
case CountryCode.ME:
return "MNE";
case CountryCode.MD:
return "MDA";
case CountryCode.MG:
return "MDG";
case CountryCode.MF:
return "MAF";
case CountryCode.MA:
return "MAR";
case CountryCode.MC:
return "MCO";
case CountryCode.UZ:
return "UZB";
case CountryCode.MM:
return "MMR";
case CountryCode.ML:
return "MLI";
case CountryCode.MO:
return "MAC";
case CountryCode.MN:
return "MNG";
case CountryCode.MH:
return "MHL";
case CountryCode.MK:
return "MKD";
case CountryCode.MU:
return "MUS";
case CountryCode.MT:
return "MLT";
case CountryCode.MW:
return "MWI";
case CountryCode.MV:
return "MDV";
case CountryCode.MQ:
return "MTQ";
case CountryCode.MP:
return "MNP";
case CountryCode.MS:
return "MSR";
case CountryCode.MR:
return "MRT";
case CountryCode.IM:
return "IMN";
case CountryCode.UG:
return "UGA";
case CountryCode.TZ:
return "TZA";
case CountryCode.MY:
return "MYS";
case CountryCode.MX:
return "MEX";
case CountryCode.IL:
return "ISR";
case CountryCode.FR:
return "FRA";
case CountryCode.IO:
return "IOT";
case CountryCode.SH:
return "SHN";
case CountryCode.FI:
return "FIN";
case CountryCode.FJ:
return "FJI";
case CountryCode.FK:
return "FLK";
case CountryCode.FM:
return "FSM";
case CountryCode.FO:
return "FRO";
case CountryCode.NI:
return "NIC";
case CountryCode.NL:
return "NLD";
case CountryCode.NO:
return "NOR";
case CountryCode.NA:
return "NAM";
case CountryCode.VU:
return "VUT";
case CountryCode.NC:
return "NCL";
case CountryCode.NE:
return "NER";
case CountryCode.NF:
return "NFK";
case CountryCode.NG:
return "NGA";
case CountryCode.NZ:
return "NZL";
case CountryCode.NP:
return "NPL";
case CountryCode.NR:
return "NRU";
case CountryCode.NU:
return "NIU";
case CountryCode.CK:
return "COK";
case CountryCode.XK:
return "XKX";
case CountryCode.CI:
return "CIV";
case CountryCode.CH:
return "CHE";
case CountryCode.CO:
return "COL";
case CountryCode.CN:
return "CHN";
case CountryCode.CM:
return "CMR";
case CountryCode.CL:
return "CHL";
case CountryCode.CC:
return "CCK";
case CountryCode.CA:
return "CAN";
case CountryCode.CG:
return "COG";
case CountryCode.CF:
return "CAF";
case CountryCode.CD:
return "COD";
case CountryCode.CZ:
return "CZE";
case CountryCode.CY:
return "CYP";
case CountryCode.CX:
return "CXR";
case CountryCode.CR:
return "CRI";
case CountryCode.CW:
return "CUW";
case CountryCode.CV:
return "CPV";
case CountryCode.CU:
return "CUB";
case CountryCode.SZ:
return "SWZ";
case CountryCode.SY:
return "SYR";
case CountryCode.SX:
return "SXM";
case CountryCode.KG:
return "KGZ";
case CountryCode.KE:
return "KEN";
case CountryCode.SS:
return "SSD";
case CountryCode.SR:
return "SUR";
case CountryCode.KI:
return "KIR";
case CountryCode.KH:
return "KHM";
case CountryCode.KN:
return "KNA";
case CountryCode.KM:
return "COM";
case CountryCode.ST:
return "STP";
case CountryCode.SK:
return "SVK";
case CountryCode.KR:
return "KOR";
case CountryCode.SI:
return "SVN";
case CountryCode.KP:
return "PRK";
case CountryCode.KW:
return "KWT";
case CountryCode.SN:
return "SEN";
case CountryCode.SM:
return "SMR";
case CountryCode.SL:
return "SLE";
case CountryCode.SC:
return "SYC";
case CountryCode.KZ:
return "KAZ";
case CountryCode.KY:
return "CYM";
case CountryCode.SG:
return "SGP";
case CountryCode.SE:
return "SWE";
case CountryCode.SD:
return "SDN";
case CountryCode.DO:
return "DOM";
case CountryCode.DM:
return "DMA";
case CountryCode.DJ:
return "DJI";
case CountryCode.DK:
return "DNK";
case CountryCode.VG:
return "VGB";
case CountryCode.DE:
return "DEU";
case CountryCode.YE:
return "YEM";
case CountryCode.DZ:
return "DZA";
case CountryCode.US:
return "USA";
case CountryCode.UY:
return "URY";
case CountryCode.YT:
return "MYT";
case CountryCode.UM:
return "UMI";
case CountryCode.LB:
return "LBN";
case CountryCode.LC:
return "LCA";
case CountryCode.LA:
return "LAO";
case CountryCode.TV:
return "TUV";
case CountryCode.TW:
return "TWN";
case CountryCode.TT:
return "TTO";
case CountryCode.TR:
return "TUR";
case CountryCode.LK:
return "LKA";
case CountryCode.LI:
return "LIE";
case CountryCode.LV:
return "LVA";
case CountryCode.TO:
return "TON";
case CountryCode.LT:
return "LTU";
case CountryCode.LU:
return "LUX";
case CountryCode.LR:
return "LBR";
case CountryCode.LS:
return "LSO";
case CountryCode.TH:
return "THA";
case CountryCode.TF:
return "ATF";
case CountryCode.TG:
return "TGO";
case CountryCode.TD:
return "TCD";
case CountryCode.TC:
return "TCA";
case CountryCode.LY:
return "LBY";
case CountryCode.VA:
return "VAT";
case CountryCode.VC:
return "VCT";
case CountryCode.AE:
return "ARE";
case CountryCode.AD:
return "AND";
case CountryCode.AG:
return "ATG";
case CountryCode.AF:
return "AFG";
case CountryCode.AI:
return "AIA";
case CountryCode.VI:
return "VIR";
case CountryCode.IS:
return "ISL";
case CountryCode.IR:
return "IRN";
case CountryCode.AM:
return "ARM";
case CountryCode.AL:
return "ALB";
case CountryCode.AO:
return "AGO";
case CountryCode.AQ:
return "ATA";
case CountryCode.AS:
return "ASM";
case CountryCode.AR:
return "ARG";
case CountryCode.AU:
return "AUS";
case CountryCode.AT:
return "AUT";
case CountryCode.AW:
return "ABW";
case CountryCode.IN:
return "IND";
case CountryCode.AX:
return "ALA";
case CountryCode.AZ:
return "AZE";
case CountryCode.IE:
return "IRL";
case CountryCode.ID:
return "IDN";
case CountryCode.UA:
return "UKR";
case CountryCode.QA:
return "QAT";
case CountryCode.MZ:
return "MOZ";
default:
throw new ArgumentOutOfRangeException(nameof(country));
}
}
}
}

View File

@ -22,7 +22,8 @@ public class TournamentUser : IUser
/// <summary>
/// The player's country.
/// </summary>
public Country? Country { get; set; }
[JsonProperty("country_code")]
public CountryCode CountryCode { get; set; }
/// <summary>
/// The player's global rank, or null if not available.
@ -40,7 +41,7 @@ public APIUser ToAPIUser()
{
Id = OnlineID,
Username = Username,
Country = Country,
CountryCode = CountryCode,
CoverUrl = CoverUrl,
};

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,13 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -25,9 +25,6 @@ namespace osu.Game.Tournament.Screens.Editors
{
public class TeamEditorScreen : TournamentEditorScreen<TeamEditorScreen.TeamRow, TournamentTeam>
{
[Resolved]
private TournamentGameBase game { get; set; }
protected override BindableList<TournamentTeam> Storage => LadderInfo.Teams;
[BackgroundDependencyLoader]
@ -45,11 +42,17 @@ private void load()
private void addAllCountries()
{
List<TournamentTeam> countries;
var countries = new List<TournamentTeam>();
using (Stream stream = game.Resources.GetStream("Resources/countries.json"))
using (var sr = new StreamReader(stream))
countries = JsonConvert.DeserializeObject<List<TournamentTeam>>(sr.ReadToEnd());
foreach (var country in Enum.GetValues(typeof(CountryCode)).Cast<CountryCode>().Skip(1))
{
countries.Add(new TournamentTeam
{
FlagName = { Value = country.ToString() },
FullName = { Value = country.GetDescription() },
Acronym = { Value = country.GetAcronym() },
});
}
Debug.Assert(countries != null);

View File

@ -21,6 +21,7 @@
using osu.Game.Tournament.IO;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tournament
@ -186,7 +187,9 @@ private bool addPlayers()
{
var playersRequiringPopulation = ladder.Teams
.SelectMany(t => t.Players)
.Where(p => string.IsNullOrEmpty(p.Username) || p.Rank == null).ToList();
.Where(p => string.IsNullOrEmpty(p.Username)
|| p.CountryCode == CountryCode.Unknown
|| p.Rank == null).ToList();
if (playersRequiringPopulation.Count == 0)
return false;
@ -288,7 +291,7 @@ void populate()
user.Username = res.Username;
user.CoverUrl = res.CoverUrl;
user.Country = res.Country;
user.CountryCode = res.CountryCode;
user.Rank = res.Statistics?.GlobalRank;
success?.Invoke();

View File

@ -169,8 +169,8 @@ private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func<IBeatmapMeta
Debug.Assert(x.BeatmapSet != null);
Debug.Assert(y.BeatmapSet != null);
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash;
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash;
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.Metadata))?.File.Hash;
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.Metadata))?.File.Hash;
return fileHashX == fileHashY;
}

View File

@ -3,10 +3,10 @@
#nullable disable
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Extensions;
namespace osu.Game.Beatmaps
{
@ -25,7 +25,7 @@ public BeatmapStatisticIcon(BeatmapStatisticsIconType iconType)
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().Kebaberize()}");
Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().ToKebabCase()}");
}
}

View File

@ -60,8 +60,9 @@ public class RealmAccess : IDisposable
/// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo.
/// 15 2022-07-13 Added LastPlayed to BeatmapInfo.
/// 16 2022-07-15 Removed HasReplay from ScoreInfo.
/// 17 2022-07-16 Added CountryCode to RealmUser.
/// </summary>
private const int schema_version = 16;
private const int schema_version = 17;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.

View File

@ -1,7 +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.
using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -67,7 +66,7 @@ public static void ApplySkinnableInfo(this Drawable component, SkinnableInfo inf
foreach (var (_, property) in component.GetSettingsSourceProperties())
{
if (!info.Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue))
continue;
skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue);

View File

@ -0,0 +1,94 @@
// 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.
// Based on code from the Humanizer library (https://github.com/Humanizr/Humanizer/blob/606e958cb83afc9be5b36716ac40d4daa9fa73a7/src/Humanizer/InflectorExtensions.cs)
//
// Humanizer is licenced under the MIT License (MIT)
//
// Copyright (c) .NET Foundation and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System.Text.RegularExpressions;
namespace osu.Game.Extensions
{
/// <summary>
/// Class with extension methods used to turn human-readable strings to casing conventions frequently used in code.
/// Often used for communicating with other systems (web API, spectator server).
/// All of the operations in this class are intentionally culture-invariant.
/// </summary>
public static class StringDehumanizeExtensions
{
/// <summary>
/// Converts the string to "Pascal case" (also known as "upper camel case").
/// </summary>
/// <example>
/// <code>
/// "this is a test string".ToPascalCase() == "ThisIsATestString"
/// </code>
/// </example>
public static string ToPascalCase(this string input)
{
return Regex.Replace(input, "(?:^|_|-| +)(.)", match => match.Groups[1].Value.ToUpperInvariant());
}
/// <summary>
/// Converts the string to (lower) "camel case".
/// </summary>
/// <example>
/// <code>
/// "this is a test string".ToCamelCase() == "thisIsATestString"
/// </code>
/// </example>
public static string ToCamelCase(this string input)
{
string word = input.ToPascalCase();
return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word;
}
/// <summary>
/// Converts the string to "snake case".
/// </summary>
/// <example>
/// <code>
/// "this is a test string".ToSnakeCase() == "this_is_a_test_string"
/// </code>
/// </example>
public static string ToSnakeCase(this string input)
{
return Regex.Replace(
Regex.Replace(
Regex.Replace(input, @"([\p{Lu}]+)([\p{Lu}][\p{Ll}])", "$1_$2"), @"([\p{Ll}\d])([\p{Lu}])", "$1_$2"), @"[-\s]", "_").ToLowerInvariant();
}
/// <summary>
/// Converts the string to "kebab case".
/// </summary>
/// <example>
/// <code>
/// "this is a test string".ToKebabCase() == "this-is-a-test-string"
/// </code>
/// </example>
public static string ToKebabCase(this string input)
{
return ToSnakeCase(input).Replace('_', '-');
}
}
}

View File

@ -3,8 +3,8 @@
#nullable disable
using Humanizer;
using Newtonsoft.Json.Serialization;
using osu.Game.Extensions;
namespace osu.Game.IO.Serialization
{
@ -12,7 +12,7 @@ public class SnakeCaseKeyContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
return propertyName.Underscore();
return propertyName.ToSnakeCase();
}
}
}

View File

@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using JetBrains.Annotations;
namespace osu.Game.Localisation
{
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public enum Language
{
[Description(@"English")]

View File

@ -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 osu.Game.Database;
using osu.Game.Users;
@ -17,6 +15,16 @@ public class RealmUser : EmbeddedObject, IUser, IEquatable<RealmUser>, IDeepClon
public string Username { get; set; } = string.Empty;
[Ignored]
public CountryCode CountryCode
{
get => Enum.TryParse(CountryString, out CountryCode country) ? country : CountryCode.Unknown;
set => CountryString = value.ToString();
}
[MapTo(nameof(CountryCode))]
public string CountryString { get; set; } = default(CountryCode).ToString();
public bool IsBot => false;
public bool Equals(RealmUser other)

View File

@ -5,13 +5,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Humanizer;
using MessagePack;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -44,11 +45,11 @@ public APIMod(Mod mod)
var bindable = (IBindable)property.GetValue(mod);
if (!bindable.IsDefault)
Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue());
Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue());
}
}
public Mod ToMod(Ruleset ruleset)
public Mod ToMod([NotNull] Ruleset ruleset)
{
Mod resultMod = ruleset.CreateModFromAcronym(Acronym);
@ -62,7 +63,7 @@ public Mod ToMod(Ruleset ruleset)
{
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
{
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
if (!Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue))
continue;
try

View File

@ -4,7 +4,7 @@
#nullable disable
using osu.Framework.IO.Network;
using Humanizer;
using osu.Game.Extensions;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Comments;
@ -32,7 +32,7 @@ protected override WebRequest CreateWebRequest()
var req = base.CreateWebRequest();
req.AddParameter("commentable_id", commentableId.ToString());
req.AddParameter("commentable_type", type.ToString().Underscore().ToLowerInvariant());
req.AddParameter("commentable_type", type.ToString().ToSnakeCase().ToLowerInvariant());
req.AddParameter("page", page.ToString());
req.AddParameter("sort", sort.ToString().ToLowerInvariant());

View File

@ -3,8 +3,8 @@
#nullable disable
using Humanizer;
using System.Collections.Generic;
using osu.Game.Extensions;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
@ -22,7 +22,7 @@ public GetUserBeatmapsRequest(long userId, BeatmapSetType type, PaginationParame
this.type = type;
}
protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}";
protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().ToSnakeCase()}";
}
public enum BeatmapSetType

View File

@ -5,6 +5,7 @@
using osu.Framework.IO.Network;
using osu.Game.Rulesets;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
@ -12,21 +13,21 @@ public class GetUserRankingsRequest : GetRankingsRequest<GetTopUsersResponse>
{
public readonly UserRankingsType Type;
private readonly string country;
private readonly CountryCode countryCode;
public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null)
public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = CountryCode.Unknown)
: base(ruleset, page)
{
Type = type;
this.country = country;
this.countryCode = countryCode;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
if (country != null)
req.AddParameter("country", country);
if (countryCode != CountryCode.Unknown)
req.AddParameter("country", countryCode.ToString());
return req;
}

View File

@ -10,7 +10,7 @@
namespace osu.Game.Online.API.Requests
{
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScore>>
public class GetUserScoresRequest : PaginatedAPIRequest<List<SoloScoreInfo>>
{
private readonly long userId;
private readonly ScoreType type;

View File

@ -4,8 +4,8 @@
#nullable disable
using System;
using Humanizer;
using Newtonsoft.Json;
using osu.Game.Extensions;
using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests.Responses
@ -21,7 +21,7 @@ public class APIRecentActivity
[JsonProperty]
private string type
{
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize());
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.ToPascalCase());
}
public RecentActivityType Type;

View File

@ -13,7 +13,7 @@ public class APIScoresCollection
[JsonProperty(@"scores")]
public List<SoloScoreInfo> Scores;
[JsonProperty(@"userScore")]
[JsonProperty(@"user_score")]
public APIScoreWithPosition UserScore;
}
}

View File

@ -34,8 +34,19 @@ public class APIUser : IEquatable<APIUser>, IUser
[JsonProperty(@"previous_usernames")]
public string[] PreviousUsernames;
private CountryCode? countryCode;
public CountryCode CountryCode
{
get => countryCode ??= (Enum.TryParse(country?.Code, out CountryCode result) ? result : default);
set => countryCode = value;
}
#pragma warning disable 649
[CanBeNull]
[JsonProperty(@"country")]
public Country Country;
private Country country;
#pragma warning restore 649
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
@ -256,5 +267,13 @@ private APIRankHistory rankHistory
public int OnlineID => Id;
public bool Equals(APIUser other) => this.MatchesOnlineID(other);
#pragma warning disable 649
private class Country
{
[JsonProperty(@"code")]
public string Code;
}
#pragma warning restore 649
}
}

View File

@ -54,7 +54,7 @@ public class SoloScoreInfo : IHasOnlineID<long>
public DateTimeOffset? StartedAt { get; set; }
[JsonProperty("ended_at")]
public DateTimeOffset? EndedAt { get; set; }
public DateTimeOffset EndedAt { get; set; }
[JsonProperty("mods")]
public APIMod[] Mods { get; set; } = Array.Empty<APIMod>();
@ -82,6 +82,23 @@ public class SoloScoreInfo : IHasOnlineID<long>
[JsonProperty("user")]
public APIUser? User { get; set; }
[JsonProperty("beatmap")]
public APIBeatmap? Beatmap { get; set; }
[JsonProperty("beatmapset")]
public APIBeatmapSet? BeatmapSet
{
set
{
// in the deserialisation case we need to ferry this data across.
// the order of properties returned by the API guarantees that the beatmap is populated by this point.
if (!(Beatmap is APIBeatmap apiBeatmap))
throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response");
apiBeatmap.BeatmapSet = value;
}
}
[JsonProperty("pp")]
public double? PP { get; set; }
@ -128,7 +145,7 @@ public ScoreInfo ToScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null)
MaxCombo = MaxCombo,
Rank = Rank,
Statistics = Statistics,
Date = EndedAt ?? DateTimeOffset.Now,
Date = EndedAt,
Hash = HasReplay ? "online" : string.Empty, // TODO: temporary?
Mods = mods,
PP = PP,

View File

@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using JetBrains.Annotations;
using osu.Framework.IO.Network;
using osu.Game.Extensions;
@ -86,7 +85,7 @@ protected override WebRequest CreateWebRequest()
req.AddParameter("q", query);
if (General != null && General.Any())
req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore())));
req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToSnakeCase())));
if (ruleset.OnlineID >= 0)
req.AddParameter("m", ruleset.OnlineID.ToString());

View File

@ -64,26 +64,26 @@ public HubClientConnector(string clientName, string endpoint, IAPIProvider api,
this.preferMessagePack = preferMessagePack;
apiState.BindTo(api.State);
apiState.BindValueChanged(_ => connectIfPossible(), true);
apiState.BindValueChanged(_ => Task.Run(connectIfPossible), true);
}
public void Reconnect()
public Task Reconnect()
{
Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network);
Task.Run(connectIfPossible);
return Task.Run(connectIfPossible);
}
private void connectIfPossible()
private async Task connectIfPossible()
{
switch (apiState.Value)
{
case APIState.Failing:
case APIState.Offline:
Task.Run(() => disconnect(true));
await disconnect(true);
break;
case APIState.Online:
Task.Run(connect);
await connect();
break;
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Bindables;
using osu.Game.Online.API;
@ -32,6 +33,6 @@ public interface IHubClientConnector : IDisposable
/// <summary>
/// Reconnect if already connected.
/// </summary>
void Reconnect();
Task Reconnect();
}
}

View File

@ -181,7 +181,7 @@ private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager)
Masking = true,
Children = new Drawable[]
{
new UpdateableFlag(user.Country)
new UpdateableFlag(user.CountryCode)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,

View File

@ -21,7 +21,6 @@
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Online.Multiplayer
{
@ -91,7 +90,7 @@ public abstract class MultiplayerClient : Component, IMultiplayerClient, IMultip
/// <summary>
/// The joined <see cref="MultiplayerRoom"/>.
/// </summary>
public virtual MultiplayerRoom? Room
public virtual MultiplayerRoom? Room // virtual for moq
{
get
{
@ -150,7 +149,7 @@ private void load()
// clean up local room state on server disconnect.
if (!connected.NewValue && Room != null)
{
Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important);
Logger.Log("Clearing room due to multiplayer server connection loss.", LoggingTarget.Runtime, LogLevel.Important);
LeaveRoom();
}
}));

View File

@ -85,7 +85,13 @@ protected override async Task<MultiplayerRoom> JoinRoom(long roomId, string? pas
catch (HubException exception)
{
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
connector?.Reconnect();
{
Debug.Assert(connector != null);
await connector.Reconnect();
return await JoinRoom(roomId, password);
}
throw;
}
}

View File

@ -4,8 +4,8 @@
#nullable disable
using System.Collections.Generic;
using Humanizer;
using osu.Framework.IO.Network;
using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
@ -27,7 +27,7 @@ protected override WebRequest CreateWebRequest()
var req = base.CreateWebRequest();
if (status != RoomStatusFilter.Open)
req.AddParameter("mode", status.ToString().Underscore().ToLowerInvariant());
req.AddParameter("mode", status.ToString().ToSnakeCase().ToLowerInvariant());
if (!string.IsNullOrEmpty(category))
req.AddParameter("category", category);

View File

@ -56,13 +56,20 @@ protected override async Task BeginPlayingInternal(SpectatorState state)
try
{
await connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state);
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state);
}
catch (HubException exception)
{
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
connector?.Reconnect();
throw;
{
Debug.Assert(connector != null);
await connector.Reconnect();
await BeginPlayingInternal(state);
}
// Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart.
// For now, let's ignore these so they don't cause unobserved exceptions to appear to the user (and sentry).
}
}
@ -73,7 +80,7 @@ protected override Task SendFramesInternal(FrameDataBundle bundle)
Debug.Assert(connection != null);
return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle);
return connection.InvokeAsync(nameof(ISpectatorServer.SendFrameData), bundle);
}
protected override Task EndPlayingInternal(SpectatorState state)
@ -83,7 +90,7 @@ protected override Task EndPlayingInternal(SpectatorState state)
Debug.Assert(connection != null);
return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state);
return connection.InvokeAsync(nameof(ISpectatorServer.EndPlaySession), state);
}
protected override Task WatchUserInternal(int userId)
@ -93,7 +100,7 @@ protected override Task WatchUserInternal(int userId)
Debug.Assert(connection != null);
return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId);
return connection.InvokeAsync(nameof(ISpectatorServer.StartWatchingUser), userId);
}
protected override Task StopWatchingUserInternal(int userId)
@ -103,7 +110,7 @@ protected override Task StopWatchingUserInternal(int userId)
Debug.Assert(connection != null);
return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
}
}
}

View File

@ -165,10 +165,10 @@ private Drawable[] createContent(int index, ScoreInfo score)
Font = OsuFont.GetFont(size: text_size),
Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White
},
new UpdateableFlag(score.User.Country)
new UpdateableFlag(score.User.CountryCode)
{
Size = new Vector2(19, 14),
ShowPlaceholderOnNull = false,
ShowPlaceholderOnUnknown = false,
},
username,
new OsuSpriteText

View File

@ -120,7 +120,7 @@ public TopScoreUserSection()
Origin = Anchor.CentreLeft,
Size = new Vector2(19, 14),
Margin = new MarginPadding { Top = 3 }, // makes spacing look more even
ShowPlaceholderOnNull = false,
ShowPlaceholderOnUnknown = false,
},
}
}
@ -141,7 +141,7 @@ public ScoreInfo Score
set
{
avatar.User = value.User;
flag.Country = value.User.Country;
flag.CountryCode = value.User.CountryCode;
achievedOn.Date = value.Date;
usernameText.Clear();

View File

@ -257,10 +257,14 @@ public MessageSender(APIUser sender)
}
[BackgroundDependencyLoader]
private void load(UserProfileOverlay? profile, ChannelManager? chatManager)
private void load(UserProfileOverlay? profile, ChannelManager? chatManager, ChatOverlay? chatOverlay)
{
Action = () => profile?.ShowUser(sender);
startChatAction = () => chatManager?.OpenPrivateChannel(sender);
startChatAction = () =>
{
chatManager?.OpenPrivateChannel(sender);
chatOverlay?.Show();
};
}
public MenuItem[] ContextMenuItems

View File

@ -5,6 +5,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
@ -136,7 +137,7 @@ private void load(OverlayColourProvider colourProvider)
userFlag = new UpdateableFlag
{
Size = new Vector2(28, 20),
ShowPlaceholderOnNull = false,
ShowPlaceholderOnUnknown = false,
},
userCountryText = new OsuSpriteText
{
@ -174,8 +175,8 @@ private void updateUser(APIUser user)
avatar.User = user;
usernameText.Text = user?.Username ?? string.Empty;
openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}";
userFlag.Country = user?.Country;
userCountryText.Text = user?.Country?.FullName ?? "Alien";
userFlag.CountryCode = user?.CountryCode ?? default;
userCountryText.Text = (user?.CountryCode ?? default).GetDescription();
supporterTag.SupportLevel = user?.SupportLevel ?? 0;
titleText.Text = user?.Title ?? string.Empty;
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");

View File

@ -31,7 +31,7 @@ public class DrawableProfileScore : CompositeDrawable
private const float performance_background_shear = 0.45f;
protected readonly APIScore Score;
protected readonly SoloScoreInfo Score;
[Resolved]
private OsuColour colours { get; set; }
@ -39,7 +39,7 @@ public class DrawableProfileScore : CompositeDrawable
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
public DrawableProfileScore(APIScore score)
public DrawableProfileScore(SoloScoreInfo score)
{
Score = score;
@ -98,7 +98,7 @@ private void load(RulesetStore rulesets)
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
Colour = colours.Yellow
},
new DrawableDate(Score.Date, 12)
new DrawableDate(Score.EndedAt, 12)
{
Colour = colourProvider.Foreground1
}
@ -138,7 +138,7 @@ private void load(RulesetStore rulesets)
{
var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally");
return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym))
return new ModIcon(mod.ToMod(ruleset.CreateInstance()))
{
Scale = new Vector2(0.35f)
};

View File

@ -18,7 +18,7 @@ public class DrawableProfileWeightedScore : DrawableProfileScore
{
private readonly double weight;
public DrawableProfileWeightedScore(APIScore score, double weight)
public DrawableProfileWeightedScore(SoloScoreInfo score, double weight)
: base(score)
{
this.weight = weight;

View File

@ -17,7 +17,7 @@
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScore>
public class PaginatedScoreContainer : PaginatedProfileSubsection<SoloScoreInfo>
{
private readonly ScoreType type;
@ -54,7 +54,7 @@ protected override int GetCount(APIUser user)
}
}
protected override void OnItemsReceived(List<APIScore> items)
protected override void OnItemsReceived(List<SoloScoreInfo> items)
{
if (CurrentPage == null || CurrentPage?.Offset == 0)
drawableItemIndex = 0;
@ -62,12 +62,12 @@ protected override void OnItemsReceived(List<APIScore> items)
base.OnItemsReceived(items);
}
protected override APIRequest<List<APIScore>> CreateRequest(PaginationParameters pagination) =>
protected override APIRequest<List<SoloScoreInfo>> CreateRequest(PaginationParameters pagination) =>
new GetUserScoresRequest(User.Value.Id, type, pagination);
private int drawableItemIndex;
protected override Drawable CreateDrawableItem(APIScore model)
protected override Drawable CreateDrawableItem(SoloScoreInfo model)
{
switch (type)
{

View File

@ -16,14 +16,14 @@
namespace osu.Game.Overlays.Rankings
{
public class CountryFilter : CompositeDrawable, IHasCurrentValue<Country>
public class CountryFilter : CompositeDrawable, IHasCurrentValue<CountryCode>
{
private const int duration = 200;
private const int height = 70;
private readonly BindableWithCurrent<Country> current = new BindableWithCurrent<Country>();
private readonly BindableWithCurrent<CountryCode> current = new BindableWithCurrent<CountryCode>();
public Bindable<Country> Current
public Bindable<CountryCode> Current
{
get => current.Current;
set => current.Current = value;
@ -89,9 +89,9 @@ protected override void LoadComplete()
Current.BindValueChanged(onCountryChanged, true);
}
private void onCountryChanged(ValueChangedEvent<Country> country)
private void onCountryChanged(ValueChangedEvent<CountryCode> country)
{
if (country.NewValue == null)
if (Current.Value == CountryCode.Unknown)
{
countryPill.Collapse();
this.ResizeHeightTo(0, duration, Easing.OutQuint);

View File

@ -6,6 +6,7 @@
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -21,13 +22,13 @@
namespace osu.Game.Overlays.Rankings
{
public class CountryPill : CompositeDrawable, IHasCurrentValue<Country>
public class CountryPill : CompositeDrawable, IHasCurrentValue<CountryCode>
{
private const int duration = 200;
private readonly BindableWithCurrent<Country> current = new BindableWithCurrent<Country>();
private readonly BindableWithCurrent<CountryCode> current = new BindableWithCurrent<CountryCode>();
public Bindable<Country> Current
public Bindable<CountryCode> Current
{
get => current.Current;
set => current.Current = value;
@ -93,7 +94,7 @@ public CountryPill()
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => Current.Value = null
Action = Current.SetDefault,
}
}
}
@ -130,13 +131,13 @@ public void Collapse()
this.FadeOut(duration, Easing.OutQuint);
}
private void onCountryChanged(ValueChangedEvent<Country> country)
private void onCountryChanged(ValueChangedEvent<CountryCode> country)
{
if (country.NewValue == null)
if (Current.Value == CountryCode.Unknown)
return;
flag.Country = country.NewValue;
countryName.Text = country.NewValue.FullName;
flag.CountryCode = country.NewValue;
countryName.Text = country.NewValue.GetDescription();
}
private class CloseButton : OsuHoverContainer

View File

@ -16,7 +16,7 @@ public class RankingsOverlayHeader : TabControlOverlayHeader<RankingsScope>
{
public Bindable<RulesetInfo> Ruleset => rulesetSelector.Current;
public Bindable<Country> Country => countryFilter.Current;
public Bindable<CountryCode> Country => countryFilter.Current;
private OverlayRulesetSelector rulesetSelector;
private CountryFilter countryFilter;

View File

@ -11,6 +11,7 @@
using osu.Game.Graphics.Containers;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Resources.Localisation.Web;
@ -33,9 +34,9 @@ protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
};
protected override Country GetCountry(CountryStatistics item) => item.Country;
protected override CountryCode GetCountryCode(CountryStatistics item) => item.Code;
protected override Drawable CreateFlagContent(CountryStatistics item) => new CountryName(item.Country);
protected override Drawable CreateFlagContent(CountryStatistics item) => new CountryName(item.Code);
protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[]
{
@ -70,15 +71,15 @@ private class CountryName : LinkFlowContainer
[Resolved(canBeNull: true)]
private RankingsOverlay rankings { get; set; }
public CountryName(Country country)
public CountryName(CountryCode countryCode)
: base(t => t.Font = OsuFont.GetFont(size: 12))
{
AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
TextAnchor = Anchor.CentreLeft;
if (!string.IsNullOrEmpty(country.FullName))
AddLink(country.FullName, () => rankings?.ShowCountry(country));
if (countryCode != CountryCode.Unknown)
AddLink(countryCode.GetDescription(), () => rankings?.ShowCountry(countryCode));
}
}
}

View File

@ -79,7 +79,7 @@ private void load()
protected sealed override Drawable CreateHeader(int index, TableColumn column)
=> (column as RankingsTableColumn)?.CreateHeaderText() ?? new HeaderText(column?.Header ?? default, false);
protected abstract Country GetCountry(TModel item);
protected abstract CountryCode GetCountryCode(TModel item);
protected abstract Drawable CreateFlagContent(TModel item);
@ -97,10 +97,10 @@ protected sealed override Drawable CreateHeader(int index, TableColumn column)
Margin = new MarginPadding { Bottom = row_spacing },
Children = new[]
{
new UpdateableFlag(GetCountry(item))
new UpdateableFlag(GetCountryCode(item))
{
Size = new Vector2(28, 20),
ShowPlaceholderOnNull = false,
ShowPlaceholderOnUnknown = false,
},
CreateFlagContent(item)
}

View File

@ -59,7 +59,7 @@ protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
.Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))))
.ToArray();
protected sealed override Country GetCountry(UserStatistics item) => item.User.Country;
protected sealed override CountryCode GetCountryCode(UserStatistics item) => item.User.CountryCode;
protected sealed override Drawable CreateFlagContent(UserStatistics item)
{

View File

@ -17,7 +17,7 @@ namespace osu.Game.Overlays
{
public class RankingsOverlay : TabbableOnlineOverlay<RankingsOverlayHeader, RankingsScope>
{
protected Bindable<Country> Country => Header.Country;
protected Bindable<CountryCode> Country => Header.Country;
private APIRequest lastRequest;
@ -44,7 +44,7 @@ protected override void LoadComplete()
Country.BindValueChanged(_ =>
{
// if a country is requested, force performance scope.
if (Country.Value != null)
if (!Country.IsDefault)
Header.Current.Value = RankingsScope.Performance;
Scheduler.AddOnce(triggerTabChanged);
@ -76,7 +76,7 @@ protected override void OnTabChanged(RankingsScope tab)
{
// country filtering is only valid for performance scope.
if (Header.Current.Value != RankingsScope.Performance)
Country.Value = null;
Country.SetDefault();
Scheduler.AddOnce(triggerTabChanged);
}
@ -85,9 +85,9 @@ protected override void OnTabChanged(RankingsScope tab)
protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader();
public void ShowCountry(Country requested)
public void ShowCountry(CountryCode requested)
{
if (requested == null)
if (requested == default)
return;
Show();
@ -128,7 +128,7 @@ private APIRequest createScopedRequest()
switch (Header.Current.Value)
{
case RankingsScope.Performance:
return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName);
return new GetUserRankingsRequest(ruleset.Value, countryCode: Country.Value);
case RankingsScope.Country:
return new GetCountryRankingsRequest(ruleset.Value);

View File

@ -58,6 +58,7 @@ public ModReplayData(Replay replay, ModCreatedUser user = null)
public class ModCreatedUser : IUser
{
public int OnlineID => APIUser.SYSTEM_USER_ID;
public CountryCode CountryCode => default;
public bool IsBot => true;
public string Username { get; set; } = string.Empty;

View File

@ -0,0 +1,52 @@
// 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 JetBrains.Annotations;
using osu.Framework.Bindables;
namespace osu.Game.Rulesets.Objects
{
/// <summary>
/// Represents a wrapper containing a lazily-initialised <see cref="Bindable{T}"/>, backed by a temporary field used for <see cref="Value"/> storage until initialisation.
/// </summary>
public struct HitObjectProperty<T>
{
[CanBeNull]
private Bindable<T> backingBindable;
/// <summary>
/// A temporary field to store the current value to, prior to <see cref="Bindable"/>'s initialisation.
/// </summary>
private T backingValue;
/// <summary>
/// The underlying <see cref="Bindable{T}"/>, only initialised on first access.
/// </summary>
public Bindable<T> Bindable => backingBindable ??= new Bindable<T>(defaultValue) { Value = backingValue };
/// <summary>
/// The current value, derived from and delegated to <see cref="Bindable"/> if initialised, or a temporary field otherwise.
/// </summary>
public T Value
{
get => backingBindable != null ? backingBindable.Value : backingValue;
set
{
if (backingBindable != null)
backingBindable.Value = value;
else
backingValue = value;
}
}
private readonly T defaultValue;
public HitObjectProperty(T value = default)
{
backingValue = defaultValue = value;
backingBindable = null;
}
}
}

View File

@ -84,7 +84,7 @@ protected override void PostImport(ScoreInfo model, Realm realm)
api.Perform(userRequest);
if (userRequest.Response is APIUser user)
model.RealmUser.OnlineID = user.Id;
model.User = user;
}
}
}

View File

@ -85,8 +85,9 @@ public APIUser User
{
get => user ??= new APIUser
{
Username = RealmUser.Username,
Id = RealmUser.OnlineID,
Username = RealmUser.Username,
CountryCode = RealmUser.CountryCode,
};
set
{
@ -95,7 +96,8 @@ public APIUser User
RealmUser = new RealmUser
{
OnlineID = user.OnlineID,
Username = user.Username
Username = user.Username,
CountryCode = user.CountryCode,
};
}
}
@ -135,6 +137,7 @@ public ScoreInfo DeepClone()
{
OnlineID = RealmUser.OnlineID,
Username = RealmUser.Username,
CountryCode = RealmUser.CountryCode,
};
return clone;

View File

@ -70,12 +70,8 @@ protected override void LoadComplete()
client.LoadRequested += onLoadRequested;
client.RoomUpdated += onRoomUpdated;
isConnected.BindTo(client.IsConnected);
isConnected.BindValueChanged(connected =>
{
if (!connected.NewValue)
handleRoomLost();
}, true);
if (!client.IsConnected.Value)
handleRoomLost();
}
protected override Drawable CreateMainContent() => new Container

View File

@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@ -22,6 +21,7 @@
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osu.Game.Users.Drawables;
@ -35,18 +35,18 @@ public class ParticipantPanel : MultiplayerRoomComposite, IHasContextMenu
public readonly MultiplayerRoomUser User;
[Resolved]
private IAPIProvider api { get; set; }
private IAPIProvider api { get; set; } = null!;
[Resolved]
private IRulesetStore rulesets { get; set; }
private IRulesetStore rulesets { get; set; } = null!;
private SpriteIcon crown;
private SpriteIcon crown = null!;
private OsuSpriteText userRankText;
private ModDisplay userModsDisplay;
private StateDisplay userStateDisplay;
private OsuSpriteText userRankText = null!;
private ModDisplay userModsDisplay = null!;
private StateDisplay userStateDisplay = null!;
private IconButton kickButton;
private IconButton kickButton = null!;
public ParticipantPanel(MultiplayerRoomUser user)
{
@ -128,14 +128,14 @@ private void load()
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(28, 20),
Country = user?.Country
CountryCode = user?.CountryCode ?? default
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
Text = user?.Username
Text = user?.Username ?? string.Empty
},
userRankText = new OsuSpriteText
{
@ -188,7 +188,7 @@ protected override void OnRoomUpdated()
const double fade_time = 50;
var currentItem = Playlist.GetCurrentItem();
var ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null;
Ruleset? ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null;
int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
@ -205,10 +205,13 @@ protected override void OnRoomUpdated()
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList());
Schedule(() =>
{
userModsDisplay.Current.Value = ruleset != null ? User.Mods.Select(m => m.ToMod(ruleset)).ToList() : Array.Empty<Mod>();
});
}
public MenuItem[] ContextMenuItems
public MenuItem[]? ContextMenuItems
{
get
{

View File

@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -71,7 +70,7 @@ public SkinnableInfo(Drawable component)
var bindable = (IBindable)property.GetValue(component);
if (!bindable.IsDefault)
Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue());
Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue());
}
if (component is Container<Drawable> container)

View File

@ -6,6 +6,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@ -214,7 +215,7 @@ private Drawable createStatistic(HitResultDisplayStatistic result)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Text = key,
Text = key.ToTitle(),
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)
},
new OsuSpriteText

View File

@ -37,7 +37,7 @@ public RetryButton()
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(13),
Icon = FontAwesome.Solid.ArrowCircleLeft,
Icon = FontAwesome.Solid.Redo,
},
};

View File

@ -96,7 +96,7 @@ private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) =>
new SkinInfo
{
Name = beatmapInfo.ToString(),
Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty
Creator = beatmapInfo.Metadata.Author.Username
};
}
}

View File

@ -1,27 +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 System;
using Newtonsoft.Json;
namespace osu.Game.Users
{
public class Country : IEquatable<Country>
{
/// <summary>
/// The name of this country.
/// </summary>
[JsonProperty(@"name")]
public string FullName;
/// <summary>
/// Two-letter flag acronym (ISO 3166 standard)
/// </summary>
[JsonProperty(@"code")]
public string FlagName;
public bool Equals(Country other) => FlagName == other?.FlagName;
}
}

View File

@ -0,0 +1,768 @@
// 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.ComponentModel;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace osu.Game.Users
{
[JsonConverter(typeof(StringEnumConverter))]
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public enum CountryCode
{
[Description("Unknown")]
Unknown = 0,
[Description("Bangladesh")]
BD,
[Description("Belgium")]
BE,
[Description("Burkina Faso")]
BF,
[Description("Bulgaria")]
BG,
[Description("Bosnia and Herzegovina")]
BA,
[Description("Barbados")]
BB,
[Description("Wallis and Futuna")]
WF,
[Description("Saint Barthelemy")]
BL,
[Description("Bermuda")]
BM,
[Description("Brunei")]
BN,
[Description("Bolivia")]
BO,
[Description("Bahrain")]
BH,
[Description("Burundi")]
BI,
[Description("Benin")]
BJ,
[Description("Bhutan")]
BT,
[Description("Jamaica")]
JM,
[Description("Bouvet Island")]
BV,
[Description("Botswana")]
BW,
[Description("Samoa")]
WS,
[Description("Bonaire, Saint Eustatius and Saba")]
BQ,
[Description("Brazil")]
BR,
[Description("Bahamas")]
BS,
[Description("Jersey")]
JE,
[Description("Belarus")]
BY,
[Description("Belize")]
BZ,
[Description("Russia")]
RU,
[Description("Rwanda")]
RW,
[Description("Serbia")]
RS,
[Description("East Timor")]
TL,
[Description("Reunion")]
RE,
[Description("Turkmenistan")]
TM,
[Description("Tajikistan")]
TJ,
[Description("Romania")]
RO,
[Description("Tokelau")]
TK,
[Description("Guinea-Bissau")]
GW,
[Description("Guam")]
GU,
[Description("Guatemala")]
GT,
[Description("South Georgia and the South Sandwich Islands")]
GS,
[Description("Greece")]
GR,
[Description("Equatorial Guinea")]
GQ,
[Description("Guadeloupe")]
GP,
[Description("Japan")]
JP,
[Description("Guyana")]
GY,
[Description("Guernsey")]
GG,
[Description("French Guiana")]
GF,
[Description("Georgia")]
GE,
[Description("Grenada")]
GD,
[Description("United Kingdom")]
GB,
[Description("Gabon")]
GA,
[Description("El Salvador")]
SV,
[Description("Guinea")]
GN,
[Description("Gambia")]
GM,
[Description("Greenland")]
GL,
[Description("Gibraltar")]
GI,
[Description("Ghana")]
GH,
[Description("Oman")]
OM,
[Description("Tunisia")]
TN,
[Description("Jordan")]
JO,
[Description("Croatia")]
HR,
[Description("Haiti")]
HT,
[Description("Hungary")]
HU,
[Description("Hong Kong")]
HK,
[Description("Honduras")]
HN,
[Description("Heard Island and McDonald Islands")]
HM,
[Description("Venezuela")]
VE,
[Description("Puerto Rico")]
PR,
[Description("Palestinian Territory")]
PS,
[Description("Palau")]
PW,
[Description("Portugal")]
PT,
[Description("Svalbard and Jan Mayen")]
SJ,
[Description("Paraguay")]
PY,
[Description("Iraq")]
IQ,
[Description("Panama")]
PA,
[Description("French Polynesia")]
PF,
[Description("Papua New Guinea")]
PG,
[Description("Peru")]
PE,
[Description("Pakistan")]
PK,
[Description("Philippines")]
PH,
[Description("Pitcairn")]
PN,
[Description("Poland")]
PL,
[Description("Saint Pierre and Miquelon")]
PM,
[Description("Zambia")]
ZM,
[Description("Western Sahara")]
EH,
[Description("Estonia")]
EE,
[Description("Egypt")]
EG,
[Description("South Africa")]
ZA,
[Description("Ecuador")]
EC,
[Description("Italy")]
IT,
[Description("Vietnam")]
VN,
[Description("Solomon Islands")]
SB,
[Description("Ethiopia")]
ET,
[Description("Somalia")]
SO,
[Description("Zimbabwe")]
ZW,
[Description("Saudi Arabia")]
SA,
[Description("Spain")]
ES,
[Description("Eritrea")]
ER,
[Description("Montenegro")]
ME,
[Description("Moldova")]
MD,
[Description("Madagascar")]
MG,
[Description("Saint Martin")]
MF,
[Description("Morocco")]
MA,
[Description("Monaco")]
MC,
[Description("Uzbekistan")]
UZ,
[Description("Myanmar")]
MM,
[Description("Mali")]
ML,
[Description("Macao")]
MO,
[Description("Mongolia")]
MN,
[Description("Marshall Islands")]
MH,
[Description("North Macedonia")]
MK,
[Description("Mauritius")]
MU,
[Description("Malta")]
MT,
[Description("Malawi")]
MW,
[Description("Maldives")]
MV,
[Description("Martinique")]
MQ,
[Description("Northern Mariana Islands")]
MP,
[Description("Montserrat")]
MS,
[Description("Mauritania")]
MR,
[Description("Isle of Man")]
IM,
[Description("Uganda")]
UG,
[Description("Tanzania")]
TZ,
[Description("Malaysia")]
MY,
[Description("Mexico")]
MX,
[Description("Israel")]
IL,
[Description("France")]
FR,
[Description("British Indian Ocean Territory")]
IO,
[Description("Saint Helena")]
SH,
[Description("Finland")]
FI,
[Description("Fiji")]
FJ,
[Description("Falkland Islands")]
FK,
[Description("Micronesia")]
FM,
[Description("Faroe Islands")]
FO,
[Description("Nicaragua")]
NI,
[Description("Netherlands")]
NL,
[Description("Norway")]
NO,
[Description("Namibia")]
NA,
[Description("Vanuatu")]
VU,
[Description("New Caledonia")]
NC,
[Description("Niger")]
NE,
[Description("Norfolk Island")]
NF,
[Description("Nigeria")]
NG,
[Description("New Zealand")]
NZ,
[Description("Nepal")]
NP,
[Description("Nauru")]
NR,
[Description("Niue")]
NU,
[Description("Cook Islands")]
CK,
[Description("Kosovo")]
XK,
[Description("Ivory Coast")]
CI,
[Description("Switzerland")]
CH,
[Description("Colombia")]
CO,
[Description("China")]
CN,
[Description("Cameroon")]
CM,
[Description("Chile")]
CL,
[Description("Cocos Islands")]
CC,
[Description("Canada")]
CA,
[Description("Republic of the Congo")]
CG,
[Description("Central African Republic")]
CF,
[Description("Democratic Republic of the Congo")]
CD,
[Description("Czech Republic")]
CZ,
[Description("Cyprus")]
CY,
[Description("Christmas Island")]
CX,
[Description("Costa Rica")]
CR,
[Description("Curacao")]
CW,
[Description("Cabo Verde")]
CV,
[Description("Cuba")]
CU,
[Description("Eswatini")]
SZ,
[Description("Syria")]
SY,
[Description("Sint Maarten")]
SX,
[Description("Kyrgyzstan")]
KG,
[Description("Kenya")]
KE,
[Description("South Sudan")]
SS,
[Description("Suriname")]
SR,
[Description("Kiribati")]
KI,
[Description("Cambodia")]
KH,
[Description("Saint Kitts and Nevis")]
KN,
[Description("Comoros")]
KM,
[Description("Sao Tome and Principe")]
ST,
[Description("Slovakia")]
SK,
[Description("South Korea")]
KR,
[Description("Slovenia")]
SI,
[Description("North Korea")]
KP,
[Description("Kuwait")]
KW,
[Description("Senegal")]
SN,
[Description("San Marino")]
SM,
[Description("Sierra Leone")]
SL,
[Description("Seychelles")]
SC,
[Description("Kazakhstan")]
KZ,
[Description("Cayman Islands")]
KY,
[Description("Singapore")]
SG,
[Description("Sweden")]
SE,
[Description("Sudan")]
SD,
[Description("Dominican Republic")]
DO,
[Description("Dominica")]
DM,
[Description("Djibouti")]
DJ,
[Description("Denmark")]
DK,
[Description("British Virgin Islands")]
VG,
[Description("Germany")]
DE,
[Description("Yemen")]
YE,
[Description("Algeria")]
DZ,
[Description("United States")]
US,
[Description("Uruguay")]
UY,
[Description("Mayotte")]
YT,
[Description("United States Minor Outlying Islands")]
UM,
[Description("Lebanon")]
LB,
[Description("Saint Lucia")]
LC,
[Description("Laos")]
LA,
[Description("Tuvalu")]
TV,
[Description("Taiwan")]
TW,
[Description("Trinidad and Tobago")]
TT,
[Description("Turkey")]
TR,
[Description("Sri Lanka")]
LK,
[Description("Liechtenstein")]
LI,
[Description("Latvia")]
LV,
[Description("Tonga")]
TO,
[Description("Lithuania")]
LT,
[Description("Luxembourg")]
LU,
[Description("Liberia")]
LR,
[Description("Lesotho")]
LS,
[Description("Thailand")]
TH,
[Description("French Southern Territories")]
TF,
[Description("Togo")]
TG,
[Description("Chad")]
TD,
[Description("Turks and Caicos Islands")]
TC,
[Description("Libya")]
LY,
[Description("Vatican")]
VA,
[Description("Saint Vincent and the Grenadines")]
VC,
[Description("United Arab Emirates")]
AE,
[Description("Andorra")]
AD,
[Description("Antigua and Barbuda")]
AG,
[Description("Afghanistan")]
AF,
[Description("Anguilla")]
AI,
[Description("U.S. Virgin Islands")]
VI,
[Description("Iceland")]
IS,
[Description("Iran")]
IR,
[Description("Armenia")]
AM,
[Description("Albania")]
AL,
[Description("Angola")]
AO,
[Description("Antarctica")]
AQ,
[Description("American Samoa")]
AS,
[Description("Argentina")]
AR,
[Description("Australia")]
AU,
[Description("Austria")]
AT,
[Description("Aruba")]
AW,
[Description("India")]
IN,
[Description("Aland Islands")]
AX,
[Description("Azerbaijan")]
AZ,
[Description("Ireland")]
IE,
[Description("Indonesia")]
ID,
[Description("Ukraine")]
UA,
[Description("Qatar")]
QA,
[Description("Mozambique")]
MZ,
}
}

View File

@ -9,11 +9,8 @@ namespace osu.Game.Users
{
public class CountryStatistics
{
[JsonProperty]
public Country Country;
[JsonProperty(@"code")]
public string FlagName;
public CountryCode Code;
[JsonProperty(@"active_users")]
public long ActiveUsers;

View File

@ -5,6 +5,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
@ -14,13 +15,13 @@ namespace osu.Game.Users.Drawables
{
public class DrawableFlag : Sprite, IHasTooltip
{
private readonly Country country;
private readonly CountryCode countryCode;
public LocalisableString TooltipText => country?.FullName;
public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription();
public DrawableFlag(Country country)
public DrawableFlag(CountryCode countryCode)
{
this.country = country;
this.countryCode = countryCode;
}
[BackgroundDependencyLoader]
@ -29,7 +30,8 @@ private void load(TextureStore ts)
if (ts == null)
throw new ArgumentNullException(nameof(ts));
Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}") ?? ts.Get(@"Flags/__");
string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString();
Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__");
}
}
}

View File

@ -13,18 +13,18 @@
namespace osu.Game.Users.Drawables
{
public class UpdateableFlag : ModelBackedDrawable<Country>
public class UpdateableFlag : ModelBackedDrawable<CountryCode>
{
public Country Country
public CountryCode CountryCode
{
get => Model;
set => Model = value;
}
/// <summary>
/// Whether to show a place holder on null country.
/// Whether to show a place holder on unknown country.
/// </summary>
public bool ShowPlaceholderOnNull = true;
public bool ShowPlaceholderOnUnknown = true;
/// <summary>
/// Perform an action in addition to showing the country ranking.
@ -32,14 +32,14 @@ public Country Country
/// </summary>
public Action Action;
public UpdateableFlag(Country country = null)
public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown)
{
Country = country;
CountryCode = countryCode;
}
protected override Drawable CreateDrawable(Country country)
protected override Drawable CreateDrawable(CountryCode countryCode)
{
if (country == null && !ShowPlaceholderOnNull)
if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown)
return null;
return new Container
@ -47,7 +47,7 @@ protected override Drawable CreateDrawable(Country country)
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new DrawableFlag(country)
new DrawableFlag(countryCode)
{
RelativeSizeAxes = Axes.Both
},
@ -62,7 +62,7 @@ protected override Drawable CreateDrawable(Country country)
protected override bool OnClick(ClickEvent e)
{
Action?.Invoke();
rankingsOverlay?.ShowCountry(Country);
rankingsOverlay?.ShowCountry(CountryCode);
return true;
}
}

View File

@ -53,7 +53,7 @@ protected override void LoadComplete()
protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false);
protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country)
protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode)
{
Size = new Vector2(36, 26),
Action = Action,

View File

@ -10,6 +10,8 @@ public interface IUser : IHasOnlineID<int>, IEquatable<IUser>
{
string Username { get; }
CountryCode CountryCode { get; }
bool IsBot { get; }
bool IEquatable<IUser>.Equals(IUser? other)

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.14.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.715.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.719.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
<PackageReference Include="Sentry" Version="3.19.0" />
<PackageReference Include="SharpCompress" Version="0.32.1" />

View File

@ -61,7 +61,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.715.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.719.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
@ -84,7 +84,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.715.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.719.1" />
<PackageReference Include="SharpCompress" Version="0.32.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />