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

Merge branch 'master' into edit-map-carousel

This commit is contained in:
Salman Ahmed 2022-07-20 02:43:47 +03:00 committed by GitHub
commit ae6aab2555
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 2436 additions and 1715 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;
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 @@ namespace osu.Desktop
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 @@ namespace osu.Desktop
// 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 @@ namespace osu.Game.Rulesets.Catch.Objects
{
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 @@ namespace osu.Game.Rulesets.Catch.Objects
[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 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </remarks>
public float OriginalX
{
get => OriginalXBindable.Value;
set => OriginalXBindable.Value = value;
get => originalX.Value;
set => originalX.Value = value;
}
/// <summary>
@ -69,59 +73,71 @@ namespace osu.Game.Rulesets.Catch.Objects
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 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </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 @@ namespace osu.Game.Rulesets.Osu.Tests
{
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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// 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;
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 @@ namespace osu.Game.Rulesets.Osu.Objects
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 @@ namespace osu.Game.Rulesets.Osu.Objects
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 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
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 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
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 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
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 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
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 System.Linq;
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 @@ namespace osu.Game.Tests.NonVisual
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 @@ namespace osu.Game.Tests.Resources
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
public TestSceneRankingsCountryFilter()
{
var countryBindable = new Bindable<Country>();
var countryBindable = new Bindable<CountryCode>();
AddRange(new Drawable[]
{
@ -56,20 +56,12 @@ namespace osu.Game.Tests.Visual.Online
}
});
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
};
}
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
},
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 @@ namespace osu.Game.Tests.Visual.Online
User = new APIUser
{
Username = "first active user",
Country = new Country { FlagName = "JP" },
CountryCode = CountryCode.JP,
Active = true,
},
Accuracy = 0.9972,
@ -106,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online
User = new APIUser
{
Username = "inactive user",
Country = new Country { FlagName = "AU" },
CountryCode = CountryCode.AU,
Active = false,
},
Accuracy = 0.9831,
@ -126,7 +124,7 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
CountryCode = CountryCode.ES,
},
Mods = new[]
{
@ -184,11 +180,7 @@ namespace osu.Game.Tests.Visual.Online
{
Id = 4608074,
Username = @"Skycries",
Country = new Country
{
FullName = @"Brazil",
FlagName = @"BR",
},
CountryCode = CountryCode.BR,
},
Mods = new[]
{
@ -210,11 +202,7 @@ namespace osu.Game.Tests.Visual.Online
{
Id = 1014222,
Username = @"eLy",
Country = new Country
{
FullName = @"Japan",
FlagName = @"JP",
},
CountryCode = CountryCode.JP,
},
Mods = new[]
{
@ -235,11 +223,7 @@ namespace osu.Game.Tests.Visual.Online
{
Id = 1541390,
Username = @"Toukai",
Country = new Country
{
FullName = @"Canada",
FlagName = @"CA",
},
CountryCode = CountryCode.CA,
},
Mods = new[]
{
@ -259,11 +243,7 @@ namespace osu.Game.Tests.Visual.Online
{
Id = 7151382,
Username = @"Mayuri Hana",
Country = new Country
{
FullName = @"Thailand",
FlagName = @"TH",
},
CountryCode = CountryCode.TH,
},
Rank = ScoreRank.D,
PP = 160,
@ -302,11 +282,7 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
{
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 @@ namespace osu.Game.Tests.Visual.Online
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 @@ namespace osu.Game.Tests.Visual.Online
{
public TestSceneUserProfileScores()
{
var firstScore = new APIScore
var firstScore = new SoloScoreInfo
{
PP = 1047.21,
Rank = ScoreRank.SH,
@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online
},
DifficultyName = "Extreme"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Mods = new[]
{
new APIMod { Acronym = new OsuModHidden().Acronym },
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
Accuracy = 0.9813
};
var secondScore = new APIScore
var secondScore = new SoloScoreInfo
{
PP = 134.32,
Rank = ScoreRank.A,
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online
},
DifficultyName = "[4K] Regret"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Mods = new[]
{
new APIMod { Acronym = new OsuModHardRock().Acronym },
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online
Accuracy = 0.998546
};
var thirdScore = new APIScore
var thirdScore = new SoloScoreInfo
{
PP = 96.83,
Rank = ScoreRank.S,
@ -79,11 +79,11 @@ namespace osu.Game.Tests.Visual.Online
},
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 @@ namespace osu.Game.Tests.Visual.Online
},
DifficultyName = "[4K] Cataclysmic Hypernova"
},
Date = DateTimeOffset.Now,
EndedAt = DateTimeOffset.Now,
Accuracy = 0.55879
};

View File

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

View File

@ -69,11 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
Id = 6602580,
Username = @"waaiiru",
Country = new Country
{
FullName = @"Spain",
FlagName = @"ES",
},
CountryCode = CountryCode.ES,
},
},
new ScoreInfo
@ -88,11 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
Id = 4608074,
Username = @"Skycries",
Country = new Country
{
FullName = @"Brazil",
FlagName = @"BR",
},
CountryCode = CountryCode.BR,
},
},
new ScoreInfo
@ -107,11 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
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 @@ namespace osu.Game.Tests.Visual.UserInterface
};
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 @@ namespace osu.Game.Tournament.Models
/// <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 @@ namespace osu.Game.Tournament.Models
{
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 @@ namespace osu.Game.Tournament.Screens.Editors
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.Online.API.Requests.Responses;
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 @@ namespace osu.Game.Tournament
{
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 @@ namespace osu.Game.Tournament
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 @@ namespace osu.Game.Beatmaps
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 @@ namespace osu.Game.Beatmaps
[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 @@ namespace osu.Game.Database
/// 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 @@ namespace osu.Game.Extensions
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 @@ namespace osu.Game.IO.Serialization
{
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 @@ namespace osu.Game.Models
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 @@ namespace osu.Game.Online.API
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 @@ namespace osu.Game.Online.API
{
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 @@ namespace osu.Game.Online.API.Requests
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 @@ namespace osu.Game.Online.API.Requests
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 @@ namespace osu.Game.Online.API.Requests
{
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 @@ using osu.Game.Rulesets;
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 @@ namespace osu.Game.Online.API.Requests.Responses
[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 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"scores")]
public List<SoloScoreInfo> Scores;
[JsonProperty(@"userScore")]
[JsonProperty(@"user_score")]
public APIScoreWithPosition UserScore;
}
}

View File

@ -34,8 +34,19 @@ namespace osu.Game.Online.API.Requests.Responses
[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 @@ namespace osu.Game.Online.API.Requests.Responses
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 @@ namespace osu.Game.Online.API.Requests.Responses
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 @@ namespace osu.Game.Online.API.Requests.Responses
[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 @@ namespace osu.Game.Online.API.Requests.Responses
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 @@ namespace osu.Game.Online.API.Requests
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 @@ namespace osu.Game.Online
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 @@ namespace osu.Game.Online
/// <summary>
/// Reconnect if already connected.
/// </summary>
void Reconnect();
Task Reconnect();
}
}

View File

@ -181,7 +181,7 @@ namespace osu.Game.Online.Leaderboards
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.Online.Rooms.RoomStatuses;
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 @@ namespace osu.Game.Online.Multiplayer
/// <summary>
/// The joined <see cref="MultiplayerRoom"/>.
/// </summary>
public virtual MultiplayerRoom? Room
public virtual MultiplayerRoom? Room // virtual for moq
{
get
{
@ -150,7 +149,7 @@ namespace osu.Game.Online.Multiplayer
// 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 @@ namespace osu.Game.Online.Multiplayer
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 @@ namespace osu.Game.Online.Rooms
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 @@ namespace osu.Game.Online.Spectator
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 @@ namespace osu.Game.Online.Spectator
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 @@ namespace osu.Game.Online.Spectator
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 @@ namespace osu.Game.Online.Spectator
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 @@ namespace osu.Game.Online.Spectator
Debug.Assert(connection != null);
return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId);
}
}
}

View File

@ -165,10 +165,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
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 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
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 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
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 @@ namespace osu.Game.Overlays.Chat
}
[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 @@ namespace osu.Game.Overlays.Profile.Header
userFlag = new UpdateableFlag
{
Size = new Vector2(28, 20),
ShowPlaceholderOnNull = false,
ShowPlaceholderOnUnknown = false,
},
userCountryText = new OsuSpriteText
{
@ -174,8 +175,8 @@ namespace osu.Game.Overlays.Profile.Header
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 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
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 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
public DrawableProfileScore(APIScore score)
public DrawableProfileScore(SoloScoreInfo score)
{
Score = score;
@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
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 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
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 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
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 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
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 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
}
}
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 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
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 @@ using osuTK;
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 @@ namespace osu.Game.Overlays.Rankings
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 @@ using osuTK.Graphics;
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 @@ namespace osu.Game.Overlays.Rankings
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => Current.Value = null
Action = Current.SetDefault,
}
}
}
@ -130,13 +131,13 @@ namespace osu.Game.Overlays.Rankings
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 @@ namespace osu.Game.Overlays.Rankings
{
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;
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 @@ namespace osu.Game.Overlays.Rankings.Tables
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 @@ namespace osu.Game.Overlays.Rankings.Tables
[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 @@ namespace osu.Game.Overlays.Rankings.Tables
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 @@ namespace osu.Game.Overlays.Rankings.Tables
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 @@ namespace osu.Game.Overlays.Rankings.Tables
.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 @@ namespace osu.Game.Overlays
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 @@ namespace osu.Game.Overlays
{
// 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 @@ namespace osu.Game.Overlays
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 @@ namespace osu.Game.Overlays
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

@ -1,17 +1,14 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Rulesets;
using osu.Game.Localisation;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Settings.Sections
{
@ -36,9 +33,9 @@ namespace osu.Game.Overlays.Settings.Sections
if (section != null)
Add(section);
}
catch (Exception e)
catch
{
Logger.Error(e, "Failed to load ruleset settings");
Logger.Log($"Failed to load ruleset settings for {ruleset.RulesetInfo.Name}. Please check for an update from the developer.", level: LogLevel.Error);
}
}
}

View File

@ -58,6 +58,7 @@ namespace osu.Game.Rulesets.Mods
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 @@ namespace osu.Game.Scoring
api.Perform(userRequest);
if (userRequest.Response is APIUser user)
model.RealmUser.OnlineID = user.Id;
model.User = user;
}
}
}

View File

@ -85,8 +85,9 @@ namespace osu.Game.Scoring
{
get => user ??= new APIUser
{
Username = RealmUser.Username,
Id = RealmUser.OnlineID,
Username = RealmUser.Username,
CountryCode = RealmUser.CountryCode,
};
set
{
@ -95,7 +96,8 @@ namespace osu.Game.Scoring
RealmUser = new RealmUser
{
OnlineID = user.OnlineID,
Username = user.Username
Username = user.Username,
CountryCode = user.CountryCode,
};
}
}
@ -135,6 +137,7 @@ namespace osu.Game.Scoring
{
OnlineID = RealmUser.OnlineID,
Username = RealmUser.Username,
CountryCode = RealmUser.CountryCode,
};
return clone;

View File

@ -73,12 +73,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
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.API;
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 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
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 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
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 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
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 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
// 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 @@ namespace osu.Game.Screens.Play.HUD
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 @@ namespace osu.Game.Screens.Ranking.Contracted
{
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 @@ namespace osu.Game.Screens.Ranking
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(13),
Icon = FontAwesome.Solid.ArrowCircleLeft,
Icon = FontAwesome.Solid.Redo,
},
};

View File

@ -96,7 +96,7 @@ namespace osu.Game.Skinning
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 @@ namespace osu.Game.Users.Drawables
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 @@ using osu.Game.Overlays;
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 @@ namespace osu.Game.Users.Drawables
/// </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 @@ namespace osu.Game.Users.Drawables
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new DrawableFlag(country)
new DrawableFlag(countryCode)
{
RelativeSizeAxes = Axes.Both
},
@ -62,7 +62,7 @@ namespace osu.Game.Users.Drawables
protected override bool OnClick(ClickEvent e)
{
Action?.Invoke();
rankingsOverlay?.ShowCountry(Country);
rankingsOverlay?.ShowCountry(CountryCode);
return true;
}
}

View File

@ -53,7 +53,7 @@ namespace osu.Game.Users
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 @@ namespace osu.Game.Users
{
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" />