1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:47:27 +08:00

implemented everything

This commit is contained in:
Givikap120 2024-08-16 16:27:46 +03:00
parent 8bfb5cedc4
commit 2c5eeefa8d
5 changed files with 341 additions and 0 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@ -8,6 +9,7 @@ using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@ -121,6 +123,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
SpinnerCount = onlineInfo.SpinnerCount;
}
public override SkillValue[] GetSkillValues()
{
double aimPerformanceWithoutSliders = Math.Pow(OsuStrainSkill.DifficultyToPerformance(AimDifficulty * SliderFactor), 2);
double aimPerformanceOnlySliders = Math.Pow(OsuStrainSkill.DifficultyToPerformance(AimDifficulty), 2) - aimPerformanceWithoutSliders;
double speedPerformance = Math.Pow(OsuStrainSkill.DifficultyToPerformance(SpeedDifficulty), 2);
double flashlightPerformance = Math.Pow(Flashlight.DifficultyToPerformance(FlashlightDifficulty), 2);
return [
new SkillValue { Value = aimPerformanceOnlySliders, SkillName = "Slider Aim" },
new SkillValue { Value = aimPerformanceWithoutSliders, SkillName = "Aim" },
new SkillValue { Value = speedPerformance, SkillName = "Speed" },
new SkillValue { Value = flashlightPerformance, SkillName = "Flashlight" }
];
}
#region Newtonsoft.Json implicit ShouldSerialize() methods
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
@ -59,5 +60,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
public override SkillValue[] GetSkillValues()
{
//double aimPerformanceWithoutSliders = OsuStrainSkill.DifficultyToPerformance(AimDifficulty * SliderFactor);
//double speedPerformance = OsuStrainSkill.DifficultyToPerformance(SpeedDifficulty);
//double flashlightPerformance = Flashlight.DifficultyToPerformance(FlashlightDifficulty);
return [
new SkillValue { Value = difficultyRescale(ColourDifficulty), SkillName = "Colour" },
new SkillValue { Value = difficultyRescale(RhythmDifficulty), SkillName = "Rhythm" },
new SkillValue { Value = difficultyRescale(StaminaDifficulty), SkillName = "Stamina" },
];
}
private static double difficultyRescale(double difficulty) => 10.43 * Math.Log(difficulty * 1.4 / 8 + 1);
}
}

View File

@ -0,0 +1,106 @@
// 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.Graphics;
using osu.Framework.Graphics.Shapes;
using NUnit.Framework;
using osu.Framework.Testing;
using osuTK.Graphics;
using osu.Game.Skinning.Components;
using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using System.Threading;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Skinning;
using System.Collections.Generic;
namespace osu.Game.Tests.Visual.Components
{
public partial class TestSceneSkillsBreakdown : OsuTestScene
{
private TestBeatmapDifficultyCache difficultyCache = null!;
[BackgroundDependencyLoader]
private void load()
{
Dependencies.CacheAs<BeatmapDifficultyCache>(difficultyCache = new TestBeatmapDifficultyCache());
AddRange(new Drawable[]
{
difficultyCache,
new SkillsBreakdown
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new osuTK.Vector2(250)
}
});
}
[Test]
public void TestEqualValues()
{
AddStep("1", () =>
{
difficultyCache.UseRandomAttributes = false;
triggerCacheRecalc();
});
}
[Test]
public void TestRandomValues()
{
AddStep("2", () =>
{
difficultyCache.UseRandomAttributes = true;
triggerCacheRecalc();
});
}
private void triggerCacheRecalc()
{
// Invalidate cache to force recalc
difficultyCache.Invalidate(Beatmap.Value.BeatmapInfo);
// Change mods to trigger ValueChanged
SelectedMods.Value = SelectedMods.Value.Count >= 0 ? new List<Mod>() : SelectedMods.Value.Append(new OsuModNoFail()).ToList();
}
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
{
public bool UseRandomAttributes;
private Random random = new Random();
protected override Task<StarDifficulty?> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
{
var attributes = new TestDifficultyAttributes(
Enumerable.Range(0, 4)
.Select(_ => UseRandomAttributes ? random.NextDouble() : 1.0)
.ToArray());
return Task.FromResult<StarDifficulty?>(new StarDifficulty(attributes));
}
}
private partial class TestDifficultyAttributes : DifficultyAttributes
{
private SkillValue[] values;
public TestDifficultyAttributes(double[] values)
{
this.values = new SkillValue[values.Length];
for (int i = 0; i < values.Length; i++)
{
this.values[i] = new SkillValue { Value = values[i], SkillName = $"Test Skill {i + 1}" };
}
}
public override SkillValue[] GetSkillValues() => values;
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
@ -82,5 +83,13 @@ namespace osu.Game.Rulesets.Difficulty
{
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
}
public virtual SkillValue[] GetSkillValues() => [];
}
public struct SkillValue
{
public double Value;
public LocalisableString SkillName;
}
}

View File

@ -0,0 +1,190 @@
// 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 JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System;
using System.Linq;
using System.Threading;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Framework.Layout;
namespace osu.Game.Skinning
{
[UsedImplicitly]
public partial class SkillsBreakdown : CompositeDrawable, ISerialisableDrawable
{
public bool UsesFixedAnchor { get; set; }
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
private IBindable<StarDifficulty?> starDifficulty = null!;
private CancellationTokenSource? cancellationSource;
private Color4[] colors = null!;
private Container<SkillCircle> circles = null!;
public SkillsBreakdown()
{
Size = new Vector2(50);
}
[BackgroundDependencyLoader]
private void load(OsuColour colorSource)
{
InternalChild = circles = new Container<SkillCircle>
{
RelativeSizeAxes = Axes.Both
};
colors = new Color4[]
{
colorSource.Blue3,
colorSource.Lime3,
colorSource.Red3,
colorSource.YellowDark
};
foreach (var color in colors)
circles.Add(new SkillCircle(color, 1, 1));
circles.First().SetProgress(0, 1);
}
protected override void LoadComplete()
{
base.LoadComplete();
beatmap.BindValueChanged(b =>
{
cancellationSource?.Cancel();
starDifficulty = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token);
starDifficulty.BindValueChanged(d => updateSkillsBreakdown(d.NewValue));
}, true);
}
private void updateSkillsBreakdown(StarDifficulty? starDifficulty)
{
if (starDifficulty == null)
return;
var skillNameValues = starDifficulty.Value.Attributes.GetSkillValues();
// Square the values to make visual representation more intuitive
double[] skillValues = skillNameValues.Select(x => Math.Pow(x.Value, 1)).ToArray();
LocalisableString[] skillNames = skillNameValues.Select(x => x.SkillName).ToArray();
double sum = skillValues.Sum();
if (sum == 0) sum = 1;
double[] skillValuesNormalized = skillValues.Select(x => x / sum).ToArray();
double cumulativeValue = 0;
for (int i = 0; i < circles.Count; i++)
{
double nextCumulativeValue = i < skillValuesNormalized.Length ? cumulativeValue + skillValuesNormalized[i] : 1;
circles[i].SetProgress(Math.Round(cumulativeValue, 5), Math.Round(nextCumulativeValue, 5));
circles[i].SkillName = i < skillNames.Length ? skillNames[i] : "";
cumulativeValue = nextCumulativeValue;
}
}
private partial class SkillCircle : Container
{
public LocalisableString SkillName { set => innerCircle.SkillName = value; }
// Same as StarRatingDisplay
private const double animation_duration = 700;
private const Easing animation_type = Easing.OutQuint;
private TooltipCircularProgress innerCircle;
public SkillCircle(Color4 color, double startProgress, double endProgress)
{
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Child = innerCircle = new TooltipCircularProgress
{
InnerRadius = 0.5f,
Colour = color,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both
};
SetProgress(startProgress, endProgress);
}
public void SetProgress(double startProgress, double endProgress)
{
this.RotateTo((float)startProgress * 360, animation_duration, animation_type);
innerCircle.ProgressTo(endProgress - startProgress, animation_duration, animation_type);
}
private partial class TooltipCircularProgress : CircularProgress, IHasTooltip
{
public LocalisableString SkillName;
LocalisableString IHasTooltip.TooltipText => SkillName;
protected override void LoadComplete()
{
base.LoadComplete();
precalcInputData();
}
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
{
bool result = base.OnInvalidate(invalidation, source);
precalcInputData();
return result;
}
private void precalcInputData()
{
center = ScreenSpaceDrawQuad.Centre;
float radius = ScreenSpaceDrawQuad.Width / 2;
innerDistanceSquared = MathF.Pow(radius * InnerRadius, 2);
outerDistanceSquared = MathF.Pow(radius, 2);
startAngle = Parent.Rotation * MathF.PI / 180;
endAngle = startAngle + (float)Progress * 2 * MathF.PI;
}
private Vector2 center;
private double innerDistanceSquared, outerDistanceSquared;
private float startAngle, endAngle;
private float deltaAngle;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
Vector2 deltaVector = screenSpacePos - center;
double distanceSquared = deltaVector.X * deltaVector.X + deltaVector.Y * deltaVector.Y;
if (distanceSquared > outerDistanceSquared || distanceSquared < innerDistanceSquared)
return false;
deltaAngle = MathF.Atan2(-deltaVector.X, deltaVector.Y) + MathF.PI;
return deltaAngle > startAngle && deltaAngle < endAngle;
}
}
}
}
}