mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge branch 'master' into beatmap-set-link-genre-language
This commit is contained in:
commit
464f251c0c
@ -1,6 +1,6 @@
|
||||
clone_depth: 1
|
||||
version: '{build}'
|
||||
image: Visual Studio 2019
|
||||
image: Visual Studio 2022
|
||||
test: off
|
||||
skip_non_tags: true
|
||||
configuration: Release
|
||||
@ -83,4 +83,4 @@ artifacts:
|
||||
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: nuget
|
||||
name: nuget
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1219.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1226.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" package="sh.ppy.osulazer" android:installLocation="auto" android:versionName="0.1.0">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
|
||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
||||
</manifest>
|
@ -8,6 +8,9 @@
|
||||
<UseMauiEssentials>true</UseMauiEssentials>
|
||||
<!-- This currently causes random lockups during gameplay. https://github.com/mono/mono/issues/18973 -->
|
||||
<EnableLLVM>false</EnableLLVM>
|
||||
<Version>0.0.0</Version>
|
||||
<ApplicationVersion Condition=" '$(ApplicationVersion)' == '' ">1</ApplicationVersion>
|
||||
<ApplicationDisplayVersion Condition=" '$(ApplicationDisplayVersion)' == '' ">$(Version)</ApplicationDisplayVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
||||
|
@ -18,6 +18,36 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestAlwaysHidden()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModNoScope
|
||||
{
|
||||
HiddenComboCount = { Value = 0 },
|
||||
},
|
||||
Autoplay = true,
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X * 0.5f,
|
||||
StartTime = 1000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X * 1.5f,
|
||||
StartTime = 2000,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibleDuringBreak()
|
||||
{
|
||||
|
@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
var catchPlayfield = (CatchPlayfield)playfield;
|
||||
bool shouldAlwaysShowCatcher = IsBreakTime.Value;
|
||||
float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha;
|
||||
|
||||
// AlwaysPresent required for catcher to still act on input when fully hidden.
|
||||
catchPlayfield.CatcherArea.AlwaysPresent = true;
|
||||
catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
}
|
||||
}
|
||||
|
@ -1,193 +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.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||
{
|
||||
protected readonly HitResult Result;
|
||||
|
||||
protected SpriteText JudgementText { get; private set; } = null!;
|
||||
|
||||
private RingExplosion? ringExplosion;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public ArgonJudgementPiece(HitResult result)
|
||||
{
|
||||
Result = result;
|
||||
Origin = Anchor.Centre;
|
||||
Y = 160;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
JudgementText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = Result.GetDescription().ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(Result),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
|
||||
},
|
||||
};
|
||||
|
||||
if (Result.IsHit())
|
||||
{
|
||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||
{
|
||||
Colour = colours.ForHitResult(Result),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the default animation for this judgement piece.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The base implementation only handles fade (for all result types) and misses.
|
||||
/// Individual rulesets are recommended to implement their appropriate hit animations.
|
||||
/// </remarks>
|
||||
public virtual void PlayAnimation()
|
||||
{
|
||||
switch (Result)
|
||||
{
|
||||
default:
|
||||
JudgementText
|
||||
.ScaleTo(Vector2.One)
|
||||
.ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
|
||||
break;
|
||||
|
||||
case HitResult.Miss:
|
||||
this.ScaleTo(1.6f);
|
||||
this.ScaleTo(1, 100, Easing.In);
|
||||
|
||||
this.MoveTo(Vector2.Zero);
|
||||
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
|
||||
this.RotateTo(0);
|
||||
this.RotateTo(40, 800, Easing.InQuint);
|
||||
break;
|
||||
}
|
||||
|
||||
this.FadeOutFromOne(800);
|
||||
|
||||
ringExplosion?.PlayAnimation();
|
||||
}
|
||||
|
||||
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||
|
||||
private partial class RingExplosion : CompositeDrawable
|
||||
{
|
||||
private readonly float travel = 52;
|
||||
|
||||
public RingExplosion(HitResult result)
|
||||
{
|
||||
const float thickness = 4;
|
||||
|
||||
const float small_size = 9;
|
||||
const float large_size = 14;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
|
||||
int countSmall = 0;
|
||||
int countLarge = 0;
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Meh:
|
||||
countSmall = 3;
|
||||
travel *= 0.3f;
|
||||
break;
|
||||
|
||||
case HitResult.Ok:
|
||||
case HitResult.Good:
|
||||
countSmall = 4;
|
||||
travel *= 0.6f;
|
||||
break;
|
||||
|
||||
case HitResult.Great:
|
||||
case HitResult.Perfect:
|
||||
countSmall = 4;
|
||||
countLarge = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < countSmall; i++)
|
||||
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
|
||||
|
||||
for (int i = 0; i < countLarge; i++)
|
||||
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
|
||||
}
|
||||
|
||||
public void PlayAnimation()
|
||||
{
|
||||
foreach (var c in InternalChildren)
|
||||
{
|
||||
const float start_position_ratio = 0.3f;
|
||||
|
||||
float direction = RNG.NextSingle(0, 360);
|
||||
float distance = RNG.NextSingle(travel / 2, travel);
|
||||
|
||||
c.MoveTo(new Vector2(
|
||||
MathF.Cos(direction) * distance * start_position_ratio,
|
||||
MathF.Sin(direction) * distance * start_position_ratio
|
||||
));
|
||||
|
||||
c.MoveTo(new Vector2(
|
||||
MathF.Cos(direction) * distance,
|
||||
MathF.Sin(direction) * distance
|
||||
), 600, Easing.OutQuint);
|
||||
}
|
||||
|
||||
this.FadeOutFromOne(1000, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public partial class RingPiece : CircularContainer
|
||||
{
|
||||
public RingPiece(float thickness = 9)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Masking = true;
|
||||
BorderThickness = thickness;
|
||||
BorderColour = Color4.White;
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast<CatcherAnimationState>())
|
||||
foreach (var state in Enum.GetValues<CatcherAnimationState>())
|
||||
{
|
||||
AddInternal(drawables[state] = getDrawableFor(state).With(d =>
|
||||
{
|
||||
|
@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
||||
: base(hitObject, beatmap, previousPattern)
|
||||
{
|
||||
if (random == null) throw new ArgumentNullException(nameof(random));
|
||||
if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap));
|
||||
ArgumentNullException.ThrowIfNull(random);
|
||||
ArgumentNullException.ThrowIfNull(originalBeatmap);
|
||||
|
||||
Random = random;
|
||||
OriginalBeatmap = originalBeatmap;
|
||||
|
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
|
||||
protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
|
||||
{
|
||||
if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
|
||||
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
|
||||
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
|
||||
ArgumentNullException.ThrowIfNull(hitObject);
|
||||
ArgumentNullException.ThrowIfNull(beatmap);
|
||||
ArgumentNullException.ThrowIfNull(previousPattern);
|
||||
|
||||
HitObject = hitObject;
|
||||
Beatmap = beatmap;
|
||||
|
@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils
|
||||
|
||||
public static void Sort(T[] keys, IComparer<T> comparer)
|
||||
{
|
||||
if (keys == null)
|
||||
throw new ArgumentNullException(nameof(keys));
|
||||
ArgumentNullException.ThrowIfNull(keys);
|
||||
|
||||
if (keys.Length == 0)
|
||||
return;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -18,20 +17,18 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||
public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
|
||||
{
|
||||
protected readonly HitResult Result;
|
||||
|
||||
protected SpriteText JudgementText { get; private set; } = null!;
|
||||
|
||||
private RingExplosion? ringExplosion;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public ArgonJudgementPiece(HitResult result)
|
||||
: base(result)
|
||||
{
|
||||
Result = result;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
Y = 160;
|
||||
}
|
||||
@ -39,22 +36,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
JudgementText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = Result.GetDescription().ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(Result),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
|
||||
},
|
||||
};
|
||||
|
||||
if (Result.IsHit())
|
||||
{
|
||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||
@ -64,6 +45,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
}
|
||||
}
|
||||
|
||||
protected override SpriteText CreateJudgementText() =>
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Plays the default animation for this judgement piece.
|
||||
/// </summary>
|
||||
|
@ -27,6 +27,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
switch (lookup)
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||
return Drawable.Empty();
|
||||
|
||||
return new ArgonJudgementPiece(resultComponent.Component);
|
||||
|
||||
case ManiaSkinComponentLookup maniaComponent:
|
||||
|
@ -134,6 +134,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
// must happen before children are disposed in base call to prevent illegal accesses to the hit explosion pool.
|
||||
NewResult -= OnNewResult;
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (skin != null)
|
||||
|
@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public ManiaPlayfield(List<StageDefinition> stageDefinitions)
|
||||
{
|
||||
if (stageDefinitions == null)
|
||||
throw new ArgumentNullException(nameof(stageDefinitions));
|
||||
ArgumentNullException.ThrowIfNull(stageDefinitions);
|
||||
|
||||
if (stageDefinitions.Count <= 0)
|
||||
throw new ArgumentException("Can't have zero or fewer stages.");
|
||||
|
@ -156,6 +156,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
// must happen before children are disposed in base call to prevent illegal accesses to the judgement pool.
|
||||
NewResult -= OnNewResult;
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (currentSkin != null)
|
||||
|
@ -160,9 +160,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
|
||||
}
|
||||
|
||||
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
||||
private Drawable testSimpleBig(int repeats = 0) => createSlider(repeats: repeats);
|
||||
|
||||
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
|
||||
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(repeats: repeats, stackHeight: 10);
|
||||
|
||||
private Drawable testDistanceOverflow(int repeats = 0)
|
||||
{
|
||||
|
@ -84,8 +84,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
public int Compare(ReplayFrame? f1, ReplayFrame? f2)
|
||||
{
|
||||
if (f1 == null) throw new ArgumentNullException(nameof(f1));
|
||||
if (f2 == null) throw new ArgumentNullException(nameof(f2));
|
||||
ArgumentNullException.ThrowIfNull(f1);
|
||||
ArgumentNullException.ThrowIfNull(f2);
|
||||
|
||||
return f1.Time.CompareTo(f2.Time);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@ -17,42 +16,24 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||
public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
|
||||
{
|
||||
protected readonly HitResult Result;
|
||||
|
||||
protected SpriteText JudgementText { get; private set; } = null!;
|
||||
|
||||
private RingExplosion? ringExplosion;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public ArgonJudgementPiece(HitResult result)
|
||||
: base(result)
|
||||
{
|
||||
Result = result;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
JudgementText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = Result.GetDescription().ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(Result),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(5, 0),
|
||||
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold),
|
||||
},
|
||||
};
|
||||
|
||||
if (Result.IsHit())
|
||||
{
|
||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||
@ -62,6 +43,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
}
|
||||
}
|
||||
|
||||
protected override SpriteText CreateJudgementText() =>
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(5, 0),
|
||||
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Plays the default animation for this judgement piece.
|
||||
/// </summary>
|
||||
|
@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
switch (lookup)
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||
return Drawable.Empty();
|
||||
|
||||
return new ArgonJudgementPiece(resultComponent.Component);
|
||||
|
||||
case OsuSkinComponentLookup osuComponent:
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
HitPolicy = new StartTimeOrderedHitPolicy();
|
||||
|
||||
var hitWindows = new OsuHitWindows();
|
||||
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
|
||||
foreach (var result in Enum.GetValues<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
|
||||
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
|
||||
|
||||
AddRangeInternal(poolDictionary.Values);
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -18,20 +17,16 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||
public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
|
||||
{
|
||||
protected readonly HitResult Result;
|
||||
|
||||
protected SpriteText JudgementText { get; private set; } = null!;
|
||||
|
||||
private RingExplosion? ringExplosion;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public ArgonJudgementPiece(HitResult result)
|
||||
: base(result)
|
||||
{
|
||||
Result = result;
|
||||
RelativePositionAxes = Axes.Both;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
@ -39,21 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
JudgementText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = Result.GetDescription().ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(Result),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(10, 0),
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular),
|
||||
},
|
||||
};
|
||||
|
||||
if (Result.IsHit())
|
||||
{
|
||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||
@ -64,6 +44,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
}
|
||||
}
|
||||
|
||||
protected override SpriteText CreateJudgementText() =>
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(10, 0),
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Plays the default animation for this judgement piece.
|
||||
/// </summary>
|
||||
|
@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
switch (component)
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||
return Drawable.Empty();
|
||||
|
||||
return new ArgonJudgementPiece(resultComponent.Component);
|
||||
|
||||
case TaikoSkinComponentLookup taikoComponent:
|
||||
|
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
var hitWindows = new TaikoHitWindows();
|
||||
|
||||
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)))
|
||||
foreach (var result in Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)))
|
||||
{
|
||||
judgementPools.Add(result, new DrawablePool<DrawableTaikoJudgement>(15));
|
||||
explosionPools.Add(result, new HitExplosionPool(result));
|
||||
|
@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@ -139,6 +140,29 @@ namespace osu.Game.Tests.Gameplay
|
||||
Assert.That(score.MaximumStatistics[HitResult.LargeBonus], Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAccuracyModes()
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = Enumerable.Range(0, 4).Select(_ => new TestHitObject(HitResult.Great)).ToList<HitObject>()
|
||||
};
|
||||
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0));
|
||||
Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1));
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great });
|
||||
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo((double)(100 + 3 * 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
public override HitResult MaxResult { get; }
|
||||
|
@ -60,6 +60,6 @@ namespace osu.Game.Tests.Mods
|
||||
/// This local helper is used rather than <see cref="Ruleset.CreateAllMods"/>, because the aforementioned method flattens multi mods.
|
||||
/// </remarks>>
|
||||
private static IEnumerable<MultiMod> getMultiMods(Ruleset ruleset)
|
||||
=> Enum.GetValues(typeof(ModType)).Cast<ModType>().SelectMany(ruleset.GetModsFor).OfType<MultiMod>();
|
||||
=> Enum.GetValues<ModType>().SelectMany(ruleset.GetModsFor).OfType<MultiMod>();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
|
||||
{
|
||||
private ZoomableScrollContainer scrollContainer;
|
||||
private TestZoomableScrollContainer scrollContainer;
|
||||
private Drawable innerBox;
|
||||
|
||||
[SetUpSteps]
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(30)
|
||||
},
|
||||
scrollContainer = new ZoomableScrollContainer(1, 60, 1)
|
||||
scrollContainer = new TestZoomableScrollContainer(1, 60, 1)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -93,6 +93,14 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWidthUpdatesOnSecondZoomSetup()
|
||||
{
|
||||
AddAssert("Inner container width = 1x", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||
AddStep("reload zoom", () => scrollContainer.SetupZoom(10, 10, 60));
|
||||
AddAssert("Inner container width = 10x", () => innerBox.DrawWidth == scrollContainer.DrawWidth * 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestZoom0()
|
||||
{
|
||||
@ -190,5 +198,15 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
|
||||
private Quad boxQuad => innerBox.ScreenSpaceDrawQuad;
|
||||
|
||||
private partial class TestZoomableScrollContainer : ZoomableScrollContainer
|
||||
{
|
||||
public TestZoomableScrollContainer(int minimum, float maximum, float initial)
|
||||
: base(minimum, maximum, initial)
|
||||
{
|
||||
}
|
||||
|
||||
public new void SetupZoom(float initial, float minimum, float maximum) => base.SetupZoom(initial, minimum, maximum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTestAPI(true);
|
||||
|
||||
createPlayerTest(false, createRuleset: () => new OsuRuleset
|
||||
createPlayerTest(createRuleset: () => new OsuRuleset
|
||||
{
|
||||
RulesetInfo =
|
||||
{
|
||||
|
@ -201,6 +201,23 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddAssert("volume not changed", () => Audio.Volume.Value == 0.5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetSelectorOverflow()
|
||||
{
|
||||
AddStep("set toolbar width", () =>
|
||||
{
|
||||
toolbar.RelativeSizeAxes = Axes.None;
|
||||
toolbar.Width = 400;
|
||||
});
|
||||
AddStep("move mouse over news toggle button", () =>
|
||||
{
|
||||
var button = toolbar.ChildrenOfType<ToolbarNewsButton>().Single();
|
||||
InputManager.MoveMouseTo(button);
|
||||
});
|
||||
AddAssert("no ruleset toggle buttons hovered", () => !toolbar.ChildrenOfType<ToolbarRulesetTabButton>().Any(button => button.IsHovered));
|
||||
AddUntilStep("toolbar gradient visible", () => toolbar.ChildrenOfType<Toolbar.ToolbarBackground>().Single().Children.All(d => d.Alpha > 0));
|
||||
}
|
||||
|
||||
public partial class TestToolbar : Toolbar
|
||||
{
|
||||
public new Bindable<OverlayActivation> OverlayActivationMode => base.OverlayActivationMode as Bindable<OverlayActivation>;
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -46,10 +47,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("item removed", () => !playlist.Items.Contains(selectedItem));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextItemSelectedAfterDeletion()
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestNextItemSelectedAfterDeletion(bool allowSelection)
|
||||
{
|
||||
createPlaylist();
|
||||
createPlaylist(p =>
|
||||
{
|
||||
p.AllowSelection = allowSelection;
|
||||
});
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
@ -57,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
|
||||
AddAssert("item 0 is " + (allowSelection ? "selected" : "not selected"), () => playlist.SelectedItem.Value == (allowSelection ? playlist.Items[0] : null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -117,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
InputManager.MoveMouseTo(item.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(0), offset);
|
||||
});
|
||||
|
||||
private void createPlaylist()
|
||||
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
@ -154,6 +159,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupPlaylist?.Invoke(playlist);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
|
@ -530,6 +530,52 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTextBoxSavePerChannel()
|
||||
{
|
||||
var testPMChannel = new Channel(testUser);
|
||||
|
||||
AddStep("show overlay", () => chatOverlay.Show());
|
||||
joinTestChannel(0);
|
||||
joinChannel(testPMChannel);
|
||||
|
||||
AddAssert("listing is visible", () => listingIsVisible);
|
||||
AddStep("search for 'number 2'", () => chatOverlayTextBox.Text = "number 2");
|
||||
AddAssert("'number 2' saved to selector", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "number 2");
|
||||
|
||||
AddStep("select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||
AddAssert("text box cleared on normal channel", () => chatOverlayTextBox.Text == string.Empty);
|
||||
AddAssert("nothing saved on normal channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == string.Empty);
|
||||
AddStep("type '727'", () => chatOverlayTextBox.Text = "727");
|
||||
AddAssert("'727' saved to normal channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "727");
|
||||
|
||||
AddStep("select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
|
||||
AddAssert("text box cleared on PM channel", () => chatOverlayTextBox.Text == string.Empty);
|
||||
AddAssert("nothing saved on PM channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == string.Empty);
|
||||
AddStep("type 'hello'", () => chatOverlayTextBox.Text = "hello");
|
||||
AddAssert("'hello' saved to PM channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "hello");
|
||||
|
||||
AddStep("select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||
AddAssert("text box contains '727'", () => chatOverlayTextBox.Text == "727");
|
||||
|
||||
AddStep("select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
|
||||
AddAssert("text box contains 'hello'", () => chatOverlayTextBox.Text == "hello");
|
||||
AddStep("click close button", () =>
|
||||
{
|
||||
ChannelListItemCloseButton closeButton = getChannelListItem(testPMChannel).ChildrenOfType<ChannelListItemCloseButton>().Single();
|
||||
clickDrawable(closeButton);
|
||||
});
|
||||
|
||||
AddAssert("listing is visible", () => listingIsVisible);
|
||||
AddAssert("text box contains 'channel 2'", () => chatOverlayTextBox.Text == "number 2");
|
||||
AddUntilStep("only channel 2 visible", () =>
|
||||
{
|
||||
IEnumerable<ChannelListingItem> listingItems = chatOverlay.ChildrenOfType<ChannelListingItem>()
|
||||
.Where(item => item.IsPresent);
|
||||
return listingItems.Count() == 1 && listingItems.Single().Channel == testChannel2;
|
||||
});
|
||||
}
|
||||
|
||||
private void joinTestChannel(int i)
|
||||
{
|
||||
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
||||
|
@ -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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -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.Game.Overlays.Profile.Sections.Kudosu;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
@ -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.Graphics;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
@ -24,7 +22,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public TestSceneProfileRulesetSelector()
|
||||
{
|
||||
ProfileRulesetSelector selector;
|
||||
var user = new Bindable<APIUser>();
|
||||
var user = new Bindable<APIUser?>();
|
||||
|
||||
Child = selector = new ProfileRulesetSelector
|
||||
{
|
||||
|
302
osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
Normal file
302
osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
Normal file
@ -0,0 +1,302 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[HeadlessTest]
|
||||
public partial class TestSceneSoloStatisticsWatcher : OsuTestScene
|
||||
{
|
||||
protected override bool UseOnlineAPI => false;
|
||||
|
||||
private SoloStatisticsWatcher watcher = null!;
|
||||
|
||||
[Resolved]
|
||||
private SpectatorClient spectatorClient { get; set; } = null!;
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private Action<GetUsersRequest>? handleGetUsersRequest;
|
||||
private Action<GetUserRequest>? handleGetUserRequest;
|
||||
|
||||
private IDisposable? subscription;
|
||||
|
||||
private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("clear server-side stats", () => serverSideStatistics.Clear());
|
||||
AddStep("set up request handling", () =>
|
||||
{
|
||||
handleGetUserRequest = null;
|
||||
handleGetUsersRequest = null;
|
||||
|
||||
dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetUsersRequest getUsersRequest:
|
||||
if (handleGetUsersRequest != null)
|
||||
{
|
||||
handleGetUsersRequest?.Invoke(getUsersRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
int userId = getUsersRequest.UserIds.Single();
|
||||
var response = new GetUsersResponse
|
||||
{
|
||||
Users = new List<APIUser>
|
||||
{
|
||||
new APIUser
|
||||
{
|
||||
Id = userId,
|
||||
RulesetsStatistics = new Dictionary<string, UserStatistics>
|
||||
{
|
||||
["osu"] = tryGetStatistics(userId, "osu"),
|
||||
["taiko"] = tryGetStatistics(userId, "taiko"),
|
||||
["fruits"] = tryGetStatistics(userId, "fruits"),
|
||||
["mania"] = tryGetStatistics(userId, "mania"),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
getUsersRequest.TriggerSuccess(response);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case GetUserRequest getUserRequest:
|
||||
if (handleGetUserRequest != null)
|
||||
{
|
||||
handleGetUserRequest.Invoke(getUserRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
int userId = int.Parse(getUserRequest.Lookup);
|
||||
string rulesetName = getUserRequest.Ruleset.ShortName;
|
||||
var response = new APIUser
|
||||
{
|
||||
Id = userId,
|
||||
Statistics = tryGetStatistics(userId, rulesetName)
|
||||
};
|
||||
getUserRequest.TriggerSuccess(response);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("create watcher", () =>
|
||||
{
|
||||
Child = watcher = new SoloStatisticsWatcher();
|
||||
});
|
||||
}
|
||||
|
||||
private UserStatistics tryGetStatistics(int userId, string rulesetName)
|
||||
=> serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics();
|
||||
|
||||
[Test]
|
||||
public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed()
|
||||
{
|
||||
int userId = getUserId();
|
||||
long scoreId = getScoreId();
|
||||
setUpUser(userId);
|
||||
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddUntilStep("update received", () => update != null);
|
||||
AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000));
|
||||
AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded()
|
||||
{
|
||||
int userId = getUserId();
|
||||
setUpUser(userId);
|
||||
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
// note ordering - in this test processing completes *before* the registration is added.
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddUntilStep("update received", () => update != null);
|
||||
AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000));
|
||||
AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(5_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatisticsUpdateNotFiredIfUserLoggedOut()
|
||||
{
|
||||
int userId = getUserId();
|
||||
setUpUser(userId);
|
||||
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("log out user", () => dummyAPI.Logout());
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddWaitStep("wait a bit", 5);
|
||||
AddAssert("update not received", () => update == null);
|
||||
|
||||
AddStep("log in user", () => dummyAPI.Login("user", "password"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn()
|
||||
{
|
||||
int userId = getUserId();
|
||||
setUpUser(userId);
|
||||
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("change user", () => dummyAPI.LocalUser.Value = new APIUser { Id = getUserId() });
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddWaitStep("wait a bit", 5);
|
||||
AddAssert("update not received", () => update == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch()
|
||||
{
|
||||
int userId = getUserId();
|
||||
setUpUser(userId);
|
||||
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal another score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, getScoreId()));
|
||||
AddWaitStep("wait a bit", 5);
|
||||
AddAssert("update not received", () => update == null);
|
||||
}
|
||||
|
||||
// the behaviour exercised in this test may not be final, it is mostly assumed for simplicity.
|
||||
// in the long run we may want each score's update to be entirely isolated from others, rather than have prior unobserved updates merge into the latest.
|
||||
[Test]
|
||||
public void TestIgnoredScoreUpdateIsMergedIntoNextOne()
|
||||
{
|
||||
int userId = getUserId();
|
||||
setUpUser(userId);
|
||||
|
||||
long firstScoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, firstScoreId));
|
||||
|
||||
long secondScoreId = getScoreId();
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 6_000_000);
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
|
||||
AddUntilStep("update received", () => update != null);
|
||||
AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000));
|
||||
AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal()
|
||||
{
|
||||
int userId = getUserId();
|
||||
setUpUser(userId);
|
||||
|
||||
long scoreId = getScoreId();
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
SoloStatisticsUpdate? update = null;
|
||||
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
|
||||
AddStep("unsubscribe", () => subscription!.Dispose());
|
||||
|
||||
feignScoreProcessing(userId, ruleset, 5_000_000);
|
||||
|
||||
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
|
||||
AddWaitStep("wait a bit", 5);
|
||||
AddAssert("update not received", () => update == null);
|
||||
}
|
||||
|
||||
private int nextUserId = 2000;
|
||||
private long nextScoreId = 50000;
|
||||
|
||||
private int getUserId() => ++nextUserId;
|
||||
private long getScoreId() => ++nextScoreId;
|
||||
|
||||
private void setUpUser(int userId)
|
||||
{
|
||||
AddStep("fetch initial stats", () =>
|
||||
{
|
||||
serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 };
|
||||
serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 };
|
||||
serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 };
|
||||
serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 };
|
||||
|
||||
dummyAPI.LocalUser.Value = new APIUser { Id = userId };
|
||||
});
|
||||
}
|
||||
|
||||
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<SoloStatisticsUpdate> onUpdateReady) =>
|
||||
AddStep("register for updates", () => subscription = watcher.RegisterForStatisticsUpdateAfter(
|
||||
new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser())
|
||||
{
|
||||
Ruleset = rulesetInfo,
|
||||
OnlineID = scoreId
|
||||
},
|
||||
onUpdateReady));
|
||||
|
||||
private void feignScoreProcessing(int userId, RulesetInfo rulesetInfo, long newTotalScore)
|
||||
=> AddStep("feign score processing", () => serverSideStatistics[(userId, rulesetInfo.ShortName)] = new UserStatistics { TotalScore = newTotalScore });
|
||||
}
|
||||
}
|
@ -50,6 +50,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private ChannelManager channelManager;
|
||||
|
||||
private TestStandAloneChatDisplay chatDisplay;
|
||||
private TestStandAloneChatDisplay chatWithTextBox;
|
||||
private TestStandAloneChatDisplay chatWithTextBox2;
|
||||
private int messageIdSequence;
|
||||
|
||||
private Channel testChannel;
|
||||
@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private void reinitialiseDrawableDisplay()
|
||||
{
|
||||
Children = new[]
|
||||
Children = new Drawable[]
|
||||
{
|
||||
chatDisplay = new TestStandAloneChatDisplay
|
||||
{
|
||||
@ -88,13 +90,28 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Size = new Vector2(400, 80),
|
||||
Channel = { Value = testChannel },
|
||||
},
|
||||
new TestStandAloneChatDisplay(true)
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Margin = new MarginPadding(20),
|
||||
Size = new Vector2(400, 150),
|
||||
Channel = { Value = testChannel },
|
||||
Children = new[]
|
||||
{
|
||||
chatWithTextBox = new TestStandAloneChatDisplay(true)
|
||||
{
|
||||
Margin = new MarginPadding(20),
|
||||
Size = new Vector2(400, 150),
|
||||
Channel = { Value = testChannel },
|
||||
},
|
||||
chatWithTextBox2 = new TestStandAloneChatDisplay(true)
|
||||
{
|
||||
Margin = new MarginPadding(20),
|
||||
Size = new Vector2(400, 150),
|
||||
Channel = { Value = testChannel },
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -351,6 +368,13 @@ namespace osu.Game.Tests.Visual.Online
|
||||
checkScrolledToBottom();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTextBoxSync()
|
||||
{
|
||||
AddStep("type 'hello' to text box 1", () => chatWithTextBox.ChildrenOfType<StandAloneChatDisplay.ChatTextBox>().Single().Text = "hello");
|
||||
AddAssert("text box 2 contains 'hello'", () => chatWithTextBox2.ChildrenOfType<StandAloneChatDisplay.ChatTextBox>().Single().Text == "hello");
|
||||
}
|
||||
|
||||
private void fillChat(int count = 10)
|
||||
{
|
||||
AddStep("fill chat", () =>
|
||||
|
@ -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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
private ProfileHeader header;
|
||||
private ProfileHeader header = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
|
@ -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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@ -14,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[TestFixture]
|
||||
public partial class TestSceneUserProfilePreviousUsernames : OsuTestScene
|
||||
{
|
||||
private PreviousUsernames container;
|
||||
private PreviousUsernames container = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("Is hidden", () => container.Alpha == 0);
|
||||
}
|
||||
|
||||
private static readonly APIUser[] users =
|
||||
private static readonly APIUser?[] users =
|
||||
{
|
||||
new APIUser { Id = 1, PreviousUsernames = new[] { "username1" } },
|
||||
new APIUser { Id = 2, PreviousUsernames = new[] { "longusername", "longerusername" } },
|
||||
|
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
|
@ -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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -4,8 +4,11 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -29,6 +32,15 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("Show main page", () => wiki.Show());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancellationDoesntShowError()
|
||||
{
|
||||
AddStep("Show main page", () => wiki.Show());
|
||||
AddStep("Show another page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
|
||||
|
||||
AddUntilStep("Current path is not error", () => wiki.CurrentPath != "error");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestArticlePage()
|
||||
{
|
||||
@ -56,7 +68,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public void TestErrorPage()
|
||||
{
|
||||
setUpWikiResponse(responseArticlePage);
|
||||
AddStep("Show Error Page", () => wiki.ShowPage("Error"));
|
||||
AddStep("Show nonexistent page", () => wiki.ShowPage("This_page_will_error_out"));
|
||||
AddUntilStep("Wait for error page", () => wiki.CurrentPath == "error");
|
||||
AddUntilStep("Error message correct", () => wiki.ChildrenOfType<SpriteText>().Any(text => text.Text == "\"This_page_will_error_out\"."));
|
||||
}
|
||||
|
||||
private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null)
|
||||
|
117
osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
Normal file
117
osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
Normal file
@ -0,0 +1,117 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics.User;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneOverallRanking : OsuTestScene
|
||||
{
|
||||
private OverallRanking overallRanking = null!;
|
||||
|
||||
[Test]
|
||||
public void TestUpdatePending()
|
||||
{
|
||||
createDisplay();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllIncreased()
|
||||
{
|
||||
createDisplay();
|
||||
displayUpdate(
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
},
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 1_234,
|
||||
Accuracy = 99.07,
|
||||
MaxCombo = 2_352,
|
||||
RankedScore = 23_124_231_435,
|
||||
TotalScore = 123_124_231_435,
|
||||
PP = 5_434
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllDecreased()
|
||||
{
|
||||
createDisplay();
|
||||
displayUpdate(
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 1_234,
|
||||
Accuracy = 99.07,
|
||||
MaxCombo = 2_352,
|
||||
RankedScore = 23_124_231_435,
|
||||
TotalScore = 123_124_231_435,
|
||||
PP = 5_434
|
||||
},
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoChanges()
|
||||
{
|
||||
var statistics = new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
};
|
||||
|
||||
createDisplay();
|
||||
displayUpdate(statistics, statistics);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotRanked()
|
||||
{
|
||||
var statistics = new UserStatistics
|
||||
{
|
||||
GlobalRank = null,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = null
|
||||
};
|
||||
|
||||
createDisplay();
|
||||
displayUpdate(statistics, statistics);
|
||||
}
|
||||
|
||||
private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking
|
||||
{
|
||||
Width = 400,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
|
||||
private void displayUpdate(UserStatistics before, UserStatistics after) =>
|
||||
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new SoloStatisticsUpdate(new ScoreInfo(), before, after));
|
||||
}
|
||||
}
|
@ -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 NUnit.Framework;
|
||||
using osu.Game.Overlays.Profile.Sections;
|
||||
using osu.Framework.Testing;
|
||||
@ -18,7 +16,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||
|
||||
private ProfileSubsectionHeader header;
|
||||
private ProfileSubsectionHeader header = null!;
|
||||
|
||||
[Test]
|
||||
public void TestHiddenCounter()
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components
|
||||
|
||||
public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null)
|
||||
{
|
||||
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
|
||||
ArgumentNullException.ThrowIfNull(beatmap);
|
||||
|
||||
Beatmap = beatmap;
|
||||
this.mod = mod;
|
||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Tournament.IPC
|
||||
using (var stream = IPCStorage.GetStream(file_ipc_state_filename))
|
||||
using (var sr = new StreamReader(stream))
|
||||
{
|
||||
State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine().AsNonNull());
|
||||
State.Value = Enum.Parse<TourneyState>(sr.ReadLine().AsNonNull());
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
||||
continue;
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
string[] split = line.Split(':');
|
||||
string[] split = line.Split(':', StringSplitOptions.TrimEntries);
|
||||
|
||||
if (split.Length < 2)
|
||||
{
|
||||
@ -55,9 +55,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
||||
|
||||
teams.Add(new TournamentTeam
|
||||
{
|
||||
FullName = { Value = split[1].Trim(), },
|
||||
Acronym = { Value = split.Length >= 3 ? split[2].Trim() : null, },
|
||||
FlagName = { Value = split[0].Trim() }
|
||||
FullName = { Value = split[1], },
|
||||
Acronym = { Value = split.Length >= 3 ? split[2] : null, },
|
||||
FlagName = { Value = split[0] }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
var countries = new List<TournamentTeam>();
|
||||
|
||||
foreach (var country in Enum.GetValues(typeof(CountryCode)).Cast<CountryCode>().Skip(1))
|
||||
foreach (var country in Enum.GetValues<CountryCode>().Skip(1))
|
||||
{
|
||||
countries.Add(new TournamentTeam
|
||||
{
|
||||
|
@ -211,8 +211,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public static T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
||||
where T : class, IControlPoint
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
ArgumentNullException.ThrowIfNull(list);
|
||||
|
||||
if (list.Count == 0)
|
||||
return null;
|
||||
|
@ -15,8 +15,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
public BeatmapBackgroundSprite(IWorkingBeatmap working)
|
||||
{
|
||||
if (working == null)
|
||||
throw new ArgumentNullException(nameof(working));
|
||||
ArgumentNullException.ThrowIfNull(working);
|
||||
|
||||
this.working = working;
|
||||
}
|
||||
|
@ -18,8 +18,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
public OnlineBeatmapSetCover(IBeatmapSetOnlineInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover)
|
||||
{
|
||||
if (set == null)
|
||||
throw new ArgumentNullException(nameof(set));
|
||||
ArgumentNullException.ThrowIfNull(set);
|
||||
|
||||
this.set = set;
|
||||
this.type = type;
|
||||
|
@ -57,8 +57,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
public static Decoder<T> GetDecoder<T>(LineBufferedReader stream)
|
||||
where T : new()
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
if (!decoders.TryGetValue(typeof(T), out var typedDecoders))
|
||||
throw new IOException(@"Unknown decoder type");
|
||||
|
@ -160,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"SampleSet":
|
||||
defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
|
||||
defaultSampleBank = Enum.Parse<LegacySampleBank>(pair.Value);
|
||||
break;
|
||||
|
||||
case @"SampleVolume":
|
||||
@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"Countdown":
|
||||
beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value);
|
||||
beatmap.BeatmapInfo.Countdown = Enum.Parse<CountdownType>(pair.Value);
|
||||
break;
|
||||
|
||||
case @"CountdownOffset":
|
||||
|
@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
var comboColour = colours[i];
|
||||
|
||||
writer.Write(FormattableString.Invariant($"Combo{i}: "));
|
||||
writer.Write(FormattableString.Invariant($"Combo{1 + i}: "));
|
||||
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},"));
|
||||
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},"));
|
||||
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},"));
|
||||
|
@ -132,13 +132,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator = ':', bool shouldTrim = true)
|
||||
{
|
||||
string[] split = line.Split(separator, 2);
|
||||
|
||||
if (shouldTrim)
|
||||
{
|
||||
for (int i = 0; i < split.Length; i++)
|
||||
split[i] = split[i].Trim();
|
||||
}
|
||||
string[] split = line.Split(separator, 2, shouldTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None);
|
||||
|
||||
return new KeyValuePair<string, string>
|
||||
(
|
||||
|
@ -301,11 +301,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private string parseLayer(string value) => Enum.Parse(typeof(LegacyStoryLayer), value).ToString();
|
||||
private string parseLayer(string value) => Enum.Parse<LegacyStoryLayer>(value).ToString();
|
||||
|
||||
private Anchor parseOrigin(string value)
|
||||
{
|
||||
var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
|
||||
var origin = Enum.Parse<LegacyOrigins>(value);
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
@ -343,8 +343,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
private AnimationLoopType parseAnimationLoopType(string value)
|
||||
{
|
||||
var parsed = (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), value);
|
||||
return Enum.IsDefined(typeof(AnimationLoopType), parsed) ? parsed : AnimationLoopType.LoopForever;
|
||||
var parsed = Enum.Parse<AnimationLoopType>(value);
|
||||
return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever;
|
||||
}
|
||||
|
||||
private void handleVariables(string line)
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
@ -63,10 +62,10 @@ namespace osu.Game.Database
|
||||
|
||||
private void copyToStore(RealmFile file, Stream data, bool preferHardLinks)
|
||||
{
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs && preferHardLinks)
|
||||
if (data is FileStream fs && preferHardLinks)
|
||||
{
|
||||
// attempt to do a fast hard link rather than copy.
|
||||
if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero))
|
||||
if (HardLinkHelper.TryCreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name))
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,7 @@ namespace osu.Game.Graphics.Containers
|
||||
/// <param name="easing">The easing type of the initial transform.</param>
|
||||
public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None)
|
||||
{
|
||||
if (logo == null)
|
||||
throw new ArgumentNullException(nameof(logo));
|
||||
ArgumentNullException.ThrowIfNull(logo);
|
||||
|
||||
if (logo.IsTracking && Logo == null)
|
||||
throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s");
|
||||
|
@ -22,38 +22,8 @@ namespace osu.Game.Graphics
|
||||
public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a <see cref="DifficultyRating"/>.
|
||||
/// Retrieves the colour for a given point in the star range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sourced from the @diff-{rating} variables in https://github.com/ppy/osu-web/blob/71fbab8936d79a7929d13854f5e854b4f383b236/resources/assets/less/variables.less.
|
||||
/// </remarks>
|
||||
public Color4 ForDifficultyRating(DifficultyRating difficulty, bool useLighterColour = false)
|
||||
{
|
||||
switch (difficulty)
|
||||
{
|
||||
case DifficultyRating.Easy:
|
||||
return Color4Extensions.FromHex("4ebfff");
|
||||
|
||||
case DifficultyRating.Normal:
|
||||
return Color4Extensions.FromHex("66ff91");
|
||||
|
||||
case DifficultyRating.Hard:
|
||||
return Color4Extensions.FromHex("f7e85d");
|
||||
|
||||
case DifficultyRating.Insane:
|
||||
return Color4Extensions.FromHex("ff7e68");
|
||||
|
||||
case DifficultyRating.Expert:
|
||||
return Color4Extensions.FromHex("fe3c71");
|
||||
|
||||
case DifficultyRating.ExpertPlus:
|
||||
return Color4Extensions.FromHex("6662dd");
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(difficulty));
|
||||
}
|
||||
}
|
||||
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[]
|
||||
{
|
||||
(0.1f, Color4Extensions.FromHex("aaaaaa")),
|
||||
|
@ -52,8 +52,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public readonly SpriteIcon Chevron;
|
||||
|
||||
//don't allow clicking between transitions and don't make the chevron clickable
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && Text.ReceivePositionalInputAt(screenSpacePos);
|
||||
//don't allow clicking between transitions
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && base.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public override bool HandleNonPositionalInput => State == Visibility.Visible;
|
||||
public override bool HandlePositionalInput => State == Visibility.Visible;
|
||||
@ -95,7 +95,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Text.Font = Text.Font.With(size: 18);
|
||||
Text.Margin = new MarginPadding { Vertical = 8 };
|
||||
Padding = new MarginPadding { Right = padding + ChevronSize };
|
||||
Margin = new MarginPadding { Right = padding + ChevronSize };
|
||||
Add(Chevron = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
if (Link != null)
|
||||
{
|
||||
items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link)));
|
||||
items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => host.OpenUrlExternally(Link)));
|
||||
items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl));
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Up:
|
||||
|
@ -114,8 +114,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
current.UnbindBindings();
|
||||
current.BindTo(value);
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public OsuEnumDropdown()
|
||||
{
|
||||
Items = (T[])Enum.GetValues(typeof(T));
|
||||
Items = Enum.GetValues<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,7 @@ namespace osu.Game.IO.FileAbstraction
|
||||
|
||||
public void CloseStream(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
stream.Close();
|
||||
}
|
||||
|
@ -14,9 +14,8 @@ namespace osu.Game.IO
|
||||
{
|
||||
public static bool CheckAvailability(string testDestinationPath, string testSourcePath)
|
||||
{
|
||||
// We can support other operating systems quite easily in the future.
|
||||
// Let's handle the most common one for now, though.
|
||||
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
|
||||
// For simplicity, only support desktop operating systems for now.
|
||||
if (!RuntimeInfo.IsDesktop)
|
||||
return false;
|
||||
|
||||
const string test_filename = "_hard_link_test";
|
||||
@ -31,7 +30,7 @@ namespace osu.Game.IO
|
||||
File.WriteAllText(testSourcePath, string.Empty);
|
||||
|
||||
// Test availability by creating an arbitrary hard link between the source and destination paths.
|
||||
return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero);
|
||||
return TryCreateHardLink(testDestinationPath, testSourcePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -55,21 +54,60 @@ namespace osu.Game.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create a hard link from <paramref name="sourcePath"/> to <paramref name="destinationPath"/>,
|
||||
/// using platform-specific native methods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Hard links are only available on desktop platforms.
|
||||
/// </remarks>
|
||||
/// <returns>Whether the hard link was successfully created.</returns>
|
||||
public static bool TryCreateHardLink(string destinationPath, string sourcePath)
|
||||
{
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
return CreateHardLink(destinationPath, sourcePath, IntPtr.Zero);
|
||||
|
||||
case RuntimeInfo.Platform.Linux:
|
||||
case RuntimeInfo.Platform.macOS:
|
||||
return link(sourcePath, destinationPath) == 0;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For future use (to detect if a file is a hard link with other references existing on disk).
|
||||
public static int GetFileLinkCount(string filePath)
|
||||
{
|
||||
int result = 0;
|
||||
SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
|
||||
|
||||
ByHandleFileInformation fileInfo;
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
|
||||
|
||||
if (GetFileInformationByHandle(handle, out fileInfo))
|
||||
result = (int)fileInfo.NumberOfLinks;
|
||||
CloseHandle(handle);
|
||||
ByHandleFileInformation fileInfo;
|
||||
|
||||
if (GetFileInformationByHandle(handle, out fileInfo))
|
||||
result = (int)fileInfo.NumberOfLinks;
|
||||
CloseHandle(handle);
|
||||
break;
|
||||
|
||||
case RuntimeInfo.Platform.Linux:
|
||||
case RuntimeInfo.Platform.macOS:
|
||||
if (stat(filePath, out var statbuf) == 0)
|
||||
result = (int)statbuf.st_nlink;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Windows native methods
|
||||
|
||||
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
|
||||
|
||||
@ -104,5 +142,49 @@ namespace osu.Game.IO
|
||||
public readonly uint FileIndexHigh;
|
||||
public readonly uint FileIndexLow;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Linux native methods
|
||||
|
||||
#pragma warning disable IDE1006 // Naming rule violation
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
public static extern int link(string oldpath, string newpath);
|
||||
|
||||
[DllImport("libc", SetLastError = true)]
|
||||
private static extern int stat(string pathname, out struct_stat statbuf);
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// Struct layout is likely non-portable across unices. Tread with caution.
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct struct_stat
|
||||
{
|
||||
public readonly long st_dev;
|
||||
public readonly long st_ino;
|
||||
public readonly long st_nlink;
|
||||
public readonly int st_mode;
|
||||
public readonly int st_uid;
|
||||
public readonly int st_gid;
|
||||
public readonly long st_rdev;
|
||||
public readonly long st_size;
|
||||
public readonly long st_blksize;
|
||||
public readonly long st_blocks;
|
||||
public readonly timespec st_atim;
|
||||
public readonly timespec st_mtim;
|
||||
public readonly timespec st_ctim;
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct timespec
|
||||
{
|
||||
public readonly long tv_sec;
|
||||
public readonly long tv_nsec;
|
||||
}
|
||||
|
||||
#pragma warning restore IDE1006
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard");
|
||||
|
||||
/// <summary>
|
||||
/// "Learn more about lazer"
|
||||
/// </summary>
|
||||
public static LocalisableString LearnMoreAboutLazer => new TranslatableString(getKey(@"learn_more_about_lazer"), @"Learn more about lazer");
|
||||
|
||||
/// <summary>
|
||||
/// "Check out the feature comparison and FAQ"
|
||||
/// </summary>
|
||||
public static LocalisableString LearnMoreAboutLazerTooltip => new TranslatableString(getKey(@"check_out_the_feature_comparison"), @"Check out the feature comparison and FAQ");
|
||||
|
||||
/// <summary>
|
||||
/// "You are running the latest release ({0})"
|
||||
/// </summary>
|
||||
|
@ -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.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
@ -11,10 +9,11 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetScoresRequest : APIRequest<APIScoresCollection>
|
||||
public class GetScoresRequest : APIRequest<APIScoresCollection>, IEquatable<GetScoresRequest>
|
||||
{
|
||||
public const int MAX_SCORES_PER_REQUEST = 50;
|
||||
|
||||
@ -23,7 +22,7 @@ namespace osu.Game.Online.API.Requests
|
||||
private readonly IRulesetInfo ruleset;
|
||||
private readonly IEnumerable<IMod> mods;
|
||||
|
||||
public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
|
||||
public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod>? mods = null)
|
||||
{
|
||||
if (beatmapInfo.OnlineID <= 0)
|
||||
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}.");
|
||||
@ -51,5 +50,16 @@ namespace osu.Game.Online.API.Requests
|
||||
|
||||
return query.ToString();
|
||||
}
|
||||
|
||||
public bool Equals(GetScoresRequest? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
return beatmapInfo.Equals(other.beatmapInfo)
|
||||
&& scope == other.scope
|
||||
&& ruleset.Equals(other.ruleset)
|
||||
&& mods.SequenceEqual(other.mods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUsersRequest : APIRequest<GetUsersResponse>
|
||||
{
|
||||
private readonly int[] userIds;
|
||||
public readonly int[] UserIds;
|
||||
|
||||
private const int max_ids_per_request = 50;
|
||||
|
||||
@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
|
||||
if (userIds.Length > max_ids_per_request)
|
||||
throw new ArgumentException($"{nameof(GetUsersRequest)} calls only support up to {max_ids_per_request} IDs at once");
|
||||
|
||||
this.userIds = userIds;
|
||||
UserIds = userIds;
|
||||
}
|
||||
|
||||
protected override string Target => "users/?ids[]=" + string.Join("&ids[]=", userIds);
|
||||
protected override string Target => "users/?ids[]=" + string.Join("&ids[]=", UserIds);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty]
|
||||
private string type
|
||||
{
|
||||
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.ToPascalCase());
|
||||
set => Type = Enum.Parse<RecentActivityType>(value.ToPascalCase());
|
||||
}
|
||||
|
||||
public RecentActivityType Type;
|
||||
@ -29,7 +29,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty]
|
||||
private string scoreRank
|
||||
{
|
||||
set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value);
|
||||
set => ScoreRank = Enum.Parse<ScoreRank>(value);
|
||||
}
|
||||
|
||||
public ScoreRank ScoreRank;
|
||||
|
@ -185,7 +185,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"playstyle")]
|
||||
private string[] playStyle
|
||||
{
|
||||
set => PlayStyles = value?.Select(str => Enum.Parse(typeof(APIPlayStyle), str, true)).Cast<APIPlayStyle>().ToArray();
|
||||
set => PlayStyles = value?.Select(str => Enum.Parse<APIPlayStyle>(str, true)).ToArray();
|
||||
}
|
||||
|
||||
public APIPlayStyle[] PlayStyles;
|
||||
|
@ -98,6 +98,11 @@ namespace osu.Game.Online.Chat
|
||||
/// </summary>
|
||||
public Bindable<Message> HighlightedMessage = new Bindable<Message>();
|
||||
|
||||
/// <summary>
|
||||
/// The current text box message while in this <see cref="Channel"/>.
|
||||
/// </summary>
|
||||
public Bindable<string> TextBoxMessage = new Bindable<string>(string.Empty);
|
||||
|
||||
[JsonConstructor]
|
||||
public Channel()
|
||||
{
|
||||
|
@ -118,8 +118,7 @@ namespace osu.Game.Online.Chat
|
||||
/// <param name="name"></param>
|
||||
public void OpenChannel(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(name);
|
||||
}
|
||||
@ -130,8 +129,7 @@ namespace osu.Game.Online.Chat
|
||||
/// <param name="user">The user the private channel is opened with.</param>
|
||||
public void OpenPrivateChannel(APIUser user)
|
||||
{
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
if (user.Id == api.LocalUser.Value.Id)
|
||||
return;
|
||||
|
@ -111,8 +111,13 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
drawableChannel?.Expire();
|
||||
|
||||
if (e.OldValue != null)
|
||||
TextBox?.Current.UnbindFrom(e.OldValue.TextBoxMessage);
|
||||
|
||||
if (e.NewValue == null) return;
|
||||
|
||||
TextBox?.Current.BindTo(e.NewValue.TextBoxMessage);
|
||||
|
||||
drawableChannel = CreateDrawableChannel(e.NewValue);
|
||||
drawableChannel.CreateChatLineAction = CreateMessage;
|
||||
drawableChannel.Padding = new MarginPadding { Bottom = postingTextBox ? text_box_height : 0 };
|
||||
|
@ -9,7 +9,8 @@ namespace osu.Game.Online
|
||||
{
|
||||
public ProductionEndpointConfiguration()
|
||||
{
|
||||
WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh";
|
||||
WebsiteRootUrl = @"https://osu.ppy.sh";
|
||||
APIEndpointUrl = @"https://lazer.ppy.sh";
|
||||
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
APIClientID = "5";
|
||||
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";
|
||||
|
42
osu.Game/Online/Solo/SoloStatisticsUpdate.cs
Normal file
42
osu.Game/Online/Solo/SoloStatisticsUpdate.cs
Normal file
@ -0,0 +1,42 @@
|
||||
// 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.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Solo
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains data about the change in a user's profile statistics after completing a score.
|
||||
/// </summary>
|
||||
public class SoloStatisticsUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// The score set by the user that triggered the update.
|
||||
/// </summary>
|
||||
public ScoreInfo Score { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's profile statistics prior to the score being set.
|
||||
/// </summary>
|
||||
public UserStatistics Before { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's profile statistics after the score was set.
|
||||
/// </summary>
|
||||
public UserStatistics After { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SoloStatisticsUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="score">The score set by the user that triggered the update.</param>
|
||||
/// <param name="before">The user's profile statistics prior to the score being set.</param>
|
||||
/// <param name="after">The user's profile statistics after the score was set.</param>
|
||||
public SoloStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
|
||||
{
|
||||
Score = score;
|
||||
Before = before;
|
||||
After = after;
|
||||
}
|
||||
}
|
||||
}
|
162
osu.Game/Online/Solo/SoloStatisticsWatcher.cs
Normal file
162
osu.Game/Online/Solo/SoloStatisticsWatcher.cs
Normal file
@ -0,0 +1,162 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Solo
|
||||
{
|
||||
/// <summary>
|
||||
/// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics.
|
||||
/// </summary>
|
||||
public partial class SoloStatisticsWatcher : Component
|
||||
{
|
||||
[Resolved]
|
||||
private SpectatorClient spectatorClient { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private readonly Dictionary<long, StatisticsUpdateCallback> callbacks = new Dictionary<long, StatisticsUpdateCallback>();
|
||||
private long? lastProcessedScoreId;
|
||||
|
||||
private Dictionary<string, UserStatistics>? latestStatistics;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true);
|
||||
spectatorClient.OnUserScoreProcessed += userScoreProcessed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers for a user statistics update after the given <paramref name="score"/> has been processed server-side.
|
||||
/// </summary>
|
||||
/// <param name="score">The score to listen for the statistics update for.</param>
|
||||
/// <param name="onUpdateReady">The callback to be invoked once the statistics update has been prepared.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> representing the subscription. Disposing it is equivalent to unsubscribing from future notifications.</returns>
|
||||
public IDisposable RegisterForStatisticsUpdateAfter(ScoreInfo score, Action<SoloStatisticsUpdate> onUpdateReady)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (!api.IsLoggedIn)
|
||||
return;
|
||||
|
||||
if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0)
|
||||
return;
|
||||
|
||||
var callback = new StatisticsUpdateCallback(score, onUpdateReady);
|
||||
|
||||
if (lastProcessedScoreId == score.OnlineID)
|
||||
{
|
||||
requestStatisticsUpdate(api.LocalUser.Value.Id, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks.Add(score.OnlineID, callback);
|
||||
});
|
||||
|
||||
return new InvokeOnDisposal(() => Schedule(() => callbacks.Remove(score.OnlineID)));
|
||||
}
|
||||
|
||||
private void onUserChanged(APIUser? localUser) => Schedule(() =>
|
||||
{
|
||||
callbacks.Clear();
|
||||
lastProcessedScoreId = null;
|
||||
latestStatistics = null;
|
||||
|
||||
if (localUser == null || localUser.OnlineID <= 1)
|
||||
return;
|
||||
|
||||
var userRequest = new GetUsersRequest(new[] { localUser.OnlineID });
|
||||
userRequest.Success += initialiseUserStatistics;
|
||||
api.Queue(userRequest);
|
||||
});
|
||||
|
||||
private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() =>
|
||||
{
|
||||
var user = response.Users.SingleOrDefault();
|
||||
|
||||
// possible if the user is restricted or similar.
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
latestStatistics = new Dictionary<string, UserStatistics>();
|
||||
|
||||
if (user.RulesetsStatistics != null)
|
||||
{
|
||||
foreach (var rulesetStats in user.RulesetsStatistics)
|
||||
latestStatistics.Add(rulesetStats.Key, rulesetStats.Value);
|
||||
}
|
||||
});
|
||||
|
||||
private void userScoreProcessed(int userId, long scoreId)
|
||||
{
|
||||
if (userId != api.LocalUser.Value?.OnlineID)
|
||||
return;
|
||||
|
||||
lastProcessedScoreId = scoreId;
|
||||
|
||||
if (!callbacks.TryGetValue(scoreId, out var callback))
|
||||
return;
|
||||
|
||||
requestStatisticsUpdate(userId, callback);
|
||||
callbacks.Remove(scoreId);
|
||||
}
|
||||
|
||||
private void requestStatisticsUpdate(int userId, StatisticsUpdateCallback callback)
|
||||
{
|
||||
var request = new GetUserRequest(userId, callback.Score.Ruleset);
|
||||
request.Success += user => Schedule(() => dispatchStatisticsUpdate(callback, user.Statistics));
|
||||
api.Queue(request);
|
||||
}
|
||||
|
||||
private void dispatchStatisticsUpdate(StatisticsUpdateCallback callback, UserStatistics updatedStatistics)
|
||||
{
|
||||
string rulesetName = callback.Score.Ruleset.ShortName;
|
||||
|
||||
if (latestStatistics == null)
|
||||
return;
|
||||
|
||||
latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics);
|
||||
latestRulesetStatistics ??= new UserStatistics();
|
||||
|
||||
var update = new SoloStatisticsUpdate(callback.Score, latestRulesetStatistics, updatedStatistics);
|
||||
callback.OnUpdateReady.Invoke(update);
|
||||
|
||||
latestStatistics[rulesetName] = updatedStatistics;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (spectatorClient.IsNotNull())
|
||||
spectatorClient.OnUserScoreProcessed -= userScoreProcessed;
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
private class StatisticsUpdateCallback
|
||||
{
|
||||
public ScoreInfo Score { get; }
|
||||
public Action<SoloStatisticsUpdate> OnUpdateReady { get; }
|
||||
|
||||
public StatisticsUpdateCallback(ScoreInfo score, Action<SoloStatisticsUpdate> onUpdateReady)
|
||||
{
|
||||
Score = score;
|
||||
OnUpdateReady = onUpdateReady;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -32,5 +32,12 @@ namespace osu.Game.Online.Spectator
|
||||
/// <param name="userId">The user.</param>
|
||||
/// <param name="data">The frame data.</param>
|
||||
Task UserSentFrames(int userId, FrameDataBundle data);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a user's submitted score was fully processed.
|
||||
/// </summary>
|
||||
/// <param name="userId">The ID of the user who achieved the score.</param>
|
||||
/// <param name="scoreId">The ID of the score.</param>
|
||||
Task UserScoreProcessed(int userId, long scoreId);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Online.Spectator
|
||||
connection.On<int, SpectatorState>(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying);
|
||||
connection.On<int, FrameDataBundle>(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames);
|
||||
connection.On<int, SpectatorState>(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying);
|
||||
connection.On<int, long>(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed);
|
||||
};
|
||||
|
||||
IsConnected.BindTo(connector.IsConnected);
|
||||
|
@ -64,6 +64,11 @@ namespace osu.Game.Online.Spectator
|
||||
/// </summary>
|
||||
public virtual event Action<int, SpectatorState>? OnUserFinishedPlaying;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever a user-submitted score has been fully processed.
|
||||
/// </summary>
|
||||
public virtual event Action<int, long>? OnUserScoreProcessed;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary containing all users currently being watched, with the number of watching components for each user.
|
||||
/// </summary>
|
||||
@ -160,6 +165,13 @@ namespace osu.Game.Online.Spectator
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task ISpectatorClient.UserScoreProcessed(int userId, long scoreId)
|
||||
{
|
||||
Schedule(() => OnUserScoreProcessed?.Invoke(userId, scoreId));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void BeginPlaying(long? scoreToken, GameplayState state, Score score)
|
||||
{
|
||||
// This schedule is only here to match the one below in `EndPlaying`.
|
||||
|
@ -354,7 +354,10 @@ namespace osu.Game
|
||||
break;
|
||||
|
||||
case LinkAction.SearchBeatmapSet:
|
||||
SearchBeatmapSet(argString);
|
||||
if (link.Argument is RomanisableString romanisable)
|
||||
SearchBeatmapSet(romanisable.GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript));
|
||||
else
|
||||
SearchBeatmapSet(argString);
|
||||
break;
|
||||
|
||||
case LinkAction.FilterBeatmapSetGenre:
|
||||
@ -736,7 +739,7 @@ namespace osu.Game
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var languages = Enum.GetValues(typeof(Language)).OfType<Language>();
|
||||
var languages = Enum.GetValues<Language>();
|
||||
|
||||
var mappings = languages.Select(language =>
|
||||
{
|
||||
@ -1053,9 +1056,7 @@ namespace osu.Game
|
||||
|
||||
Logger.NewEntry += entry =>
|
||||
{
|
||||
if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return;
|
||||
|
||||
Debug.Assert(entry.Target != null);
|
||||
if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return;
|
||||
|
||||
const int short_term_display_limit = 3;
|
||||
|
||||
|
@ -46,6 +46,7 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
@ -193,6 +194,7 @@ namespace osu.Game
|
||||
protected MultiplayerClient MultiplayerClient { get; private set; }
|
||||
|
||||
private MetadataClient metadataClient;
|
||||
private SoloStatisticsWatcher soloStatisticsWatcher;
|
||||
|
||||
private RealmAccess realm;
|
||||
|
||||
@ -301,6 +303,7 @@ namespace osu.Game
|
||||
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
||||
dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints));
|
||||
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
||||
dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher());
|
||||
|
||||
AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
|
||||
|
||||
@ -346,6 +349,7 @@ namespace osu.Game
|
||||
AddInternal(spectatorClient);
|
||||
AddInternal(MultiplayerClient);
|
||||
AddInternal(metadataClient);
|
||||
AddInternal(soloStatisticsWatcher);
|
||||
|
||||
AddInternal(rulesetConfigCache);
|
||||
|
||||
@ -603,7 +607,7 @@ namespace osu.Game
|
||||
|
||||
try
|
||||
{
|
||||
foreach (ModType type in Enum.GetValues(typeof(ModType)))
|
||||
foreach (ModType type in Enum.GetValues<ModType>())
|
||||
{
|
||||
dict[type] = instance.GetModsFor(type)
|
||||
// Rulesets should never return null mods, but let's be defensive just in case.
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -12,17 +13,20 @@ using osu.Framework.Graphics.Colour;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.BeatmapSet.Buttons;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
@ -41,12 +45,10 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
|
||||
private readonly UpdateableOnlineBeatmapSetCover cover;
|
||||
private readonly Box coverGradient;
|
||||
private readonly OsuSpriteText title, artist;
|
||||
private readonly LinkFlowContainer title, artist;
|
||||
private readonly AuthorInfo author;
|
||||
|
||||
private readonly ExplicitContentBeatmapBadge explicitContent;
|
||||
private readonly SpotlightBeatmapBadge spotlight;
|
||||
private readonly FeaturedArtistBeatmapBadge featuredArtist;
|
||||
private ExternalLinkButton externalLink;
|
||||
|
||||
private readonly FillFlowContainer downloadButtonsContainer;
|
||||
private readonly BeatmapAvailability beatmapAvailability;
|
||||
@ -65,8 +67,6 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
|
||||
public BeatmapSetHeaderContent()
|
||||
{
|
||||
ExternalLinkButton externalLink;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
InternalChild = new Container
|
||||
@ -120,58 +120,19 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = Picker = new BeatmapPicker(),
|
||||
},
|
||||
new FillFlowContainer
|
||||
title = new MetadataFlowContainer(s =>
|
||||
{
|
||||
s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true);
|
||||
})
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Top = 15 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
title = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true)
|
||||
},
|
||||
externalLink = new ExternalLinkButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 5, Bottom = 4 }, // To better lineup with the font
|
||||
},
|
||||
explicitContent = new ExplicitContentBeatmapBadge
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 10, Bottom = 4 },
|
||||
},
|
||||
spotlight = new SpotlightBeatmapBadge
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 10, Bottom = 4 },
|
||||
}
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
artist = new MetadataFlowContainer(s =>
|
||||
{
|
||||
s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true);
|
||||
})
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Bottom = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
artist = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true),
|
||||
},
|
||||
featuredArtist = new FeaturedArtistBeatmapBadge
|
||||
{
|
||||
Alpha = 0f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 10 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -237,12 +198,17 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
Picker.Beatmap.ValueChanged += b =>
|
||||
{
|
||||
Details.BeatmapInfo = b.NewValue;
|
||||
externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}";
|
||||
updateExternalLink();
|
||||
|
||||
onlineStatusPill.Status = b.NewValue?.Status ?? BeatmapOnlineStatus.None;
|
||||
};
|
||||
}
|
||||
|
||||
private void updateExternalLink()
|
||||
{
|
||||
if (externalLink != null) externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{Picker.Beatmap.Value?.Ruleset.ShortName}/{Picker.Beatmap.Value?.OnlineID}";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
@ -275,12 +241,38 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
|
||||
loading.Hide();
|
||||
|
||||
title.Text = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title);
|
||||
artist.Text = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist);
|
||||
var titleText = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title);
|
||||
var artistText = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist);
|
||||
|
||||
explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0;
|
||||
spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0;
|
||||
featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0;
|
||||
title.Clear();
|
||||
artist.Clear();
|
||||
|
||||
title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText);
|
||||
|
||||
title.AddArbitraryDrawable(Empty().With(d => d.Width = 5));
|
||||
title.AddArbitraryDrawable(externalLink = new ExternalLinkButton());
|
||||
|
||||
if (setInfo.NewValue.HasExplicitContent)
|
||||
{
|
||||
title.AddArbitraryDrawable(Empty().With(d => d.Width = 10));
|
||||
title.AddArbitraryDrawable(new ExplicitContentBeatmapBadge());
|
||||
}
|
||||
|
||||
if (setInfo.NewValue.FeaturedInSpotlight)
|
||||
{
|
||||
title.AddArbitraryDrawable(Empty().With(d => d.Width = 10));
|
||||
title.AddArbitraryDrawable(new SpotlightBeatmapBadge());
|
||||
}
|
||||
|
||||
artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText);
|
||||
|
||||
if (setInfo.NewValue.TrackId != null)
|
||||
{
|
||||
artist.AddArbitraryDrawable(Empty().With(d => d.Width = 10));
|
||||
artist.AddArbitraryDrawable(new FeaturedArtistBeatmapBadge());
|
||||
}
|
||||
|
||||
updateExternalLink();
|
||||
|
||||
onlineStatusPill.FadeIn(500, Easing.OutQuint);
|
||||
|
||||
@ -327,5 +319,32 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class MetadataFlowContainer : LinkFlowContainer
|
||||
{
|
||||
public MetadataFlowContainer(Action<SpriteText> defaultCreationParameters = null)
|
||||
: base(defaultCreationParameters)
|
||||
{
|
||||
TextAnchor = Anchor.CentreLeft;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new MetadataLinkCompiler(textPart);
|
||||
|
||||
public partial class MetadataLinkCompiler : DrawableLinkCompiler
|
||||
{
|
||||
public MetadataLinkCompiler(ITextPart part)
|
||||
: base(part)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
IdleColour = Color4.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +95,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersScore, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
|
||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)),
|
||||
new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag
|
||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)),
|
||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120))
|
||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(minSize: 125)),
|
||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(minSize: 70, maxSize: 120))
|
||||
};
|
||||
|
||||
// All statistics across all scores, unordered.
|
||||
@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
|
||||
var displayName = ruleset.GetDisplayNameForHitResult(result);
|
||||
|
||||
columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60)));
|
||||
columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60)));
|
||||
statisticResultTypes.Add((result, displayName));
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Overlays
|
||||
/// <see cref="APIChangelogBuild.DisplayVersion"/> are specified, the header will instantly display them.</param>
|
||||
public void ShowBuild([NotNull] APIChangelogBuild build)
|
||||
{
|
||||
if (build == null) throw new ArgumentNullException(nameof(build));
|
||||
ArgumentNullException.ThrowIfNull(build);
|
||||
|
||||
Current.Value = build;
|
||||
Show();
|
||||
@ -78,8 +78,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
public void ShowBuild([NotNull] string updateStream, [NotNull] string version)
|
||||
{
|
||||
if (updateStream == null) throw new ArgumentNullException(nameof(updateStream));
|
||||
if (version == null) throw new ArgumentNullException(nameof(version));
|
||||
ArgumentNullException.ThrowIfNull(updateStream);
|
||||
ArgumentNullException.ThrowIfNull(version);
|
||||
|
||||
performAfterFetch(() =>
|
||||
{
|
||||
|
@ -128,9 +128,8 @@ namespace osu.Game.Overlays.Chat
|
||||
chattingTextContainer.FadeTo(showSearch ? 0 : 1);
|
||||
searchIconContainer.FadeTo(showSearch ? 1 : 0);
|
||||
|
||||
// Clear search terms if any exist when switching back to chat mode
|
||||
if (!showSearch)
|
||||
OnSearchTermsChanged?.Invoke(string.Empty);
|
||||
if (showSearch)
|
||||
OnSearchTermsChanged?.Invoke(chatTextBox.Current.Value);
|
||||
}, true);
|
||||
|
||||
currentChannel.BindValueChanged(change =>
|
||||
@ -151,6 +150,12 @@ namespace osu.Game.Overlays.Chat
|
||||
chattingText.Text = ChatStrings.TalkingIn(newChannel.Name);
|
||||
break;
|
||||
}
|
||||
|
||||
if (change.OldValue != null)
|
||||
chatTextBox.Current.UnbindFrom(change.OldValue.TextBoxMessage);
|
||||
|
||||
if (newChannel != null)
|
||||
chatTextBox.Current.BindTo(newChannel.TextBoxMessage);
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,6 @@ namespace osu.Game.Overlays.Chat
|
||||
bool showSearch = change.NewValue;
|
||||
|
||||
PlaceholderText = showSearch ? HomeStrings.SearchPlaceholder : ChatStrings.InputPlaceholder;
|
||||
Text = string.Empty;
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
@ -126,14 +127,18 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
|
||||
if (available)
|
||||
{
|
||||
copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation.";
|
||||
copyInformation.Text =
|
||||
"Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation. ";
|
||||
|
||||
copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links");
|
||||
}
|
||||
else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
|
||||
else if (!RuntimeInfo.IsDesktop)
|
||||
copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import.";
|
||||
else
|
||||
{
|
||||
copyInformation.Text =
|
||||
"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). ";
|
||||
copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows
|
||||
? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). "
|
||||
: "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links). ";
|
||||
copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () =>
|
||||
{
|
||||
game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen()));
|
||||
@ -173,6 +178,18 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
c.Current.Disabled = !allow;
|
||||
}
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
stableLocatorTextBox.HidePopover();
|
||||
base.OnSuspending(e);
|
||||
}
|
||||
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
{
|
||||
stableLocatorTextBox.HidePopover();
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
private partial class ImportCheckbox : SettingsCheckbox
|
||||
{
|
||||
public readonly StableContent StableContent;
|
||||
|
@ -13,7 +13,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@ -58,7 +57,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Size = new Vector2(screen_width, screen_width / 16f * 9 / 2),
|
||||
Size = new Vector2(screen_width, screen_width / 16f * 9),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
@ -68,7 +67,6 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new SampleScreenContainer(new PinnedMainMenu()),
|
||||
new SampleScreenContainer(new NestedSongSelect()),
|
||||
},
|
||||
// TODO: add more screens here in the future (gameplay / results)
|
||||
@ -109,17 +107,6 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
}
|
||||
|
||||
private partial class PinnedMainMenu : MainMenu
|
||||
{
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
|
||||
Buttons.ReturnToTopOnIdle = false;
|
||||
Buttons.State = ButtonSystemState.TopLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class UIScaleSlider : OsuSliderBar<float>
|
||||
{
|
||||
public override LocalisableString TooltipText => base.TooltipText + "x";
|
||||
|
@ -79,26 +79,28 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
Direction = FillDirection.Full;
|
||||
Spacing = new Vector2(5);
|
||||
|
||||
ChildrenEnumerable = Enum.GetValues(typeof(Language))
|
||||
.Cast<Language>()
|
||||
ChildrenEnumerable = Enum.GetValues<Language>()
|
||||
.Select(l => new LanguageButton(l)
|
||||
{
|
||||
Action = () => frameworkLocale.Value = l.ToCultureCode()
|
||||
});
|
||||
|
||||
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
|
||||
frameworkLocale.BindValueChanged(_ => onLanguageChange());
|
||||
|
||||
localisationParameters = localisation.CurrentParameters.GetBoundCopy();
|
||||
localisationParameters.BindValueChanged(p =>
|
||||
{
|
||||
var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue);
|
||||
localisationParameters.BindValueChanged(_ => onLanguageChange(), true);
|
||||
}
|
||||
|
||||
// Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded.
|
||||
// Scheduling ensures the button animation plays smoothly after any blocking operation completes.
|
||||
// Note that a delay is required (the alternative would be a double-schedule; delay feels better).
|
||||
updateSelectedDelegate?.Cancel();
|
||||
updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50);
|
||||
}, true);
|
||||
private void onLanguageChange()
|
||||
{
|
||||
var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value);
|
||||
|
||||
// Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded.
|
||||
// Scheduling ensures the button animation plays smoothly after any blocking operation completes.
|
||||
// Note that a delay is required (the alternative would be a double-schedule; delay feels better).
|
||||
updateSelectedDelegate?.Cancel();
|
||||
updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50);
|
||||
}
|
||||
|
||||
private void updateSelectedStates(Language language)
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Overlays
|
||||
/// <exception cref="InvalidOperationException">If <paramref name="configManager"/> is already being tracked from the same <paramref name="source"/>.</exception>
|
||||
public void BeginTracking(object source, ITrackableConfigManager configManager)
|
||||
{
|
||||
if (configManager == null) throw new ArgumentNullException(nameof(configManager));
|
||||
ArgumentNullException.ThrowIfNull(configManager);
|
||||
|
||||
if (trackedConfigManagers.ContainsKey((source, configManager)))
|
||||
throw new InvalidOperationException($"{nameof(configManager)} is already registered.");
|
||||
@ -82,7 +82,7 @@ namespace osu.Game.Overlays
|
||||
/// <exception cref="InvalidOperationException">If <paramref name="configManager"/> is not being tracked from the same <paramref name="source"/>.</exception>
|
||||
public void StopTracking(object source, ITrackableConfigManager configManager)
|
||||
{
|
||||
if (configManager == null) throw new ArgumentNullException(nameof(configManager));
|
||||
ArgumentNullException.ThrowIfNull(configManager);
|
||||
|
||||
if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing))
|
||||
return;
|
||||
|
@ -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 Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
@ -25,15 +23,15 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
{
|
||||
public partial class BottomHeaderContainer : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<APIUser> User = new Bindable<APIUser>();
|
||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||
|
||||
private LinkFlowContainer topLinkContainer;
|
||||
private LinkFlowContainer bottomLinkContainer;
|
||||
private LinkFlowContainer topLinkContainer = null!;
|
||||
private LinkFlowContainer bottomLinkContainer = null!;
|
||||
|
||||
private Color4 iconColour;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
public BottomHeaderContainer()
|
||||
{
|
||||
@ -78,7 +76,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
User.BindValueChanged(user => updateDisplay(user.NewValue));
|
||||
}
|
||||
|
||||
private void updateDisplay(APIUser user)
|
||||
private void updateDisplay(APIUser? user)
|
||||
{
|
||||
topLinkContainer.Clear();
|
||||
bottomLinkContainer.Clear();
|
||||
@ -164,7 +162,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
|
||||
private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 });
|
||||
|
||||
private bool tryAddInfo(IconUsage icon, string content, string link = null)
|
||||
private bool tryAddInfo(IconUsage icon, string content, string? link = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content)) return false;
|
||||
|
||||
|
@ -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.Extensions.LocalisationExtensions;
|
||||
@ -20,10 +18,10 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
public partial class CentreHeaderContainer : CompositeDrawable
|
||||
{
|
||||
public readonly BindableBool DetailsVisible = new BindableBool(true);
|
||||
public readonly Bindable<APIUser> User = new Bindable<APIUser>();
|
||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||
|
||||
private OverlinedInfoContainer hiddenDetailGlobal;
|
||||
private OverlinedInfoContainer hiddenDetailCountry;
|
||||
private OverlinedInfoContainer hiddenDetailGlobal = null!;
|
||||
private OverlinedInfoContainer hiddenDetailCountry = null!;
|
||||
|
||||
public CentreHeaderContainer()
|
||||
{
|
||||
@ -146,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
User.BindValueChanged(user => updateDisplay(user.NewValue));
|
||||
}
|
||||
|
||||
private void updateDisplay(APIUser user)
|
||||
private void updateDisplay(APIUser? user)
|
||||
{
|
||||
hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
@ -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.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -23,9 +21,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
|
||||
public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand;
|
||||
|
||||
private SpriteIcon icon;
|
||||
private Sample sampleOpen;
|
||||
private Sample sampleClose;
|
||||
private SpriteIcon icon = null!;
|
||||
private Sample? sampleOpen;
|
||||
private Sample? sampleClose;
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds();
|
||||
|
||||
|
@ -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.Sprites;
|
||||
@ -14,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class FollowersButton : ProfileHeaderStatisticsButton
|
||||
{
|
||||
public readonly Bindable<APIUser> User = new Bindable<APIUser>();
|
||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||
|
||||
public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled;
|
||||
|
||||
|
@ -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;
|
||||
@ -20,11 +18,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class LevelBadge : CompositeDrawable, IHasTooltip
|
||||
{
|
||||
public readonly Bindable<APIUser> User = new Bindable<APIUser>();
|
||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||
|
||||
public LocalisableString TooltipText { get; private set; }
|
||||
|
||||
private OsuSpriteText levelText;
|
||||
private OsuSpriteText levelText = null!;
|
||||
|
||||
public LevelBadge()
|
||||
{
|
||||
@ -53,10 +51,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
User.BindValueChanged(user => updateLevel(user.NewValue));
|
||||
}
|
||||
|
||||
private void updateLevel(APIUser user)
|
||||
private void updateLevel(APIUser? user)
|
||||
{
|
||||
levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0";
|
||||
TooltipText = UsersStrings.ShowStatsLevel(user?.Statistics?.Level.Current.ToString());
|
||||
string level = user?.Statistics?.Level.Current.ToString() ?? "0";
|
||||
levelText.Text = level;
|
||||
TooltipText = UsersStrings.ShowStatsLevel(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.Extensions.LocalisationExtensions;
|
||||
@ -21,12 +19,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class LevelProgressBar : CompositeDrawable, IHasTooltip
|
||||
{
|
||||
public readonly Bindable<APIUser> User = new Bindable<APIUser>();
|
||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||
|
||||
public LocalisableString TooltipText { get; }
|
||||
|
||||
private Bar levelProgressBar;
|
||||
private OsuSpriteText levelProgressText;
|
||||
private Bar levelProgressBar = null!;
|
||||
private OsuSpriteText levelProgressText = null!;
|
||||
|
||||
public LevelProgressBar()
|
||||
{
|
||||
@ -61,7 +59,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
User.BindValueChanged(user => updateProgress(user.NewValue));
|
||||
}
|
||||
|
||||
private void updateProgress(APIUser user)
|
||||
private void updateProgress(APIUser? user)
|
||||
{
|
||||
levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0;
|
||||
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default;
|
||||
|
@ -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.Sprites;
|
||||
@ -14,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class MappingSubscribersButton : ProfileHeaderStatisticsButton
|
||||
{
|
||||
public readonly Bindable<APIUser> User = new Bindable<APIUser>();
|
||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||
|
||||
public override LocalisableString TooltipText => FollowsStrings.MappingFollowers;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user