mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:07:52 +08:00
Merge branch 'master' into velocitychanges
This commit is contained in:
commit
cfb2adfd27
@ -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.
|
||||
|
@ -51,8 +51,8 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.707.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.716.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.719.0" />
|
||||
</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. -->
|
||||
|
76
osu.Android/AndroidJoystickSettings.cs
Normal file
76
osu.Android/AndroidJoystickSettings.cs
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Android.Input;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Android
|
||||
{
|
||||
public class AndroidJoystickSettings : SettingsSubsection
|
||||
{
|
||||
protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad;
|
||||
|
||||
private readonly AndroidJoystickHandler joystickHandler;
|
||||
|
||||
private readonly Bindable<bool> enabled = new BindableBool(true);
|
||||
|
||||
private SettingsSlider<float> deadzoneSlider = null!;
|
||||
|
||||
private Bindable<float> handlerDeadzone = null!;
|
||||
|
||||
private Bindable<float> localDeadzone = null!;
|
||||
|
||||
public AndroidJoystickSettings(AndroidJoystickHandler joystickHandler)
|
||||
{
|
||||
this.joystickHandler = joystickHandler;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// use local bindable to avoid changing enabled state of game host's bindable.
|
||||
handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy();
|
||||
localDeadzone = handlerDeadzone.GetUnboundCopy();
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = CommonStrings.Enabled,
|
||||
Current = enabled
|
||||
},
|
||||
deadzoneSlider = new SettingsSlider<float>
|
||||
{
|
||||
LabelText = JoystickSettingsStrings.DeadzoneThreshold,
|
||||
KeyboardStep = 0.01f,
|
||||
DisplayAsPercentage = true,
|
||||
Current = localDeadzone,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
enabled.BindTo(joystickHandler.Enabled);
|
||||
enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true);
|
||||
|
||||
handlerDeadzone.BindValueChanged(val =>
|
||||
{
|
||||
bool disabled = localDeadzone.Disabled;
|
||||
|
||||
localDeadzone.Disabled = false;
|
||||
localDeadzone.Value = val.NewValue;
|
||||
localDeadzone.Disabled = disabled;
|
||||
}, true);
|
||||
|
||||
localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -96,6 +96,9 @@ namespace osu.Android
|
||||
case AndroidMouseHandler mh:
|
||||
return new AndroidMouseSettings(mh);
|
||||
|
||||
case AndroidJoystickHandler jh:
|
||||
return new AndroidJoystickSettings(jh);
|
||||
|
||||
default:
|
||||
return base.CreateSettingsSubsectionFor(handler);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AndroidJoystickSettings.cs" />
|
||||
<Compile Include="AndroidMouseSettings.cs" />
|
||||
<Compile Include="GameplayScreenRotationLocker.cs" />
|
||||
<Compile Include="OsuGameActivity.cs" />
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Platform;
|
||||
using osu.Game;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Tournament;
|
||||
using SDL2;
|
||||
using Squirrel;
|
||||
|
||||
namespace osu.Desktop
|
||||
@ -29,7 +30,27 @@ namespace osu.Desktop
|
||||
{
|
||||
// run Squirrel first, as the app may exit after these run
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var windowsVersion = Environment.OSVersion.Version;
|
||||
|
||||
// While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher.
|
||||
// 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.\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;
|
||||
}
|
||||
|
||||
setupSquirrel();
|
||||
}
|
||||
|
||||
// Back up the cwd before DesktopGameHost changes it
|
||||
string cwd = Environment.CurrentDirectory;
|
||||
|
166
osu.Game.Benchmarks/BenchmarkHitObject.cs
Normal file
166
osu.Game.Benchmarks/BenchmarkHitObject.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModSingleTap : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestInputSingular() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(200, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1500,
|
||||
Position = new Vector2(300, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 2000,
|
||||
Position = new Vector2(400, 100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputAlternating() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(200, 100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(1001, new Vector2(200, 100)),
|
||||
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1501, new Vector2(300, 100)),
|
||||
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(2001, new Vector2(400, 100)),
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Ensures singletapping is reset before the first hitobject after intro.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestInputAlternatingAtIntro() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
// first press during intro.
|
||||
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(200)),
|
||||
// press different key at hitobject and ensure it has been hit.
|
||||
new OsuReplayFrame(1000, new Vector2(100), OsuAction.RightButton),
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Ensures singletapping is reset before the first hitobject after a break.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestInputAlternatingWithBreak() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 2500,
|
||||
Position = new Vector2(500, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(500, 100),
|
||||
},
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
// first press to start singletap lock.
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
// press different key after break but before hit object.
|
||||
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(2251, new Vector2(300, 100)),
|
||||
// press same key at second hitobject and ensure it has been hit.
|
||||
new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(2501, new Vector2(500, 100)),
|
||||
// press different key at third hitobject and ensure it has been missed.
|
||||
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(3001, new Vector2(500, 100)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
@ -0,0 +1,114 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
private const double flash_duration = 1000;
|
||||
|
||||
private DrawableRuleset<OsuHitObject> ruleset = null!;
|
||||
|
||||
protected OsuAction? LastAcceptedAction { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||
/// </remarks>
|
||||
private PeriodTracker nonGameplayPeriods = null!;
|
||||
|
||||
private IFrameStableClock gameplayClock = null!;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = drawableRuleset;
|
||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
|
||||
var periods = new List<Period>();
|
||||
|
||||
if (drawableRuleset.Objects.Any())
|
||||
{
|
||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||
|
||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||
|
||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||
}
|
||||
|
||||
nonGameplayPeriods = new PeriodTracker(periods);
|
||||
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
|
||||
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||
|
||||
private bool checkCorrectAction(OsuAction action)
|
||||
{
|
||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||
{
|
||||
LastAcceptedAction = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
break;
|
||||
|
||||
// Any action which is not left or right button should be ignored.
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CheckValidNewAction(action))
|
||||
{
|
||||
LastAcceptedAction = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private readonly InputBlockingMod mod;
|
||||
|
||||
public InputInterceptor(InputBlockingMod mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||
=> !mod.checkCorrectAction(e.Action);
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
public class OsuModAlternate : InputBlockingMod
|
||||
{
|
||||
public override string Name => @"Alternate";
|
||||
public override string Acronym => @"AL";
|
||||
public override string Description => @"Don't use the same key twice in a row!";
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
private const double flash_duration = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||
/// </remarks>
|
||||
private PeriodTracker nonGameplayPeriods;
|
||||
|
||||
private OsuAction? lastActionPressed;
|
||||
private DrawableRuleset<OsuHitObject> ruleset;
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = drawableRuleset;
|
||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
|
||||
var periods = new List<Period>();
|
||||
|
||||
if (drawableRuleset.Objects.Any())
|
||||
{
|
||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||
|
||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||
|
||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||
}
|
||||
|
||||
nonGameplayPeriods = new PeriodTracker(periods);
|
||||
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
|
||||
private bool checkCorrectAction(OsuAction action)
|
||||
{
|
||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||
{
|
||||
lastActionPressed = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
break;
|
||||
|
||||
// Any action which is not left or right button should be ignored.
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastActionPressed != action)
|
||||
{
|
||||
// User alternated correctly.
|
||||
lastActionPressed = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private readonly OsuModAlternate mod;
|
||||
|
||||
public InputInterceptor(OsuModAlternate mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||
=> !mod.checkCorrectAction(e.Action);
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||
|
@ -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) }).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" });
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||
{
|
||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// How early before a hitobject's start time to trigger a hit.
|
||||
|
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModSingleTap : InputBlockingMod
|
||||
{
|
||||
public override string Name => @"Single Tap";
|
||||
public override string Acronym => @"SG";
|
||||
public override string Description => @"You must only use one key!";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||
}
|
||||
}
|
@ -319,13 +319,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
const float fade_out_time = 450;
|
||||
|
||||
// intentionally pile on an extra FadeOut to make it happen much faster.
|
||||
Ball.FadeOut(fade_out_time / 4, Easing.Out);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
||||
if (SliderBody?.SnakingOut.Value == true)
|
||||
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
||||
break;
|
||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
||||
{
|
||||
public const float FOLLOW_AREA = 2.4f;
|
||||
|
||||
public Func<OsuAction?> GetInitialHitAction;
|
||||
|
||||
public Color4 AccentColour
|
||||
@ -31,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
set => ball.Colour = value;
|
||||
}
|
||||
|
||||
private Drawable followCircle;
|
||||
private Drawable followCircleReceptor;
|
||||
private DrawableSlider drawableSlider;
|
||||
private Drawable ball;
|
||||
@ -47,12 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle())
|
||||
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle())
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
followCircleReceptor = new CircularContainer
|
||||
{
|
||||
@ -103,10 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
tracking = value;
|
||||
|
||||
followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f);
|
||||
|
||||
followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
|
||||
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
|
||||
followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModClassic(),
|
||||
new OsuModRandom(),
|
||||
new OsuModMirror(),
|
||||
new OsuModAlternate(),
|
||||
new MultiMod(new OsuModAlternate(), new OsuModSingleTap())
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
|
@ -1,19 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public class DefaultFollowCircle : CompositeDrawable
|
||||
public class DefaultFollowCircle : FollowCircle
|
||||
{
|
||||
public DefaultFollowCircle()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -29,5 +29,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||
{
|
||||
const float scale_duration = 300f;
|
||||
const float fade_duration = 300f;
|
||||
|
||||
this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint)
|
||||
.FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void OnSliderEnd()
|
||||
{
|
||||
const float fade_duration = 450f;
|
||||
|
||||
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||
this.FadeOut(fade_duration / 4, Easing.Out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,13 +17,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public class DefaultSliderBall : CompositeDrawable
|
||||
{
|
||||
private Box box;
|
||||
private Box box = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private DrawableHitObject? parentObject { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
var slider = (DrawableSlider)drawableObject;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
||||
@ -51,10 +50,62 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
}
|
||||
};
|
||||
|
||||
slider.Tracking.BindValueChanged(trackingChanged, true);
|
||||
if (parentObject != null)
|
||||
{
|
||||
var slider = (DrawableSlider)parentObject;
|
||||
slider.Tracking.BindValueChanged(trackingChanged, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (parentObject != null)
|
||||
{
|
||||
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
||||
box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint);
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||
{
|
||||
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||
// animations which may negatively affect performance
|
||||
if (drawableObject is not DrawableSlider)
|
||||
return;
|
||||
|
||||
const float fade_duration = 450f;
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||
{
|
||||
this.FadeIn()
|
||||
.ScaleTo(1f);
|
||||
}
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
{
|
||||
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||
this.FadeOut(fade_duration / 4, Easing.Out);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
this.ScaleTo(1.4f, fade_duration, Easing.Out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (parentObject != null)
|
||||
parentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
75
osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
Normal file
75
osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
public abstract class FollowCircle : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
protected DrawableHitObject? ParentObject { get; private set; }
|
||||
|
||||
protected FollowCircle()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (ParentObject != null)
|
||||
{
|
||||
ParentObject.HitObjectApplied += onHitObjectApplied;
|
||||
onHitObjectApplied(ParentObject);
|
||||
|
||||
ParentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(ParentObject, ParentObject.State.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void onHitObjectApplied(DrawableHitObject drawableObject)
|
||||
{
|
||||
this.ScaleTo(1f)
|
||||
.FadeOut();
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||
{
|
||||
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||
// animations which may negatively affect performance
|
||||
if (drawableObject is not DrawableSlider)
|
||||
return;
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
OnSliderEnd();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (ParentObject != null)
|
||||
{
|
||||
ParentObject.HitObjectApplied -= onHitObjectApplied;
|
||||
ParentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnTrackingChanged(ValueChangedEvent<bool> tracking);
|
||||
|
||||
protected abstract void OnSliderEnd();
|
||||
}
|
||||
}
|
@ -1,12 +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.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
public class LegacyFollowCircle : CompositeDrawable
|
||||
public class LegacyFollowCircle : FollowCircle
|
||||
{
|
||||
public LegacyFollowCircle(Drawable animationContent)
|
||||
{
|
||||
@ -18,5 +20,36 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
InternalChild = animationContent;
|
||||
}
|
||||
|
||||
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||
{
|
||||
Debug.Assert(ParentObject != null);
|
||||
|
||||
if (ParentObject.Judged)
|
||||
return;
|
||||
|
||||
double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current);
|
||||
|
||||
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||
if (tracking.NewValue)
|
||||
{
|
||||
// TODO: Follow circle should bounce on each slider tick.
|
||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Should animate only at the next slider tick if we want to match stable perfectly.
|
||||
this.ScaleTo(4f, 100)
|
||||
.FadeTo(0f, 100);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSliderEnd()
|
||||
{
|
||||
this.ScaleTo(1.6f, 200, Easing.Out)
|
||||
.FadeOut(200, Easing.In);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
||||
|
85
osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs
Normal file
85
osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,9 +4,12 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
@ -23,6 +26,83 @@ namespace osu.Game.Tests.NonVisual
|
||||
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAudioEqualityNoFile()
|
||||
{
|
||||
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
|
||||
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAudioEqualitySameHash()
|
||||
{
|
||||
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
|
||||
addAudioFile(beatmapSetA, "abc");
|
||||
addAudioFile(beatmapSetB, "abc");
|
||||
|
||||
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAudioEqualityDifferentHash()
|
||||
{
|
||||
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
|
||||
addAudioFile(beatmapSetA);
|
||||
addAudioFile(beatmapSetB);
|
||||
|
||||
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAudioEqualityBeatmapInfoSameHash()
|
||||
{
|
||||
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]
|
||||
public void TestDatabasedWithDatabased()
|
||||
{
|
||||
|
@ -134,10 +134,11 @@ namespace osu.Game.Tests.Resources
|
||||
DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||
StarRating = diff,
|
||||
Length = length,
|
||||
BeatmapSet = beatmapSet,
|
||||
BPM = bpm,
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
Ruleset = rulesetInfo,
|
||||
Metadata = metadata,
|
||||
Metadata = metadata.DeepClone(),
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = diff,
|
||||
|
@ -6,6 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
@ -103,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
*/
|
||||
public void TestAddAudioTrack()
|
||||
{
|
||||
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||
|
||||
AddAssert("switch track to real track", () =>
|
||||
{
|
||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||
@ -131,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
||||
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneNoConflictingModAcronyms : TestSceneAllRulesetPlayers
|
||||
{
|
||||
protected override void AddCheckSteps()
|
||||
{
|
||||
AddStep("Check all mod acronyms are unique", () =>
|
||||
{
|
||||
var mods = Ruleset.Value.CreateInstance().AllMods;
|
||||
|
||||
IEnumerable<string> acronyms = mods.Select(m => m.Acronym);
|
||||
|
||||
Assert.That(acronyms, Is.Unique);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
@ -57,13 +62,46 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
protected override bool AllowFail => false;
|
||||
protected override bool AllowFail => allowFail;
|
||||
|
||||
private bool allowFail;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
allowFail = false;
|
||||
customRuleset = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSaveFailedReplay()
|
||||
{
|
||||
AddStep("allow fail", () => allowFail = true);
|
||||
|
||||
CreateTest();
|
||||
|
||||
AddUntilStep("fail screen displayed", () => Player.ChildrenOfType<FailOverlay>().First().State.Value == Visibility.Visible);
|
||||
AddUntilStep("score not in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) == null));
|
||||
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
|
||||
AddUntilStep("score not in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastPlayedUpdated()
|
||||
{
|
||||
DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed);
|
||||
|
||||
AddAssert("last played is null", () => getLastPlayed() == null);
|
||||
|
||||
CreateTest();
|
||||
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreStoredLocally()
|
||||
{
|
||||
AddStep("set no custom ruleset", () => customRuleset = null);
|
||||
|
||||
CreateTest();
|
||||
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
@ -195,12 +196,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestDownloadButtonHiddenWhenBeatmapExists()
|
||||
{
|
||||
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||
Live<BeatmapSetInfo> imported = null;
|
||||
|
||||
Debug.Assert(beatmap.BeatmapSet != null);
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||
|
||||
AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet));
|
||||
Debug.Assert(beatmap.BeatmapSet != null);
|
||||
imported = manager.Import(beatmap.BeatmapSet);
|
||||
});
|
||||
|
||||
createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach()));
|
||||
|
||||
@ -245,40 +249,35 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestExpiredItems()
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
createPlaylist(p =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
p.Items.Clear();
|
||||
p.Items.AddRange(new[]
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
Items =
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
ID = 0,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
Expired = true,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
ID = 0,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
Expired = true,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
ID = 1,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
ID = 1,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
@ -321,19 +320,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||
|
||||
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps) => createPlaylist(p =>
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
p.Items.Clear();
|
||||
|
||||
foreach (var b in beatmaps())
|
||||
{
|
||||
p.Items.Add(new PlaylistItem(b)
|
||||
{
|
||||
ID = index++,
|
||||
OwnerID = 2,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
}
|
||||
};
|
||||
|
||||
setupPlaylist?.Invoke(playlist);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
playlist.Items.Add(new PlaylistItem(i % 2 == 1
|
||||
@ -360,39 +384,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
}
|
||||
|
||||
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
};
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (var b in beatmaps())
|
||||
{
|
||||
playlist.Items.Add(new PlaylistItem(b)
|
||||
{
|
||||
ID = index++,
|
||||
OwnerID = 2,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
});
|
||||
}
|
||||
setupPlaylist?.Invoke(playlist);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -368,12 +369,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
ParticipantsList? participantsList = null;
|
||||
|
||||
AddStep("create new list", () => Child = participantsList = new ParticipantsList
|
||||
AddStep("create new list", () => Child = new OsuContextMenuContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(380, 0.7f)
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = participantsList = new ParticipantsList
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(380, 0.7f)
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Graphics;
|
||||
@ -146,21 +147,17 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
var scores = new APIScoresCollection
|
||||
{
|
||||
Scores = new List<APIScore>
|
||||
Scores = new List<SoloScoreInfo>
|
||||
{
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 6602580,
|
||||
Username = @"waaiiru",
|
||||
Country = new Country
|
||||
{
|
||||
FullName = @"Spain",
|
||||
FlagName = @"ES",
|
||||
},
|
||||
CountryCode = CountryCode.ES,
|
||||
},
|
||||
Mods = new[]
|
||||
{
|
||||
@ -175,19 +172,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567890,
|
||||
Accuracy = 1,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 4608074,
|
||||
Username = @"Skycries",
|
||||
Country = new Country
|
||||
{
|
||||
FullName = @"Brazil",
|
||||
FlagName = @"BR",
|
||||
},
|
||||
CountryCode = CountryCode.BR,
|
||||
},
|
||||
Mods = new[]
|
||||
{
|
||||
@ -201,19 +194,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234789,
|
||||
Accuracy = 0.9997,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 1014222,
|
||||
Username = @"eLy",
|
||||
Country = new Country
|
||||
{
|
||||
FullName = @"Japan",
|
||||
FlagName = @"JP",
|
||||
},
|
||||
CountryCode = CountryCode.JP,
|
||||
},
|
||||
Mods = new[]
|
||||
{
|
||||
@ -226,19 +215,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 12345678,
|
||||
Accuracy = 0.9854,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 1541390,
|
||||
Username = @"Toukai",
|
||||
Country = new Country
|
||||
{
|
||||
FullName = @"Canada",
|
||||
FlagName = @"CA",
|
||||
},
|
||||
CountryCode = CountryCode.CA,
|
||||
},
|
||||
Mods = new[]
|
||||
{
|
||||
@ -250,19 +235,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567,
|
||||
Accuracy = 0.8765,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 7151382,
|
||||
Username = @"Mayuri Hana",
|
||||
Country = new Country
|
||||
{
|
||||
FullName = @"Thailand",
|
||||
FlagName = @"TH",
|
||||
},
|
||||
CountryCode = CountryCode.TH,
|
||||
},
|
||||
Rank = ScoreRank.D,
|
||||
PP = 160,
|
||||
@ -273,14 +254,18 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
};
|
||||
|
||||
const int initial_great_count = 2000;
|
||||
|
||||
int greatCount = initial_great_count;
|
||||
|
||||
foreach (var s in scores.Scores)
|
||||
{
|
||||
s.Statistics = new Dictionary<string, int>
|
||||
s.Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
{ "count_300", RNG.Next(2000) },
|
||||
{ "count_100", RNG.Next(2000) },
|
||||
{ "count_50", RNG.Next(2000) },
|
||||
{ "count_miss", RNG.Next(2000) }
|
||||
{ HitResult.Great, greatCount -= 100 },
|
||||
{ HitResult.Ok, RNG.Next(100) },
|
||||
{ HitResult.Meh, RNG.Next(100) },
|
||||
{ HitResult.Miss, initial_great_count - greatCount }
|
||||
};
|
||||
}
|
||||
|
||||
@ -289,19 +274,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private APIScoreWithPosition createUserBest() => new APIScoreWithPosition
|
||||
{
|
||||
Score = new APIScore
|
||||
Score = new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 7151382,
|
||||
Username = @"Mayuri Hana",
|
||||
Country = new Country
|
||||
{
|
||||
FullName = @"Thailand",
|
||||
FlagName = @"TH",
|
||||
},
|
||||
CountryCode = CountryCode.TH,
|
||||
},
|
||||
Rank = ScoreRank.D,
|
||||
PP = 160,
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}));
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -12,7 +12,8 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
public class TournamentSpriteTextWithBackground : CompositeDrawable
|
||||
{
|
||||
protected readonly TournamentSpriteText Text;
|
||||
public readonly TournamentSpriteText Text;
|
||||
|
||||
protected readonly Box Background;
|
||||
|
||||
public TournamentSpriteTextWithBackground(string text = "")
|
||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Components
|
||||
private Video video;
|
||||
private ManualClock manualClock;
|
||||
|
||||
public bool VideoAvailable => video != null;
|
||||
|
||||
public TourneyVideo(string filename, bool drawFallbackGradient = false)
|
||||
{
|
||||
this.filename = filename;
|
||||
|
770
osu.Game.Tournament/CountryExtensions.cs
Normal file
770
osu.Game.Tournament/CountryExtensions.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Models
|
||||
public int ID;
|
||||
|
||||
[JsonProperty("BeatmapInfo")]
|
||||
public TournamentBeatmap Beatmap;
|
||||
public TournamentBeatmap? Beatmap;
|
||||
|
||||
public long Score;
|
||||
|
||||
|
@ -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
101
osu.Game.Tournament/SaveChangesOverlay.cs
Normal file
101
osu.Game.Tournament/SaveChangesOverlay.cs
Normal file
@ -0,0 +1,101 @@
|
||||
// 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.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
internal class SaveChangesOverlay : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private TournamentGame tournamentGame { get; set; } = null!;
|
||||
|
||||
private string? lastSerialisedLadder;
|
||||
private readonly TourneyButton saveChangesButton;
|
||||
|
||||
public SaveChangesOverlay()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Position = new Vector2(5),
|
||||
CornerRadius = 10,
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = OsuColour.Gray(0.2f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
saveChangesButton = new TourneyButton
|
||||
{
|
||||
Text = "Save Changes",
|
||||
Width = 140,
|
||||
Height = 50,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = 10,
|
||||
Left = 10,
|
||||
},
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Bottom = 10,
|
||||
},
|
||||
Action = saveChanges,
|
||||
// Enabled = { Value = false },
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
scheduleNextCheck();
|
||||
}
|
||||
|
||||
private async Task checkForChanges()
|
||||
{
|
||||
string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder());
|
||||
|
||||
// If a save hasn't been triggered by the user yet, populate the initial value
|
||||
lastSerialisedLadder ??= serialisedLadder;
|
||||
|
||||
if (lastSerialisedLadder != serialisedLadder && !saveChangesButton.Enabled.Value)
|
||||
{
|
||||
saveChangesButton.Enabled.Value = true;
|
||||
saveChangesButton.Background
|
||||
.FadeColour(saveChangesButton.BackgroundColour.Lighten(0.5f), 500, Easing.In).Then()
|
||||
.FadeColour(saveChangesButton.BackgroundColour, 500, Easing.Out)
|
||||
.Loop();
|
||||
}
|
||||
|
||||
scheduleNextCheck();
|
||||
}
|
||||
|
||||
private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000);
|
||||
|
||||
private void saveChanges()
|
||||
{
|
||||
tournamentGame.SaveChanges();
|
||||
lastSerialisedLadder = tournamentGame.GetSerialisedLadder();
|
||||
|
||||
saveChangesButton.Enabled.Value = false;
|
||||
saveChangesButton.Background.FadeColour(saveChangesButton.BackgroundColour, 500);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Graphics;
|
||||
@ -45,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Drawings
|
||||
public ITeamList TeamList;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures, Storage storage)
|
||||
private void load(Storage storage)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
@ -91,11 +89,10 @@ namespace osu.Game.Tournament.Screens.Drawings
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
new TourneyVideo("drawings")
|
||||
{
|
||||
Loop = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = textures.Get(@"Backgrounds/Drawings/background.png")
|
||||
},
|
||||
// Visualiser
|
||||
new VisualiserContainer
|
||||
|
@ -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);
|
||||
|
||||
@ -298,10 +301,10 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updatePanel()
|
||||
private void updatePanel() => Scheduler.AddOnce(() =>
|
||||
{
|
||||
drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 };
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
public abstract class TournamentEditorScreen<TDrawable, TModel> : TournamentScreen, IProvideVideo
|
||||
public abstract class TournamentEditorScreen<TDrawable, TModel> : TournamentScreen
|
||||
where TDrawable : Drawable, IModelBacked<TModel>
|
||||
where TModel : class, new()
|
||||
{
|
||||
|
@ -16,6 +16,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
{
|
||||
private readonly TeamScore score;
|
||||
|
||||
private readonly TournamentSpriteTextWithBackground teamText;
|
||||
|
||||
private readonly Bindable<string> teamName = new Bindable<string>("???");
|
||||
|
||||
private bool showScore;
|
||||
|
||||
public bool ShowScore
|
||||
@ -93,7 +97,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
}
|
||||
}
|
||||
},
|
||||
new TournamentSpriteTextWithBackground(team?.FullName.Value ?? "???")
|
||||
teamText = new TournamentSpriteTextWithBackground
|
||||
{
|
||||
Scale = new Vector2(0.5f),
|
||||
Origin = anchor,
|
||||
@ -113,6 +117,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
|
||||
updateDisplay();
|
||||
FinishTransforms(true);
|
||||
|
||||
if (Team != null)
|
||||
teamName.BindTo(Team.FullName);
|
||||
|
||||
teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true);
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
currentMatch.BindTo(ladder.CurrentMatch);
|
||||
currentMatch.BindValueChanged(matchChanged);
|
||||
|
||||
currentTeam.BindValueChanged(teamChanged);
|
||||
|
||||
updateMatch();
|
||||
}
|
||||
|
||||
@ -67,7 +69,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
|
||||
// team may change to same team, which means score is not in a good state.
|
||||
// thus we handle this manually.
|
||||
teamChanged(currentTeam.Value);
|
||||
currentTeam.TriggerChange();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
@ -88,11 +90,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
private void teamChanged(TournamentTeam team)
|
||||
private void teamChanged(ValueChangedEvent<TournamentTeam> team)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
|
||||
teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Gameplay
|
||||
{
|
||||
public class GameplayScreen : BeatmapInfoScreen, IProvideVideo
|
||||
public class GameplayScreen : BeatmapInfoScreen
|
||||
{
|
||||
private readonly BindableBool warmup = new BindableBool();
|
||||
|
||||
|
@ -1,14 +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
|
||||
|
||||
namespace osu.Game.Tournament.Screens
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for a screen which provides its own local video background.
|
||||
/// </summary>
|
||||
public interface IProvideVideo
|
||||
{
|
||||
}
|
||||
}
|
@ -53,6 +53,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||
|
||||
editorInfo.Selected.ValueChanged += selection =>
|
||||
{
|
||||
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
|
||||
GetContainingInputManager().TriggerFocusContention(null);
|
||||
|
||||
roundDropdown.Current = selection.NewValue?.Round;
|
||||
losersCheckbox.Current = selection.NewValue?.Losers;
|
||||
dateTimeBox.Current = selection.NewValue?.Date;
|
||||
|
@ -19,7 +19,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Ladder
|
||||
{
|
||||
public class LadderScreen : TournamentScreen, IProvideVideo
|
||||
public class LadderScreen : TournamentScreen
|
||||
{
|
||||
protected Container<DrawableTournamentMatch> MatchesContainer;
|
||||
private Container<Path> paths;
|
||||
|
@ -19,7 +19,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Schedule
|
||||
{
|
||||
public class ScheduleScreen : TournamentScreen // IProvidesVideo
|
||||
public class ScheduleScreen : TournamentScreen
|
||||
{
|
||||
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
|
||||
private Container mainContainer;
|
||||
|
@ -9,6 +9,8 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
@ -19,7 +21,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Setup
|
||||
{
|
||||
public class SetupScreen : TournamentScreen, IProvideVideo
|
||||
public class SetupScreen : TournamentScreen
|
||||
{
|
||||
private FillFlowContainer fillFlow;
|
||||
|
||||
@ -48,13 +50,21 @@ namespace osu.Game.Tournament.Screens.Setup
|
||||
{
|
||||
windowSize = frameworkConfig.GetBindable<Size>(FrameworkSetting.WindowedSize);
|
||||
|
||||
InternalChild = fillFlow = new FillFlowContainer
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding(10),
|
||||
Spacing = new Vector2(10),
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(0.2f),
|
||||
},
|
||||
fillFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding(10),
|
||||
Spacing = new Vector2(10),
|
||||
}
|
||||
};
|
||||
|
||||
api.LocalUser.BindValueChanged(_ => Schedule(reload));
|
||||
@ -74,7 +84,8 @@ namespace osu.Game.Tournament.Screens.Setup
|
||||
Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()),
|
||||
Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found",
|
||||
Failing = fileBasedIpc?.IPCStorage == null,
|
||||
Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation."
|
||||
Description =
|
||||
"The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation."
|
||||
},
|
||||
new ActionableInfo
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Showcase
|
||||
{
|
||||
public class ShowcaseScreen : BeatmapInfoScreen // IProvideVideo
|
||||
public class ShowcaseScreen : BeatmapInfoScreen
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,7 +20,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
{
|
||||
public class SeedingScreen : TournamentMatchScreen, IProvideVideo
|
||||
public class SeedingScreen : TournamentMatchScreen
|
||||
{
|
||||
private Container mainContainer;
|
||||
|
||||
@ -69,7 +70,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
currentTeam.BindValueChanged(teamChanged, true);
|
||||
}
|
||||
|
||||
private void teamChanged(ValueChangedEvent<TournamentTeam> team)
|
||||
private void teamChanged(ValueChangedEvent<TournamentTeam> team) => Scheduler.AddOnce(() =>
|
||||
{
|
||||
if (team.NewValue == null)
|
||||
{
|
||||
@ -78,7 +79,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
}
|
||||
|
||||
showTeam(team.NewValue);
|
||||
}
|
||||
});
|
||||
|
||||
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
|
||||
{
|
||||
@ -120,8 +121,14 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
foreach (var seeding in team.SeedingResults)
|
||||
{
|
||||
fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value));
|
||||
|
||||
foreach (var beatmap in seeding.Beatmaps)
|
||||
{
|
||||
if (beatmap.Beatmap == null)
|
||||
continue;
|
||||
|
||||
fill.Add(new BeatmapScoreRow(beatmap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +136,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
{
|
||||
public BeatmapScoreRow(SeedingBeatmap beatmap)
|
||||
{
|
||||
Debug.Assert(beatmap.Beatmap != null);
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
@ -157,7 +166,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 },
|
||||
new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
||||
new TournamentSpriteText
|
||||
{ Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
{
|
||||
public class TeamIntroScreen : TournamentMatchScreen, IProvideVideo
|
||||
public class TeamIntroScreen : TournamentMatchScreen
|
||||
{
|
||||
private Container mainContainer;
|
||||
|
||||
|
@ -14,7 +14,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.TeamWin
|
||||
{
|
||||
public class TeamWinScreen : TournamentMatchScreen, IProvideVideo
|
||||
public class TeamWinScreen : TournamentMatchScreen
|
||||
{
|
||||
private Container mainContainer;
|
||||
|
||||
@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens.TeamWin
|
||||
|
||||
private bool firstDisplay = true;
|
||||
|
||||
private void update() => Schedule(() =>
|
||||
private void update() => Scheduler.AddOnce(() =>
|
||||
{
|
||||
var match = CurrentMatch.Value;
|
||||
|
||||
|
@ -11,8 +11,6 @@ using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
@ -20,11 +18,11 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Tournament.Models;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
[Cached]
|
||||
public class TournamentGame : TournamentGameBase
|
||||
{
|
||||
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
|
||||
@ -78,40 +76,9 @@ namespace osu.Game.Tournament
|
||||
|
||||
LoadComponentsAsync(new[]
|
||||
{
|
||||
new Container
|
||||
new SaveChangesOverlay
|
||||
{
|
||||
CornerRadius = 10,
|
||||
Depth = float.MinValue,
|
||||
Position = new Vector2(5),
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = OsuColour.Gray(0.2f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new TourneyButton
|
||||
{
|
||||
Text = "Save Changes",
|
||||
Width = 140,
|
||||
Height = 50,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = 10,
|
||||
Left = 10,
|
||||
},
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Bottom = 10,
|
||||
},
|
||||
Action = SaveChanges,
|
||||
},
|
||||
}
|
||||
},
|
||||
heightWarning = new WarningBox("Please make the window wider")
|
||||
{
|
||||
|
@ -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,14 +291,14 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SaveChanges()
|
||||
public void SaveChanges()
|
||||
{
|
||||
if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully)
|
||||
{
|
||||
@ -311,7 +314,16 @@ namespace osu.Game.Tournament
|
||||
.ToList();
|
||||
|
||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||
string serialisedLadder = JsonConvert.SerializeObject(ladder,
|
||||
string serialisedLadder = GetSerialisedLadder();
|
||||
|
||||
using (var stream = storage.CreateFileSafely(BRACKET_FILENAME))
|
||||
using (var sw = new StreamWriter(stream))
|
||||
sw.Write(serialisedLadder);
|
||||
}
|
||||
|
||||
public string GetSerialisedLadder()
|
||||
{
|
||||
return JsonConvert.SerializeObject(ladder,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
@ -319,10 +331,6 @@ namespace osu.Game.Tournament
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||
Converters = new JsonConverter[] { new JsonPointConverter() }
|
||||
});
|
||||
|
||||
using (var stream = storage.CreateFileSafely(BRACKET_FILENAME))
|
||||
using (var sw = new StreamWriter(stream))
|
||||
sw.Write(serialisedLadder);
|
||||
}
|
||||
|
||||
protected override UserInputManager CreateUserInputManager() => new TournamentInputManager();
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -186,7 +187,7 @@ namespace osu.Game.Tournament
|
||||
var lastScreen = currentScreen;
|
||||
currentScreen = target;
|
||||
|
||||
if (currentScreen is IProvideVideo)
|
||||
if (currentScreen.ChildrenOfType<TourneyVideo>().FirstOrDefault()?.VideoAvailable == true)
|
||||
{
|
||||
video.FadeOut(200);
|
||||
|
||||
|
@ -3,12 +3,15 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
public class TourneyButton : OsuButton
|
||||
{
|
||||
public new Box Background => base.Background;
|
||||
|
||||
public TourneyButton()
|
||||
: base(null)
|
||||
{
|
||||
|
@ -80,9 +80,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
if (beatmapSet.OnlineID > 0)
|
||||
{
|
||||
var existingSetWithSameOnlineID = realm.All<BeatmapSetInfo>().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID);
|
||||
|
||||
if (existingSetWithSameOnlineID != null)
|
||||
// OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure.
|
||||
foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == beatmapSet.OnlineID))
|
||||
{
|
||||
existingSetWithSameOnlineID.DeletePending = true;
|
||||
existingSetWithSameOnlineID.OnlineID = -1;
|
||||
@ -90,7 +89,7 @@ namespace osu.Game.Beatmaps
|
||||
foreach (var b in existingSetWithSameOnlineID.Beatmaps)
|
||||
b.OnlineID = -1;
|
||||
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be deleted.");
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
@ -110,6 +111,11 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public bool SamplesMatchPlaybackRate { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which this beatmap was last played by the local user.
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastPlayed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ratio of distance travelled per time unit.
|
||||
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
|
||||
@ -151,14 +157,23 @@ namespace osu.Game.Beatmaps
|
||||
public bool AudioEquals(BeatmapInfo? other) => other != null
|
||||
&& BeatmapSet != null
|
||||
&& other.BeatmapSet != null
|
||||
&& BeatmapSet.Hash == other.BeatmapSet.Hash
|
||||
&& Metadata.AudioFile == other.Metadata.AudioFile;
|
||||
&& compareFiles(this, other, m => m.AudioFile);
|
||||
|
||||
public bool BackgroundEquals(BeatmapInfo? other) => other != null
|
||||
&& BeatmapSet != null
|
||||
&& other.BeatmapSet != null
|
||||
&& BeatmapSet.Hash == other.BeatmapSet.Hash
|
||||
&& Metadata.BackgroundFile == other.Metadata.BackgroundFile;
|
||||
&& compareFiles(this, other, m => m.BackgroundFile);
|
||||
|
||||
private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func<IBeatmapMetadataInfo, string> getFilename)
|
||||
{
|
||||
Debug.Assert(x.BeatmapSet != null);
|
||||
Debug.Assert(y.BeatmapSet != null);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
|
||||
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
||||
|
@ -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()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error);
|
||||
Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
23
osu.Game/Collections/CollectionToggleMenuItem.cs
Normal file
23
osu.Game/Collections/CollectionToggleMenuItem.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Collections
|
||||
{
|
||||
public class CollectionToggleMenuItem : ToggleMenuItem
|
||||
{
|
||||
public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap)
|
||||
: base(collection.Name.Value, MenuItemType.Standard, state =>
|
||||
{
|
||||
if (state)
|
||||
collection.BeatmapHashes.Add(beatmap.MD5Hash);
|
||||
else
|
||||
collection.BeatmapHashes.Remove(beatmap.MD5Hash);
|
||||
})
|
||||
{
|
||||
State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash);
|
||||
}
|
||||
}
|
||||
}
|
@ -443,7 +443,6 @@ namespace osu.Game.Database
|
||||
TotalScore = score.TotalScore,
|
||||
MaxCombo = score.MaxCombo,
|
||||
Accuracy = score.Accuracy,
|
||||
HasReplay = ((IScoreInfo)score).HasReplay,
|
||||
Date = score.Date,
|
||||
PP = score.PP,
|
||||
Rank = score.Rank,
|
||||
|
@ -58,8 +58,11 @@ namespace osu.Game.Database
|
||||
/// 12 2021-11-24 Add Status to RealmBeatmapSet.
|
||||
/// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields).
|
||||
/// 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 = 14;
|
||||
private const int schema_version = 17;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
|
@ -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);
|
||||
|
94
osu.Game/Extensions/StringDehumanizeExtensions.cs
Normal file
94
osu.Game/Extensions/StringDehumanizeExtensions.cs
Normal 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('_', '-');
|
||||
}
|
||||
}
|
||||
}
|
@ -94,6 +94,8 @@ namespace osu.Game.IO
|
||||
error = OsuStorageError.None;
|
||||
Storage lastStorage = UnderlyingStorage;
|
||||
|
||||
Logger.Log($"Attempting to use custom storage location {CustomStoragePath}");
|
||||
|
||||
try
|
||||
{
|
||||
Storage userStorage = host.GetStorage(CustomStoragePath);
|
||||
@ -102,6 +104,7 @@ namespace osu.Game.IO
|
||||
error = OsuStorageError.AccessibleButEmpty;
|
||||
|
||||
ChangeTargetStorage(userStorage);
|
||||
Logger.Log($"Storage successfully changed to {CustomStoragePath}.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -109,6 +112,9 @@ namespace osu.Game.IO
|
||||
ChangeTargetStorage(lastStorage);
|
||||
}
|
||||
|
||||
if (error != OsuStorageError.None)
|
||||
Logger.Log($"Custom storage location could not be used ({error}).");
|
||||
|
||||
return error == OsuStorageError.None;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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)
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
public string WebsiteRootUrl { get; }
|
||||
|
||||
public int APIVersion => 20220217; // We may want to pull this from the game version eventually.
|
||||
public int APIVersion => 20220705; // We may want to pull this from the game version eventually.
|
||||
|
||||
public Exception LastLoginError { get; private set; }
|
||||
|
||||
@ -163,7 +163,13 @@ namespace osu.Game.Online.API
|
||||
|
||||
userReq.Failure += ex =>
|
||||
{
|
||||
if (ex is WebException webException && webException.Message == @"Unauthorized")
|
||||
if (ex is APIException)
|
||||
{
|
||||
LastLoginError = ex;
|
||||
log.Add("Login failed on local user retrieval!");
|
||||
Logout();
|
||||
}
|
||||
else if (ex is WebException webException && webException.Message == @"Unauthorized")
|
||||
{
|
||||
log.Add(@"Login no longer valid");
|
||||
Logout();
|
||||
|
@ -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,10 +63,17 @@ 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;
|
||||
|
||||
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
|
||||
try
|
||||
{
|
||||
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"Failed to copy mod setting value '{settingValue ?? "null"}' to \"{property.Name}\": {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Online.API.Requests
|
||||
this.mods = mods ?? Array.Empty<IMod>();
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}";
|
||||
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/solo-scores{createQueryParameters()}";
|
||||
|
||||
private string createQueryParameters()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public int? Position;
|
||||
|
||||
[JsonProperty(@"score")]
|
||||
public APIScore Score;
|
||||
public SoloScoreInfo Score;
|
||||
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||
{
|
||||
var score = Score.CreateScoreInfo(rulesets, beatmap);
|
||||
var score = Score.ToScoreInfo(rulesets, beatmap);
|
||||
score.Position = Position;
|
||||
return score;
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public class APIScoresCollection
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public List<APIScore> Scores;
|
||||
public List<SoloScoreInfo> Scores;
|
||||
|
||||
[JsonProperty(@"userScore")]
|
||||
[JsonProperty(@"user_score")]
|
||||
public APIScoreWithPosition UserScore;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
156
osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
Normal file
156
osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
Normal file
@ -0,0 +1,156 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
[Serializable]
|
||||
public class SoloScoreInfo : IHasOnlineID<long>
|
||||
{
|
||||
[JsonProperty("replay")]
|
||||
public bool HasReplay { get; set; }
|
||||
|
||||
[JsonProperty("beatmap_id")]
|
||||
public int BeatmapID { get; set; }
|
||||
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[JsonProperty("build_id")]
|
||||
public int? BuildID { get; set; }
|
||||
|
||||
[JsonProperty("passed")]
|
||||
public bool Passed { get; set; }
|
||||
|
||||
[JsonProperty("total_score")]
|
||||
public int TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty("user_id")]
|
||||
public int UserID { get; set; }
|
||||
|
||||
// TODO: probably want to update this column to match user stats (short)?
|
||||
[JsonProperty("max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty("rank")]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
[JsonProperty("started_at")]
|
||||
public DateTimeOffset? StartedAt { get; set; }
|
||||
|
||||
[JsonProperty("ended_at")]
|
||||
public DateTimeOffset EndedAt { get; set; }
|
||||
|
||||
[JsonProperty("mods")]
|
||||
public APIMod[] Mods { get; set; } = Array.Empty<APIMod>();
|
||||
|
||||
[JsonIgnore]
|
||||
[JsonProperty("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[JsonProperty("updated_at")]
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[JsonProperty("deleted_at")]
|
||||
public DateTimeOffset? DeletedAt { get; set; }
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
|
||||
|
||||
#region osu-web API additions (not stored to database).
|
||||
|
||||
[JsonProperty("id")]
|
||||
public long? ID { get; set; }
|
||||
|
||||
[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; }
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString() => $"score_id: {ID} user_id: {UserID}";
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||
/// </summary>
|
||||
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
|
||||
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
|
||||
/// <returns></returns>
|
||||
public ScoreInfo ToScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null)
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally");
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
var mods = Mods.Select(apiMod => apiMod.ToMod(rulesetInstance)).ToArray();
|
||||
|
||||
var scoreInfo = ToScoreInfo(mods);
|
||||
|
||||
scoreInfo.Ruleset = ruleset;
|
||||
if (beatmap != null) scoreInfo.BeatmapInfo = beatmap;
|
||||
|
||||
return scoreInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mod instances, resolved from a ruleset.</param>
|
||||
/// <returns></returns>
|
||||
public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo
|
||||
{
|
||||
OnlineID = OnlineID,
|
||||
User = User ?? new APIUser { Id = UserID },
|
||||
BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID },
|
||||
Ruleset = new RulesetInfo { OnlineID = RulesetID },
|
||||
Passed = Passed,
|
||||
TotalScore = TotalScore,
|
||||
Accuracy = Accuracy,
|
||||
MaxCombo = MaxCombo,
|
||||
Rank = Rank,
|
||||
Statistics = Statistics,
|
||||
Date = EndedAt,
|
||||
Hash = HasReplay ? "online" : string.Empty, // TODO: temporary?
|
||||
Mods = mods,
|
||||
PP = PP,
|
||||
};
|
||||
|
||||
public long OnlineID => ID ?? -1;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user