mirror of
https://github.com/osukey/osukey.git
synced 2025-06-05 12:57:39 +09:00
Merge branch 'master' into remove_obsolete_star_colours
This commit is contained in:
commit
e416bdd06b
@ -1,6 +1,6 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
image: Visual Studio 2019
|
image: Visual Studio 2022
|
||||||
test: off
|
test: off
|
||||||
skip_non_tags: true
|
skip_non_tags: true
|
||||||
configuration: Release
|
configuration: Release
|
||||||
@ -83,4 +83,4 @@ artifacts:
|
|||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: Environment
|
- provider: Environment
|
||||||
name: nuget
|
name: nuget
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1219.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1226.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
||||||
: base(hitObject, beatmap, previousPattern)
|
: base(hitObject, beatmap, previousPattern)
|
||||||
{
|
{
|
||||||
if (random == null) throw new ArgumentNullException(nameof(random));
|
ArgumentNullException.ThrowIfNull(random);
|
||||||
if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap));
|
ArgumentNullException.ThrowIfNull(originalBeatmap);
|
||||||
|
|
||||||
Random = random;
|
Random = random;
|
||||||
OriginalBeatmap = originalBeatmap;
|
OriginalBeatmap = originalBeatmap;
|
||||||
|
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
|||||||
|
|
||||||
protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
|
protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
|
||||||
{
|
{
|
||||||
if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
|
ArgumentNullException.ThrowIfNull(hitObject);
|
||||||
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
|
ArgumentNullException.ThrowIfNull(beatmap);
|
||||||
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
|
ArgumentNullException.ThrowIfNull(previousPattern);
|
||||||
|
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
Beatmap = beatmap;
|
Beatmap = beatmap;
|
||||||
|
@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils
|
|||||||
|
|
||||||
public static void Sort(T[] keys, IComparer<T> comparer)
|
public static void Sort(T[] keys, IComparer<T> comparer)
|
||||||
{
|
{
|
||||||
if (keys == null)
|
ArgumentNullException.ThrowIfNull(keys);
|
||||||
throw new ArgumentNullException(nameof(keys));
|
|
||||||
|
|
||||||
if (keys.Length == 0)
|
if (keys.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -18,20 +17,18 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
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;
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; } = null!;
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
public ArgonJudgementPiece(HitResult result)
|
public ArgonJudgementPiece(HitResult result)
|
||||||
|
: base(result)
|
||||||
{
|
{
|
||||||
Result = result;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Y = 160;
|
Y = 160;
|
||||||
}
|
}
|
||||||
@ -39,22 +36,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
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())
|
if (Result.IsHit())
|
||||||
{
|
{
|
||||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
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>
|
/// <summary>
|
||||||
/// Plays the default animation for this judgement piece.
|
/// Plays the default animation for this judgement piece.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +27,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
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);
|
return new ArgonJudgementPiece(resultComponent.Component);
|
||||||
|
|
||||||
case ManiaSkinComponentLookup maniaComponent:
|
case ManiaSkinComponentLookup maniaComponent:
|
||||||
|
@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public ManiaPlayfield(List<StageDefinition> stageDefinitions)
|
public ManiaPlayfield(List<StageDefinition> stageDefinitions)
|
||||||
{
|
{
|
||||||
if (stageDefinitions == null)
|
ArgumentNullException.ThrowIfNull(stageDefinitions);
|
||||||
throw new ArgumentNullException(nameof(stageDefinitions));
|
|
||||||
|
|
||||||
if (stageDefinitions.Count <= 0)
|
if (stageDefinitions.Count <= 0)
|
||||||
throw new ArgumentException("Can't have zero or fewer stages.");
|
throw new ArgumentException("Can't have zero or fewer stages.");
|
||||||
|
@ -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);
|
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)
|
private Drawable testDistanceOverflow(int repeats = 0)
|
||||||
{
|
{
|
||||||
|
@ -84,8 +84,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
{
|
{
|
||||||
public int Compare(ReplayFrame? f1, ReplayFrame? f2)
|
public int Compare(ReplayFrame? f1, ReplayFrame? f2)
|
||||||
{
|
{
|
||||||
if (f1 == null) throw new ArgumentNullException(nameof(f1));
|
ArgumentNullException.ThrowIfNull(f1);
|
||||||
if (f2 == null) throw new ArgumentNullException(nameof(f2));
|
ArgumentNullException.ThrowIfNull(f2);
|
||||||
|
|
||||||
return f1.Time.CompareTo(f2.Time);
|
return f1.Time.CompareTo(f2.Time);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -17,42 +16,24 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
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;
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; } = null!;
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
public ArgonJudgementPiece(HitResult result)
|
public ArgonJudgementPiece(HitResult result)
|
||||||
|
: base(result)
|
||||||
{
|
{
|
||||||
Result = result;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
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())
|
if (Result.IsHit())
|
||||||
{
|
{
|
||||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
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>
|
/// <summary>
|
||||||
/// Plays the default animation for this judgement piece.
|
/// Plays the default animation for this judgement piece.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
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);
|
return new ArgonJudgementPiece(resultComponent.Component);
|
||||||
|
|
||||||
case OsuSkinComponentLookup osuComponent:
|
case OsuSkinComponentLookup osuComponent:
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -18,20 +17,16 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
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;
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; } = null!;
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
public ArgonJudgementPiece(HitResult result)
|
public ArgonJudgementPiece(HitResult result)
|
||||||
|
: base(result)
|
||||||
{
|
{
|
||||||
Result = result;
|
|
||||||
RelativePositionAxes = Axes.Both;
|
RelativePositionAxes = Axes.Both;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
@ -39,21 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
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())
|
if (Result.IsHit())
|
||||||
{
|
{
|
||||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
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>
|
/// <summary>
|
||||||
/// Plays the default animation for this judgement piece.
|
/// Plays the default animation for this judgement piece.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
switch (component)
|
switch (component)
|
||||||
{
|
{
|
||||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
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);
|
return new ArgonJudgementPiece(resultComponent.Component);
|
||||||
|
|
||||||
case TaikoSkinComponentLookup taikoComponent:
|
case TaikoSkinComponentLookup taikoComponent:
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
|
public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private ZoomableScrollContainer scrollContainer;
|
private TestZoomableScrollContainer scrollContainer;
|
||||||
private Drawable innerBox;
|
private Drawable innerBox;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(30)
|
Colour = OsuColour.Gray(30)
|
||||||
},
|
},
|
||||||
scrollContainer = new ZoomableScrollContainer(1, 60, 1)
|
scrollContainer = new TestZoomableScrollContainer(1, 60, 1)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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);
|
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]
|
[Test]
|
||||||
public void TestZoom0()
|
public void TestZoom0()
|
||||||
{
|
{
|
||||||
@ -190,5 +198,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
|
private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
|
||||||
private Quad boxQuad => innerBox.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);
|
prepareTestAPI(true);
|
||||||
|
|
||||||
createPlayerTest(false, createRuleset: () => new OsuRuleset
|
createPlayerTest(createRuleset: () => new OsuRuleset
|
||||||
{
|
{
|
||||||
RulesetInfo =
|
RulesetInfo =
|
||||||
{
|
{
|
||||||
|
280
osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
Normal file
280
osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
// 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 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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", () => 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 });
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,11 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -29,6 +32,15 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("Show main page", () => wiki.Show());
|
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]
|
[Test]
|
||||||
public void TestArticlePage()
|
public void TestArticlePage()
|
||||||
{
|
{
|
||||||
@ -56,7 +68,9 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
public void TestErrorPage()
|
public void TestErrorPage()
|
||||||
{
|
{
|
||||||
setUpWikiResponse(responseArticlePage);
|
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)
|
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 = 0.9899,
|
||||||
|
MaxCombo = 2_322,
|
||||||
|
RankedScore = 23_123_543_456,
|
||||||
|
TotalScore = 123_123_543_456,
|
||||||
|
PP = 5_072
|
||||||
|
},
|
||||||
|
new UserStatistics
|
||||||
|
{
|
||||||
|
GlobalRank = 1_234,
|
||||||
|
Accuracy = 0.9907,
|
||||||
|
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 = 0.9907,
|
||||||
|
MaxCombo = 2_352,
|
||||||
|
RankedScore = 23_124_231_435,
|
||||||
|
TotalScore = 123_124_231_435,
|
||||||
|
PP = 5_434
|
||||||
|
},
|
||||||
|
new UserStatistics
|
||||||
|
{
|
||||||
|
GlobalRank = 12_345,
|
||||||
|
Accuracy = 0.9899,
|
||||||
|
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 = 0.9899,
|
||||||
|
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 = 0.9899,
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
|
|
||||||
public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null)
|
public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null)
|
||||||
{
|
{
|
||||||
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
|
ArgumentNullException.ThrowIfNull(beatmap);
|
||||||
|
|
||||||
Beatmap = beatmap;
|
Beatmap = beatmap;
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
|
@ -211,8 +211,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public static T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
public static T BinarySearch<T>(IReadOnlyList<T> list, double time)
|
||||||
where T : class, IControlPoint
|
where T : class, IControlPoint
|
||||||
{
|
{
|
||||||
if (list == null)
|
ArgumentNullException.ThrowIfNull(list);
|
||||||
throw new ArgumentNullException(nameof(list));
|
|
||||||
|
|
||||||
if (list.Count == 0)
|
if (list.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
@ -15,8 +15,7 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
public BeatmapBackgroundSprite(IWorkingBeatmap working)
|
public BeatmapBackgroundSprite(IWorkingBeatmap working)
|
||||||
{
|
{
|
||||||
if (working == null)
|
ArgumentNullException.ThrowIfNull(working);
|
||||||
throw new ArgumentNullException(nameof(working));
|
|
||||||
|
|
||||||
this.working = working;
|
this.working = working;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
public OnlineBeatmapSetCover(IBeatmapSetOnlineInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover)
|
public OnlineBeatmapSetCover(IBeatmapSetOnlineInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover)
|
||||||
{
|
{
|
||||||
if (set == null)
|
ArgumentNullException.ThrowIfNull(set);
|
||||||
throw new ArgumentNullException(nameof(set));
|
|
||||||
|
|
||||||
this.set = set;
|
this.set = set;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
@ -57,8 +57,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
public static Decoder<T> GetDecoder<T>(LineBufferedReader stream)
|
public static Decoder<T> GetDecoder<T>(LineBufferedReader stream)
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
if (stream == null)
|
ArgumentNullException.ThrowIfNull(stream);
|
||||||
throw new ArgumentNullException(nameof(stream));
|
|
||||||
|
|
||||||
if (!decoders.TryGetValue(typeof(T), out var typedDecoders))
|
if (!decoders.TryGetValue(typeof(T), out var typedDecoders))
|
||||||
throw new IOException(@"Unknown decoder type");
|
throw new IOException(@"Unknown decoder type");
|
||||||
|
@ -36,8 +36,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// <param name="easing">The easing type of the initial transform.</param>
|
/// <param name="easing">The easing type of the initial transform.</param>
|
||||||
public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None)
|
public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None)
|
||||||
{
|
{
|
||||||
if (logo == null)
|
ArgumentNullException.ThrowIfNull(logo);
|
||||||
throw new ArgumentNullException(nameof(logo));
|
|
||||||
|
|
||||||
if (logo.IsTracking && Logo == null)
|
if (logo.IsTracking && Logo == null)
|
||||||
throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s");
|
throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s");
|
||||||
|
@ -52,8 +52,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
public readonly SpriteIcon Chevron;
|
public readonly SpriteIcon Chevron;
|
||||||
|
|
||||||
//don't allow clicking between transitions and don't make the chevron clickable
|
//don't allow clicking between transitions
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && Text.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Alpha == 1f && base.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public override bool HandleNonPositionalInput => State == Visibility.Visible;
|
public override bool HandleNonPositionalInput => State == Visibility.Visible;
|
||||||
public override bool HandlePositionalInput => 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.Font = Text.Font.With(size: 18);
|
||||||
Text.Margin = new MarginPadding { Vertical = 8 };
|
Text.Margin = new MarginPadding { Vertical = 8 };
|
||||||
Padding = new MarginPadding { Right = padding + ChevronSize };
|
Margin = new MarginPadding { Right = padding + ChevronSize };
|
||||||
Add(Chevron = new SpriteIcon
|
Add(Chevron = new SpriteIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
|
@ -114,8 +114,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
get => current;
|
get => current;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
current.UnbindBindings();
|
current.UnbindBindings();
|
||||||
current.BindTo(value);
|
current.BindTo(value);
|
||||||
|
@ -23,8 +23,7 @@ namespace osu.Game.IO.FileAbstraction
|
|||||||
|
|
||||||
public void CloseStream(Stream stream)
|
public void CloseStream(Stream stream)
|
||||||
{
|
{
|
||||||
if (stream == null)
|
ArgumentNullException.ThrowIfNull(stream);
|
||||||
throw new ArgumentNullException(nameof(stream));
|
|
||||||
|
|
||||||
stream.Close();
|
stream.Close();
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard");
|
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>
|
/// <summary>
|
||||||
/// "You are running the latest release ({0})"
|
/// "You are running the latest release ({0})"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
public class GetUsersRequest : APIRequest<GetUsersResponse>
|
public class GetUsersRequest : APIRequest<GetUsersResponse>
|
||||||
{
|
{
|
||||||
private readonly int[] userIds;
|
public readonly int[] UserIds;
|
||||||
|
|
||||||
private const int max_ids_per_request = 50;
|
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)
|
if (userIds.Length > max_ids_per_request)
|
||||||
throw new ArgumentException($"{nameof(GetUsersRequest)} calls only support up to {max_ids_per_request} IDs at once");
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <param name="name"></param>
|
/// <param name="name"></param>
|
||||||
public void OpenChannel(string name)
|
public void OpenChannel(string name)
|
||||||
{
|
{
|
||||||
if (name == null)
|
ArgumentNullException.ThrowIfNull(name);
|
||||||
throw new ArgumentNullException(nameof(name));
|
|
||||||
|
|
||||||
CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(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>
|
/// <param name="user">The user the private channel is opened with.</param>
|
||||||
public void OpenPrivateChannel(APIUser user)
|
public void OpenPrivateChannel(APIUser user)
|
||||||
{
|
{
|
||||||
if (user == null)
|
ArgumentNullException.ThrowIfNull(user);
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
|
|
||||||
if (user.Id == api.LocalUser.Value.Id)
|
if (user.Id == api.LocalUser.Value.Id)
|
||||||
return;
|
return;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
144
osu.Game/Online/Solo/SoloStatisticsWatcher.cs
Normal file
144
osu.Game/Online/Solo/SoloStatisticsWatcher.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// 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>
|
||||||
|
public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action<SoloStatisticsUpdate> onUpdateReady) => Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!api.IsLoggedIn)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!score.Ruleset.IsLegacyRuleset())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var callback = new StatisticsUpdateCallback(score, onUpdateReady);
|
||||||
|
|
||||||
|
if (lastProcessedScoreId == score.OnlineID)
|
||||||
|
{
|
||||||
|
requestStatisticsUpdate(api.LocalUser.Value.Id, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.Add(score.OnlineID, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 += response => Schedule(() =>
|
||||||
|
{
|
||||||
|
latestStatistics = new Dictionary<string, UserStatistics>();
|
||||||
|
foreach (var rulesetStats in response.Users.Single().RulesetsStatistics)
|
||||||
|
latestStatistics.Add(rulesetStats.Key, rulesetStats.Value);
|
||||||
|
});
|
||||||
|
api.Queue(userRequest);
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -353,7 +353,10 @@ namespace osu.Game
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case LinkAction.SearchBeatmapSet:
|
case LinkAction.SearchBeatmapSet:
|
||||||
SearchBeatmapSet(argString);
|
if (link.Argument is RomanisableString romanisable)
|
||||||
|
SearchBeatmapSet(romanisable.GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript));
|
||||||
|
else
|
||||||
|
SearchBeatmapSet(argString);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LinkAction.OpenEditorTimestamp:
|
case LinkAction.OpenEditorTimestamp:
|
||||||
@ -1040,9 +1043,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
Logger.NewEntry += entry =>
|
Logger.NewEntry += entry =>
|
||||||
{
|
{
|
||||||
if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return;
|
if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return;
|
||||||
|
|
||||||
Debug.Assert(entry.Target != null);
|
|
||||||
|
|
||||||
const int short_term_display_limit = 3;
|
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.Chat;
|
||||||
using osu.Game.Online.Metadata;
|
using osu.Game.Online.Metadata;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
@ -193,6 +194,7 @@ namespace osu.Game
|
|||||||
protected MultiplayerClient MultiplayerClient { get; private set; }
|
protected MultiplayerClient MultiplayerClient { get; private set; }
|
||||||
|
|
||||||
private MetadataClient metadataClient;
|
private MetadataClient metadataClient;
|
||||||
|
private SoloStatisticsWatcher soloStatisticsWatcher;
|
||||||
|
|
||||||
private RealmAccess realm;
|
private RealmAccess realm;
|
||||||
|
|
||||||
@ -301,6 +303,7 @@ namespace osu.Game
|
|||||||
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
||||||
dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints));
|
dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints));
|
||||||
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
||||||
|
dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher());
|
||||||
|
|
||||||
AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
|
AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
|
||||||
|
|
||||||
@ -346,6 +349,7 @@ namespace osu.Game
|
|||||||
AddInternal(spectatorClient);
|
AddInternal(spectatorClient);
|
||||||
AddInternal(MultiplayerClient);
|
AddInternal(MultiplayerClient);
|
||||||
AddInternal(metadataClient);
|
AddInternal(metadataClient);
|
||||||
|
AddInternal(soloStatisticsWatcher);
|
||||||
|
|
||||||
AddInternal(rulesetConfigCache);
|
AddInternal(rulesetConfigCache);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -12,17 +13,20 @@ using osu.Framework.Graphics.Colour;
|
|||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays.BeatmapSet.Buttons;
|
using osu.Game.Overlays.BeatmapSet.Buttons;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet
|
namespace osu.Game.Overlays.BeatmapSet
|
||||||
{
|
{
|
||||||
@ -41,12 +45,10 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
|
|
||||||
private readonly UpdateableOnlineBeatmapSetCover cover;
|
private readonly UpdateableOnlineBeatmapSetCover cover;
|
||||||
private readonly Box coverGradient;
|
private readonly Box coverGradient;
|
||||||
private readonly OsuSpriteText title, artist;
|
private readonly LinkFlowContainer title, artist;
|
||||||
private readonly AuthorInfo author;
|
private readonly AuthorInfo author;
|
||||||
|
|
||||||
private readonly ExplicitContentBeatmapBadge explicitContent;
|
private ExternalLinkButton externalLink;
|
||||||
private readonly SpotlightBeatmapBadge spotlight;
|
|
||||||
private readonly FeaturedArtistBeatmapBadge featuredArtist;
|
|
||||||
|
|
||||||
private readonly FillFlowContainer downloadButtonsContainer;
|
private readonly FillFlowContainer downloadButtonsContainer;
|
||||||
private readonly BeatmapAvailability beatmapAvailability;
|
private readonly BeatmapAvailability beatmapAvailability;
|
||||||
@ -65,8 +67,6 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
|
|
||||||
public BeatmapSetHeaderContent()
|
public BeatmapSetHeaderContent()
|
||||||
{
|
{
|
||||||
ExternalLinkButton externalLink;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
InternalChild = new Container
|
InternalChild = new Container
|
||||||
@ -120,58 +120,19 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Child = Picker = new BeatmapPicker(),
|
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 },
|
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 },
|
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
|
new Container
|
||||||
{
|
{
|
||||||
@ -237,12 +198,17 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
Picker.Beatmap.ValueChanged += b =>
|
Picker.Beatmap.ValueChanged += b =>
|
||||||
{
|
{
|
||||||
Details.BeatmapInfo = b.NewValue;
|
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;
|
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]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
@ -275,12 +241,38 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
|
|
||||||
loading.Hide();
|
loading.Hide();
|
||||||
|
|
||||||
title.Text = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title);
|
var titleText = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title);
|
||||||
artist.Text = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist);
|
var artistText = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist);
|
||||||
|
|
||||||
explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0;
|
title.Clear();
|
||||||
spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0;
|
artist.Clear();
|
||||||
featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0;
|
|
||||||
|
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);
|
onlineStatusPill.FadeIn(500, Easing.OutQuint);
|
||||||
|
|
||||||
@ -327,5 +319,32 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
break;
|
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.ShowScoreboardHeadersScore, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
|
||||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)),
|
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("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag
|
||||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)),
|
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(minSize: 125)),
|
||||||
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120))
|
new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(minSize: 70, maxSize: 120))
|
||||||
};
|
};
|
||||||
|
|
||||||
// All statistics across all scores, unordered.
|
// All statistics across all scores, unordered.
|
||||||
@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
|
|
||||||
var displayName = ruleset.GetDisplayNameForHitResult(result);
|
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));
|
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>
|
/// <see cref="APIChangelogBuild.DisplayVersion"/> are specified, the header will instantly display them.</param>
|
||||||
public void ShowBuild([NotNull] APIChangelogBuild build)
|
public void ShowBuild([NotNull] APIChangelogBuild build)
|
||||||
{
|
{
|
||||||
if (build == null) throw new ArgumentNullException(nameof(build));
|
ArgumentNullException.ThrowIfNull(build);
|
||||||
|
|
||||||
Current.Value = build;
|
Current.Value = build;
|
||||||
Show();
|
Show();
|
||||||
@ -78,8 +78,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public void ShowBuild([NotNull] string updateStream, [NotNull] string version)
|
public void ShowBuild([NotNull] string updateStream, [NotNull] string version)
|
||||||
{
|
{
|
||||||
if (updateStream == null) throw new ArgumentNullException(nameof(updateStream));
|
ArgumentNullException.ThrowIfNull(updateStream);
|
||||||
if (version == null) throw new ArgumentNullException(nameof(version));
|
ArgumentNullException.ThrowIfNull(version);
|
||||||
|
|
||||||
performAfterFetch(() =>
|
performAfterFetch(() =>
|
||||||
{
|
{
|
||||||
|
@ -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>
|
/// <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)
|
public void BeginTracking(object source, ITrackableConfigManager configManager)
|
||||||
{
|
{
|
||||||
if (configManager == null) throw new ArgumentNullException(nameof(configManager));
|
ArgumentNullException.ThrowIfNull(configManager);
|
||||||
|
|
||||||
if (trackedConfigManagers.ContainsKey((source, configManager)))
|
if (trackedConfigManagers.ContainsKey((source, configManager)))
|
||||||
throw new InvalidOperationException($"{nameof(configManager)} is already registered.");
|
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>
|
/// <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)
|
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))
|
if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing))
|
||||||
return;
|
return;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.Settings.Sections.General;
|
using osu.Game.Overlays.Settings.Sections.General;
|
||||||
|
|
||||||
@ -15,7 +14,10 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
public partial class GeneralSection : SettingsSection
|
public partial class GeneralSection : SettingsSection
|
||||||
{
|
{
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private FirstRunSetupOverlay firstRunSetupOverlay { get; set; }
|
private FirstRunSetupOverlay? firstRunSetupOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private OsuGame? game { get; set; }
|
||||||
|
|
||||||
public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader;
|
public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader;
|
||||||
|
|
||||||
@ -24,15 +26,24 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
Icon = FontAwesome.Solid.Cog
|
Icon = FontAwesome.Solid.Cog
|
||||||
};
|
};
|
||||||
|
|
||||||
public GeneralSection()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SettingsButton
|
new SettingsButton
|
||||||
{
|
{
|
||||||
Text = GeneralSettingsStrings.RunSetupWizard,
|
Text = GeneralSettingsStrings.RunSetupWizard,
|
||||||
|
TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
|
||||||
Action = () => firstRunSetupOverlay?.Show(),
|
Action = () => firstRunSetupOverlay?.Show(),
|
||||||
},
|
},
|
||||||
|
new SettingsButton
|
||||||
|
{
|
||||||
|
Text = GeneralSettingsStrings.LearnMoreAboutLazer,
|
||||||
|
TooltipText = GeneralSettingsStrings.LearnMoreAboutLazerTooltip,
|
||||||
|
BackgroundColour = colours.YellowDark,
|
||||||
|
Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer")
|
||||||
|
},
|
||||||
new LanguageSettings(),
|
new LanguageSettings(),
|
||||||
new UpdateSettings(),
|
new UpdateSettings(),
|
||||||
};
|
};
|
||||||
|
@ -28,8 +28,7 @@ namespace osu.Game.Overlays.Volume
|
|||||||
get => current;
|
get => current;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
current.UnbindBindings();
|
current.UnbindBindings();
|
||||||
current.BindTo(value);
|
current.BindTo(value);
|
||||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
private const string index_path = @"main_page";
|
private const string index_path = @"main_page";
|
||||||
|
|
||||||
|
public string CurrentPath => path.Value;
|
||||||
|
|
||||||
private readonly Bindable<string> path = new Bindable<string>(index_path);
|
private readonly Bindable<string> path = new Bindable<string>(index_path);
|
||||||
|
|
||||||
private readonly Bindable<APIWikiPage> wikiData = new Bindable<APIWikiPage>();
|
private readonly Bindable<APIWikiPage> wikiData = new Bindable<APIWikiPage>();
|
||||||
@ -105,6 +107,9 @@ namespace osu.Game.Overlays
|
|||||||
if (e.NewValue == wikiData.Value?.Path)
|
if (e.NewValue == wikiData.Value?.Path)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (e.NewValue == "error")
|
||||||
|
return;
|
||||||
|
|
||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
request?.Cancel();
|
request?.Cancel();
|
||||||
|
|
||||||
@ -118,7 +123,11 @@ namespace osu.Game.Overlays
|
|||||||
Loading.Show();
|
Loading.Show();
|
||||||
|
|
||||||
request.Success += response => Schedule(() => onSuccess(response));
|
request.Success += response => Schedule(() => onSuccess(response));
|
||||||
request.Failure += _ => Schedule(onFail);
|
request.Failure += ex =>
|
||||||
|
{
|
||||||
|
if (ex is not OperationCanceledException)
|
||||||
|
Schedule(onFail, request.Path);
|
||||||
|
};
|
||||||
|
|
||||||
api.PerformAsync(request);
|
api.PerformAsync(request);
|
||||||
}
|
}
|
||||||
@ -146,10 +155,11 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFail()
|
private void onFail(string originalPath)
|
||||||
{
|
{
|
||||||
|
path.Value = "error";
|
||||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/",
|
LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/",
|
||||||
$"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page)."));
|
$"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page)."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showParentPage()
|
private void showParentPage()
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -15,40 +10,25 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Judgements
|
namespace osu.Game.Rulesets.Judgements
|
||||||
{
|
{
|
||||||
public partial class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement
|
||||||
{
|
{
|
||||||
protected readonly HitResult Result;
|
|
||||||
|
|
||||||
protected SpriteText JudgementText { get; private set; }
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; }
|
|
||||||
|
|
||||||
public DefaultJudgementPiece(HitResult result)
|
public DefaultJudgementPiece(HitResult result)
|
||||||
{
|
: base(result)
|
||||||
Result = result;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
Origin = Anchor.Centre;
|
||||||
{
|
|
||||||
JudgementText = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Text = Result.GetDescription().ToUpperInvariant(),
|
|
||||||
Colour = colours.ForHitResult(Result),
|
|
||||||
Font = OsuFont.Numeric.With(size: 20),
|
|
||||||
Scale = new Vector2(0.85f, 1),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateJudgementText() =>
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Numeric.With(size: 20),
|
||||||
|
Scale = new Vector2(0.85f, 1),
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plays the default animation for this judgement piece.
|
/// Plays the default animation for this judgement piece.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -75,6 +55,6 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
this.FadeOutFromOne(800);
|
this.FadeOutFromOne(800);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable GetAboveHitObjectsProxiedContent() => null;
|
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
osu.Game/Rulesets/Judgements/JudgementPiece.cs
Normal file
38
osu.Game/Rulesets/Judgements/JudgementPiece.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Judgements
|
||||||
|
{
|
||||||
|
public abstract partial class JudgementPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
protected readonly HitResult Result;
|
||||||
|
|
||||||
|
protected SpriteText JudgementText { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
protected JudgementPiece(HitResult result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(JudgementText = CreateJudgementText());
|
||||||
|
|
||||||
|
JudgementText.Colour = colours.ForHitResult(Result);
|
||||||
|
JudgementText.Text = Result.GetDescription().ToUpperInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract SpriteText CreateJudgementText();
|
||||||
|
}
|
||||||
|
}
|
@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
get => this;
|
get => this;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
if (currentBound != null) UnbindFrom(currentBound);
|
if (currentBound != null) UnbindFrom(currentBound);
|
||||||
BindTo(currentBound = value);
|
BindTo(currentBound = value);
|
||||||
|
@ -208,8 +208,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Apply([NotNull] HitObject hitObject)
|
public void Apply([NotNull] HitObject hitObject)
|
||||||
{
|
{
|
||||||
if (hitObject == null)
|
ArgumentNullException.ThrowIfNull(hitObject);
|
||||||
throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}.");
|
|
||||||
|
|
||||||
Apply(new SyntheticHitObjectEntry(hitObject));
|
Apply(new SyntheticHitObjectEntry(hitObject));
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
public override void Add(T judgement)
|
public override void Add(T judgement)
|
||||||
{
|
{
|
||||||
if (judgement == null) throw new ArgumentNullException(nameof(judgement));
|
ArgumentNullException.ThrowIfNull(judgement);
|
||||||
|
|
||||||
// remove any existing judgements for the judged object.
|
// remove any existing judgements for the judged object.
|
||||||
// this can be the case when rewinding.
|
// this can be the case when rewinding.
|
||||||
|
@ -71,8 +71,8 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
// These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed).
|
// These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed).
|
||||||
// Under no circumstance do we want these to be written to realm as null.
|
// Under no circumstance do we want these to be written to realm as null.
|
||||||
if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo));
|
ArgumentNullException.ThrowIfNull(model.BeatmapInfo);
|
||||||
if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset));
|
ArgumentNullException.ThrowIfNull(model.Ruleset);
|
||||||
|
|
||||||
PopulateMaximumStatistics(model);
|
PopulateMaximumStatistics(model);
|
||||||
|
|
||||||
|
@ -99,9 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
minZoom = minimum;
|
minZoom = minimum;
|
||||||
maxZoom = maximum;
|
maxZoom = maximum;
|
||||||
CurrentZoom = zoomTarget = initial;
|
|
||||||
isZoomSetUp = true;
|
|
||||||
|
|
||||||
|
CurrentZoom = zoomTarget = initial;
|
||||||
|
zoomedContentWidthCache.Invalidate();
|
||||||
|
|
||||||
|
isZoomSetUp = true;
|
||||||
zoomedContent.Show();
|
zoomedContent.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private void addAmplitudesFromSource(IHasAmplitudes source)
|
private void addAmplitudesFromSource(IHasAmplitudes source)
|
||||||
{
|
{
|
||||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
|
||||||
var amplitudes = source.CurrentAmplitudes.FrequencyAmplitudes.Span;
|
var amplitudes = source.CurrentAmplitudes.FrequencyAmplitudes.Span;
|
||||||
|
|
||||||
|
@ -33,8 +33,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
get => current.Current;
|
get => current.Current;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
current.Current = value;
|
current.Current = value;
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
get => current.Current;
|
get => current.Current;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
current.Current = value;
|
current.Current = value;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public override void Add(KeyCounter key)
|
public override void Add(KeyCounter key)
|
||||||
{
|
{
|
||||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
base.Add(key);
|
base.Add(key);
|
||||||
key.IsCounting = IsCounting;
|
key.IsCounting = IsCounting;
|
||||||
|
@ -96,11 +96,11 @@ namespace osu.Game.Screens.Ranking
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
statisticsPanel = new StatisticsPanel
|
statisticsPanel = CreateStatisticsPanel().With(panel =>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
panel.RelativeSizeAxes = Axes.Both;
|
||||||
Score = { BindTarget = SelectedScore }
|
panel.Score.BindTarget = SelectedScore;
|
||||||
},
|
}),
|
||||||
ScorePanelList = new ScorePanelList
|
ScorePanelList = new ScorePanelList
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -231,6 +231,11 @@ namespace osu.Game.Screens.Ranking
|
|||||||
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
|
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
|
||||||
protected virtual APIRequest FetchNextPage(int direction, Action<IEnumerable<ScoreInfo>> scoresCallback) => null;
|
protected virtual APIRequest FetchNextPage(int direction, Action<IEnumerable<ScoreInfo>> scoresCallback) => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the <see cref="StatisticsPanel"/> to be used to display extended information about scores.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual StatisticsPanel CreateStatisticsPanel() => new StatisticsPanel();
|
||||||
|
|
||||||
private void fetchScoresCallback(IEnumerable<ScoreInfo> scores) => Schedule(() =>
|
private void fetchScoresCallback(IEnumerable<ScoreInfo> scores) => Schedule(() =>
|
||||||
{
|
{
|
||||||
foreach (var s in scores)
|
foreach (var s in scores)
|
||||||
|
@ -7,11 +7,14 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking
|
namespace osu.Game.Screens.Ranking
|
||||||
{
|
{
|
||||||
@ -22,11 +25,28 @@ namespace osu.Game.Screens.Ranking
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesets { get; set; }
|
private RulesetStore rulesets { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SoloStatisticsWatcher soloStatisticsWatcher { get; set; }
|
||||||
|
|
||||||
|
private readonly Bindable<SoloStatisticsUpdate> statisticsUpdate = new Bindable<SoloStatisticsUpdate>();
|
||||||
|
|
||||||
public SoloResultsScreen(ScoreInfo score, bool allowRetry)
|
public SoloResultsScreen(ScoreInfo score, bool allowRetry)
|
||||||
: base(score, allowRetry)
|
: base(score, allowRetry)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override StatisticsPanel CreateStatisticsPanel() => new SoloStatisticsPanel(Score)
|
||||||
|
{
|
||||||
|
StatisticsUpdate = { BindTarget = statisticsUpdate }
|
||||||
|
};
|
||||||
|
|
||||||
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
|
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
|
||||||
{
|
{
|
||||||
if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
|
if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
|
||||||
|
54
osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs
Normal file
54
osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking.Statistics.User;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
|
{
|
||||||
|
public partial class SoloStatisticsPanel : StatisticsPanel
|
||||||
|
{
|
||||||
|
private readonly ScoreInfo achievedScore;
|
||||||
|
|
||||||
|
public SoloStatisticsPanel(ScoreInfo achievedScore)
|
||||||
|
{
|
||||||
|
this.achievedScore = achievedScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<SoloStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<SoloStatisticsUpdate?>();
|
||||||
|
|
||||||
|
protected override ICollection<StatisticRow> CreateStatisticRows(ScoreInfo newScore, IBeatmap playableBeatmap)
|
||||||
|
{
|
||||||
|
var rows = base.CreateStatisticRows(newScore, playableBeatmap);
|
||||||
|
|
||||||
|
if (newScore.UserID > 1
|
||||||
|
&& newScore.UserID == achievedScore.UserID
|
||||||
|
&& newScore.OnlineID > 0
|
||||||
|
&& newScore.OnlineID == achievedScore.OnlineID)
|
||||||
|
{
|
||||||
|
rows = rows.Append(new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem("Overall Ranking", () => new OverallRanking
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 0.5f,
|
||||||
|
StatisticsUpdate = { BindTarget = StatisticsUpdate }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -100,7 +100,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
bool hitEventsAvailable = newScore.HitEvents.Count != 0;
|
bool hitEventsAvailable = newScore.HitEvents.Count != 0;
|
||||||
Container<Drawable> container;
|
Container<Drawable> container;
|
||||||
|
|
||||||
var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely());
|
var statisticRows = CreateStatisticRows(newScore, task.GetResultSafely());
|
||||||
|
|
||||||
if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents))
|
if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents))
|
||||||
{
|
{
|
||||||
@ -218,6 +218,14 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
}), localCancellationSource.Token);
|
}), localCancellationSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the <see cref="StatisticRow"/>s to be displayed in this panel for a given <paramref name="newScore"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newScore">The score to create the rows for.</param>
|
||||||
|
/// <param name="playableBeatmap">The beatmap on which the score was set.</param>
|
||||||
|
protected virtual ICollection<StatisticRow> CreateStatisticRows(ScoreInfo newScore, IBeatmap playableBeatmap)
|
||||||
|
=> newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap);
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
ToggleVisibility();
|
ToggleVisibility();
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class AccuracyChangeRow : RankingChangeRow<double>
|
||||||
|
{
|
||||||
|
public AccuracyChangeRow()
|
||||||
|
: base(stats => stats.Accuracy)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label => UsersStrings.ShowStatsHitAccuracy;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCurrentValue(double current) => current.FormatAccuracy();
|
||||||
|
|
||||||
|
protected override int CalculateDifference(double previous, double current, out LocalisableString formattedDifference)
|
||||||
|
{
|
||||||
|
double difference = current - previous;
|
||||||
|
|
||||||
|
if (difference < 0)
|
||||||
|
formattedDifference = difference.FormatAccuracy();
|
||||||
|
else if (difference > 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"+{difference.FormatAccuracy()}");
|
||||||
|
else
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
|
||||||
|
return current.CompareTo(previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class GlobalRankChangeRow : RankingChangeRow<int?>
|
||||||
|
{
|
||||||
|
public GlobalRankChangeRow()
|
||||||
|
: base(stats => stats.GlobalRank)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label => UsersStrings.ShowRankGlobalSimple;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCurrentValue(int? current)
|
||||||
|
=> current == null ? string.Empty : current.Value.FormatRank();
|
||||||
|
|
||||||
|
protected override int CalculateDifference(int? previous, int? current, out LocalisableString formattedDifference)
|
||||||
|
{
|
||||||
|
if (previous == null && current == null)
|
||||||
|
{
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous == null && current != null)
|
||||||
|
{
|
||||||
|
formattedDifference = LocalisableString.Interpolate($"+{current.Value.FormatRank()}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous != null && current == null)
|
||||||
|
{
|
||||||
|
formattedDifference = LocalisableString.Interpolate($"-{previous.Value.FormatRank()}");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(previous != null && current != null);
|
||||||
|
|
||||||
|
// note that ranks work backwards, i.e. lower rank is _better_.
|
||||||
|
int difference = previous.Value - current.Value;
|
||||||
|
|
||||||
|
if (difference < 0)
|
||||||
|
formattedDifference = difference.FormatRank();
|
||||||
|
else if (difference > 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($"+{difference.FormatRank()}");
|
||||||
|
else
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class MaximumComboChangeRow : RankingChangeRow<int>
|
||||||
|
{
|
||||||
|
public MaximumComboChangeRow()
|
||||||
|
: base(stats => stats.MaxCombo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label => UsersStrings.ShowStatsMaximumCombo;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCurrentValue(int current) => LocalisableString.Interpolate($@"{current:N0}x");
|
||||||
|
|
||||||
|
protected override int CalculateDifference(int previous, int current, out LocalisableString formattedDifference)
|
||||||
|
{
|
||||||
|
int difference = current - previous;
|
||||||
|
|
||||||
|
if (difference < 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"{difference:N0}x");
|
||||||
|
else if (difference > 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}x");
|
||||||
|
else
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
|
||||||
|
return current.CompareTo(previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs
Normal file
78
osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class OverallRanking : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const float transition_duration = 300;
|
||||||
|
|
||||||
|
public Bindable<SoloStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<SoloStatisticsUpdate?>();
|
||||||
|
|
||||||
|
private LoadingLayer loadingLayer = null!;
|
||||||
|
private FillFlowContainer content = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
AutoSizeEasing = Easing.OutQuint;
|
||||||
|
AutoSizeDuration = transition_duration;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
loadingLayer = new LoadingLayer(withBox: false)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
content = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||||
|
new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||||
|
new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||||
|
new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||||
|
new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||||
|
new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
StatisticsUpdate.BindValueChanged(onUpdateReceived, true);
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUpdateReceived(ValueChangedEvent<SoloStatisticsUpdate?> update)
|
||||||
|
{
|
||||||
|
if (update.NewValue == null)
|
||||||
|
{
|
||||||
|
loadingLayer.Show();
|
||||||
|
content.FadeOut(transition_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loadingLayer.Hide();
|
||||||
|
content.FadeIn(transition_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class PerformancePointsChangeRow : RankingChangeRow<decimal?>
|
||||||
|
{
|
||||||
|
public PerformancePointsChangeRow()
|
||||||
|
: base(stats => stats.PP)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label => RankingsStrings.StatPerformance;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCurrentValue(decimal? current)
|
||||||
|
=> current == null ? string.Empty : LocalisableString.Interpolate($@"{current:N0}pp");
|
||||||
|
|
||||||
|
protected override int CalculateDifference(decimal? previous, decimal? current, out LocalisableString formattedDifference)
|
||||||
|
{
|
||||||
|
if (previous == null && current == null)
|
||||||
|
{
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous == null && current != null)
|
||||||
|
{
|
||||||
|
formattedDifference = LocalisableString.Interpolate($"+{current.Value:N0}pp");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous != null && current == null)
|
||||||
|
{
|
||||||
|
formattedDifference = LocalisableString.Interpolate($"-{previous.Value:N0}pp");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(previous != null && current != null);
|
||||||
|
|
||||||
|
decimal difference = current.Value - previous.Value;
|
||||||
|
|
||||||
|
if (difference < 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"{difference:N0}pp");
|
||||||
|
else if (difference > 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}pp");
|
||||||
|
else
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
|
||||||
|
return current.Value.CompareTo(previous.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class RankedScoreChangeRow : RankingChangeRow<long>
|
||||||
|
{
|
||||||
|
public RankedScoreChangeRow()
|
||||||
|
: base(stats => stats.RankedScore)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label => UsersStrings.ShowStatsRankedScore;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCurrentValue(long current) => current.ToLocalisableString(@"N0");
|
||||||
|
|
||||||
|
protected override int CalculateDifference(long previous, long current, out LocalisableString formattedDifference)
|
||||||
|
{
|
||||||
|
long difference = current - previous;
|
||||||
|
|
||||||
|
if (difference < 0)
|
||||||
|
formattedDifference = difference.ToLocalisableString(@"N0");
|
||||||
|
else if (difference > 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}");
|
||||||
|
else
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
|
||||||
|
return current.CompareTo(previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
144
osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs
Normal file
144
osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public abstract partial class RankingChangeRow<T> : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Bindable<SoloStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<SoloStatisticsUpdate?>();
|
||||||
|
|
||||||
|
private readonly Func<UserStatistics, T> accessor;
|
||||||
|
|
||||||
|
private OsuSpriteText currentValueText = null!;
|
||||||
|
private SpriteIcon changeIcon = null!;
|
||||||
|
private OsuSpriteText changeText = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
protected RankingChangeRow(
|
||||||
|
Func<UserStatistics, T> accessor)
|
||||||
|
{
|
||||||
|
this.accessor = accessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = Label,
|
||||||
|
Font = OsuFont.Default.With(size: 18)
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
changeIcon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(18)
|
||||||
|
},
|
||||||
|
currentValueText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Font = OsuFont.Default.With(size: 18, weight: FontWeight.Bold)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Font = OsuFont.Default.With(weight: FontWeight.Bold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStatisticsUpdate(ValueChangedEvent<SoloStatisticsUpdate?> statisticsUpdate)
|
||||||
|
{
|
||||||
|
var update = statisticsUpdate.NewValue;
|
||||||
|
|
||||||
|
if (update == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
T previousValue = accessor.Invoke(update.Before);
|
||||||
|
T currentValue = accessor.Invoke(update.After);
|
||||||
|
int comparisonResult = CalculateDifference(previousValue, currentValue, out var formattedDifference);
|
||||||
|
|
||||||
|
Colour4 comparisonColour;
|
||||||
|
IconUsage icon;
|
||||||
|
|
||||||
|
if (comparisonResult < 0)
|
||||||
|
{
|
||||||
|
comparisonColour = colours.Red1;
|
||||||
|
icon = FontAwesome.Solid.ArrowDown;
|
||||||
|
}
|
||||||
|
else if (comparisonResult > 0)
|
||||||
|
{
|
||||||
|
comparisonColour = colours.Lime1;
|
||||||
|
icon = FontAwesome.Solid.ArrowUp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
comparisonColour = colours.Orange1;
|
||||||
|
icon = FontAwesome.Solid.Minus;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentValueText.Text = FormatCurrentValue(currentValue);
|
||||||
|
|
||||||
|
changeIcon.Icon = icon;
|
||||||
|
changeIcon.Colour = comparisonColour;
|
||||||
|
|
||||||
|
changeText.Text = formattedDifference;
|
||||||
|
changeText.Colour = comparisonColour;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract LocalisableString Label { get; }
|
||||||
|
|
||||||
|
protected abstract LocalisableString FormatCurrentValue(T current);
|
||||||
|
protected abstract int CalculateDifference(T previous, T current, out LocalisableString formattedDifference);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||||
|
{
|
||||||
|
public partial class TotalScoreChangeRow : RankingChangeRow<long>
|
||||||
|
{
|
||||||
|
public TotalScoreChangeRow()
|
||||||
|
: base(stats => stats.TotalScore)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString Label => UsersStrings.ShowStatsTotalScore;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCurrentValue(long current) => current.ToLocalisableString(@"N0");
|
||||||
|
|
||||||
|
protected override int CalculateDifference(long previous, long current, out LocalisableString formattedDifference)
|
||||||
|
{
|
||||||
|
long difference = current - previous;
|
||||||
|
|
||||||
|
if (difference < 0)
|
||||||
|
formattedDifference = difference.ToLocalisableString(@"N0");
|
||||||
|
else if (difference > 0)
|
||||||
|
formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}");
|
||||||
|
else
|
||||||
|
formattedDifference = string.Empty;
|
||||||
|
|
||||||
|
return current.CompareTo(previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,8 +27,7 @@ namespace osu.Game.Users.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore ts)
|
private void load(TextureStore ts)
|
||||||
{
|
{
|
||||||
if (ts == null)
|
ArgumentNullException.ThrowIfNull(ts);
|
||||||
throw new ArgumentNullException(nameof(ts));
|
|
||||||
|
|
||||||
string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString();
|
string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString();
|
||||||
Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__");
|
Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__");
|
||||||
|
@ -36,8 +36,7 @@ namespace osu.Game.Users
|
|||||||
protected UserPanel(APIUser user)
|
protected UserPanel(APIUser user)
|
||||||
: base(HoverSampleSet.Button)
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
if (user == null)
|
ArgumentNullException.ThrowIfNull(user);
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
|
|
||||||
User = user;
|
User = user;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.18.0" />
|
<PackageReference Include="Realm" Version="10.18.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.1219.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.1226.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1221.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1221.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.23.1" />
|
<PackageReference Include="Sentry" Version="3.23.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1219.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1226.0" />
|
||||||
|
|
||||||
<!-- Required since Veldrid references a library that depends on Microsoft.DotNet.PlatformAbstractions (2.0.3), which doesn't play nice with Realm. -->
|
<!-- Required since Veldrid references a library that depends on Microsoft.DotNet.PlatformAbstractions (2.0.3), which doesn't play nice with Realm. -->
|
||||||
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||||
|
@ -146,7 +146,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyCanBeMadeInitOnly_002EGlobal/@EntryIndexedValue">HINT</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyCanBeMadeInitOnly_002EGlobal/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyCanBeMadeInitOnly_002ELocal/@EntryIndexedValue">HINT</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyCanBeMadeInitOnly_002ELocal/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PublicConstructorInAbstractClass/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PublicConstructorInAbstractClass/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArgumentDefaultValue/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArgumentDefaultValue/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArrayCreationExpression/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArrayCreationExpression/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeParentheses/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeParentheses/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeUsageProperty/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAttributeUsageProperty/@EntryIndexedValue">WARNING</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user