mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 10:12:54 +08:00
Merge branch 'master' into better-erroring-score-import-fail
This commit is contained in:
commit
934ac1b640
@ -25,6 +25,7 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
|
@ -25,12 +25,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
public override int Version => 20220701;
|
||||
|
||||
private readonly IWorkingBeatmap workingBeatmap;
|
||||
|
||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
workingBeatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
@ -49,15 +46,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
||||
};
|
||||
|
||||
if (ComputeLegacyScoringValues)
|
||||
{
|
||||
CatchLegacyScoreSimulator sv1Simulator = new CatchLegacyScoreSimulator();
|
||||
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
|
||||
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
|
||||
attributes.LegacyComboScore = sv1Simulator.ComboScore;
|
||||
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
@ -2,33 +2,26 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
public int AccuracyScore { get; private set; }
|
||||
|
||||
public int ComboScore { get; private set; }
|
||||
|
||||
public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore;
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int modernBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
|
||||
private double scoreMultiplier;
|
||||
|
||||
public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
|
||||
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||
{
|
||||
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
||||
|
||||
@ -70,13 +63,19 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
+ baseBeatmap.Difficulty.CircleSize
|
||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
||||
|
||||
scoreMultiplier = difficultyPeppyStars * mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
|
||||
scoreMultiplier = difficultyPeppyStars;
|
||||
|
||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||
|
||||
foreach (var obj in playableBeatmap.HitObjects)
|
||||
simulateHit(obj);
|
||||
simulateHit(obj, ref attributes);
|
||||
|
||||
attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void simulateHit(HitObject hitObject)
|
||||
private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attributes)
|
||||
{
|
||||
bool increaseCombo = true;
|
||||
bool addScoreComboMultiplier = false;
|
||||
@ -112,28 +111,28 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
case JuiceStream:
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
simulateHit(nested);
|
||||
simulateHit(nested, ref attributes);
|
||||
return;
|
||||
|
||||
case BananaShower:
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
simulateHit(nested);
|
||||
simulateHit(nested, ref attributes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (addScoreComboMultiplier)
|
||||
{
|
||||
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
|
||||
ComboScore += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * scoreMultiplier));
|
||||
attributes.ComboScore += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * scoreMultiplier));
|
||||
}
|
||||
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
modernBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
}
|
||||
else
|
||||
AccuracyScore += scoreIncrease;
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
if (increaseCombo)
|
||||
combo++;
|
||||
|
@ -2,17 +2,21 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
||||
{
|
||||
private static readonly Vector2 banana_max_size = new Vector2(128);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Texture? texture = Skin.GetTexture("fruit-bananas");
|
||||
Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay");
|
||||
Texture? texture = Skin.GetTexture("fruit-bananas")?.WithMaximumSize(banana_max_size);
|
||||
Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay")?.WithMaximumSize(banana_max_size);
|
||||
|
||||
SetTexture(texture, overlayTexture);
|
||||
}
|
||||
|
@ -2,12 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
||||
{
|
||||
private static readonly Vector2 droplet_max_size = new Vector2(82, 103);
|
||||
|
||||
public LegacyDropletPiece()
|
||||
{
|
||||
Scale = new Vector2(0.8f);
|
||||
@ -17,8 +20,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Texture? texture = Skin.GetTexture("fruit-drop");
|
||||
Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay");
|
||||
Texture? texture = Skin.GetTexture("fruit-drop")?.WithMaximumSize(droplet_max_size);
|
||||
Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay")?.WithMaximumSize(droplet_max_size);
|
||||
|
||||
SetTexture(texture, overlayTexture);
|
||||
}
|
||||
|
@ -2,11 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
{
|
||||
internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
||||
{
|
||||
private static readonly Vector2 fruit_max_size = new Vector2(128);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -22,21 +26,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
switch (visualRepresentation)
|
||||
{
|
||||
case FruitVisualRepresentation.Pear:
|
||||
SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay"));
|
||||
setTextures("pear");
|
||||
break;
|
||||
|
||||
case FruitVisualRepresentation.Grape:
|
||||
SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay"));
|
||||
setTextures("grapes");
|
||||
break;
|
||||
|
||||
case FruitVisualRepresentation.Pineapple:
|
||||
SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay"));
|
||||
setTextures("apple");
|
||||
break;
|
||||
|
||||
case FruitVisualRepresentation.Raspberry:
|
||||
SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay"));
|
||||
setTextures("orange");
|
||||
break;
|
||||
}
|
||||
|
||||
void setTextures(string fruitName) => SetTexture(
|
||||
Skin.GetTexture($"fruit-{fruitName}")?.WithMaximumSize(fruit_max_size),
|
||||
Skin.GetTexture($"fruit-{fruitName}-overlay")?.WithMaximumSize(fruit_max_size)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,13 +31,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
|
||||
public override int Version => 20220902;
|
||||
|
||||
private readonly IWorkingBeatmap workingBeatmap;
|
||||
|
||||
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
workingBeatmap = beatmap;
|
||||
|
||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
|
||||
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
|
||||
}
|
||||
@ -60,15 +56,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject),
|
||||
};
|
||||
|
||||
if (ComputeLegacyScoringValues)
|
||||
{
|
||||
ManiaLegacyScoreSimulator sv1Simulator = new ManiaLegacyScoreSimulator();
|
||||
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
|
||||
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
|
||||
attributes.LegacyComboScore = sv1Simulator.ComboScore;
|
||||
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,16 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
internal class ManiaLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
public int AccuracyScore => 0;
|
||||
public int ComboScore { get; private set; }
|
||||
public double BonusScoreRatio => 0;
|
||||
|
||||
public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
|
||||
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||
{
|
||||
double multiplier = mods.Where(m => m is not (ModHidden or ModHardRock or ModDoubleTime or ModFlashlight or ManiaModFadeIn))
|
||||
.Select(m => m.ScoreMultiplier)
|
||||
.Aggregate(1.0, (c, n) => c * n);
|
||||
|
||||
ComboScore = (int)(1000000 * multiplier);
|
||||
return new LegacyScoreAttributes { ComboScore = 1000000 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
@ -25,33 +26,42 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
// Avoid flickering due to no anti-aliasing of boxes by default.
|
||||
var edgeSmoothness = new Vector2(0.3f);
|
||||
|
||||
AddInternal(mainLine = new Box
|
||||
{
|
||||
Name = "Bar line",
|
||||
EdgeSmoothness = edgeSmoothness,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
});
|
||||
|
||||
Vector2 size = new Vector2(22, 6);
|
||||
const float line_offset = 4;
|
||||
const float major_extension = 10;
|
||||
|
||||
AddInternal(leftAnchor = new Circle
|
||||
AddInternal(leftAnchor = new Box
|
||||
{
|
||||
Name = "Left anchor",
|
||||
EdgeSmoothness = edgeSmoothness,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = size,
|
||||
X = -line_offset,
|
||||
Width = major_extension,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Colour = ColourInfo.GradientHorizontal(Colour4.Transparent, Colour4.White),
|
||||
});
|
||||
|
||||
AddInternal(rightAnchor = new Circle
|
||||
AddInternal(rightAnchor = new Box
|
||||
{
|
||||
Name = "Right anchor",
|
||||
EdgeSmoothness = edgeSmoothness,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = size,
|
||||
X = line_offset,
|
||||
Width = major_extension,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Colour = ColourInfo.GradientHorizontal(Colour4.White, Colour4.Transparent),
|
||||
});
|
||||
|
||||
major = ((DrawableBarLine)drawableHitObject).Major.GetBoundCopy();
|
||||
@ -66,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
|
||||
private void updateMajor(ValueChangedEvent<bool> major)
|
||||
{
|
||||
mainLine.Alpha = major.NewValue ? 0.5f : 0.2f;
|
||||
leftAnchor.Alpha = rightAnchor.Alpha = major.NewValue ? 1 : 0;
|
||||
leftAnchor.Alpha = rightAnchor.Alpha = major.NewValue ? mainLine.Alpha * 0.3f : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
|
||||
AddUntilStep("Pitch increases", () => getSpinningSample().Frequency.Value > 0.8);
|
||||
|
||||
PausableSkinnableSound getSpinningSample() => drawableSpinner.ChildrenOfType<PausableSkinnableSound>().FirstOrDefault(s => s.Samples.Any(i => i.LookupNames.Any(l => l.Contains("spinnerspin"))));
|
||||
PausableSkinnableSound getSpinningSample() =>
|
||||
drawableSpinner.ChildrenOfType<PausableSkinnableSound>().FirstOrDefault(s => s.Samples.Any(i => i.LookupNames.Any(l => l.Contains("spinnerspin"))));
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
@ -64,6 +65,39 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("Short spinner implicitly completes", () => drawableSpinner.Progress == 1);
|
||||
}
|
||||
|
||||
[TestCase(0, 4, 6)]
|
||||
[TestCase(5, 7, 10)]
|
||||
[TestCase(10, 11, 8)]
|
||||
public void TestSpinnerSpinRequirements(int od, int normalTicks, int bonusTicks)
|
||||
{
|
||||
Spinner spinner = null;
|
||||
|
||||
AddStep("add spinner", () => SetContents(_ =>
|
||||
{
|
||||
spinner = new Spinner
|
||||
{
|
||||
StartTime = Time.Current,
|
||||
EndTime = Time.Current + 3000,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
}
|
||||
};
|
||||
|
||||
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { OverallDifficulty = od });
|
||||
|
||||
return drawableSpinner = new TestDrawableSpinner(spinner, true)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Depth = depthIndex++,
|
||||
Scale = new Vector2(0.75f)
|
||||
};
|
||||
}));
|
||||
|
||||
AddAssert("number of normal ticks matches", () => spinner.SpinsRequired, () => Is.EqualTo(normalTicks));
|
||||
AddAssert("number of bonus ticks matches", () => spinner.MaximumBonusSpins, () => Is.EqualTo(bonusTicks));
|
||||
}
|
||||
|
||||
private Drawable testSingle(float circleSize, bool auto = false, double length = 3000)
|
||||
{
|
||||
const double delay = 2000;
|
||||
|
@ -26,12 +26,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
public override int Version => 20220902;
|
||||
|
||||
private readonly IWorkingBeatmap workingBeatmap;
|
||||
|
||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
workingBeatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
@ -109,15 +106,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
SpinnerCount = spinnerCount,
|
||||
};
|
||||
|
||||
if (ComputeLegacyScoringValues)
|
||||
{
|
||||
OsuLegacyScoreSimulator sv1Simulator = new OsuLegacyScoreSimulator();
|
||||
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
|
||||
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
|
||||
attributes.LegacyComboScore = sv1Simulator.ComboScore;
|
||||
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
@ -2,37 +2,27 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
public int AccuracyScore { get; private set; }
|
||||
|
||||
public int ComboScore { get; private set; }
|
||||
|
||||
public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore;
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int modernBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
|
||||
private double scoreMultiplier;
|
||||
private IBeatmap playableBeatmap = null!;
|
||||
|
||||
public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
|
||||
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||
{
|
||||
this.playableBeatmap = playableBeatmap;
|
||||
|
||||
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
||||
|
||||
int countNormal = 0;
|
||||
@ -73,13 +63,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
+ baseBeatmap.Difficulty.CircleSize
|
||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
||||
|
||||
scoreMultiplier = difficultyPeppyStars * mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
|
||||
scoreMultiplier = difficultyPeppyStars;
|
||||
|
||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||
|
||||
foreach (var obj in playableBeatmap.HitObjects)
|
||||
simulateHit(obj);
|
||||
simulateHit(obj, ref attributes);
|
||||
|
||||
attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void simulateHit(HitObject hitObject)
|
||||
private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attributes)
|
||||
{
|
||||
bool increaseCombo = true;
|
||||
bool addScoreComboMultiplier = false;
|
||||
@ -122,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
case Slider:
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
simulateHit(nested);
|
||||
simulateHit(nested, ref attributes);
|
||||
|
||||
scoreIncrease = 300;
|
||||
increaseCombo = false;
|
||||
@ -133,22 +129,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
// The spinner object applies a lenience because gameplay mechanics differ from osu-stable.
|
||||
// We'll redo the calculations to match osu-stable here...
|
||||
const double maximum_rotations_per_second = 477.0 / 60;
|
||||
double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(playableBeatmap.Difficulty.OverallDifficulty, 3, 5, 7.5);
|
||||
|
||||
// Normally, this value depends on the final overall difficulty. For simplicity, we'll only consider the worst case that maximises bonus score.
|
||||
// As we're primarily concerned with computing the maximum theoretical final score,
|
||||
// this will have the final effect of slightly underestimating bonus score achieved on stable when converting from score V1.
|
||||
const double minimum_rotations_per_second = 3;
|
||||
|
||||
double secondsDuration = spinner.Duration / 1000;
|
||||
|
||||
// The total amount of half spins possible for the entire spinner.
|
||||
int totalHalfSpinsPossible = (int)(secondsDuration * maximum_rotations_per_second * 2);
|
||||
// The amount of half spins that are required to successfully complete the spinner (i.e. get a 300).
|
||||
int halfSpinsRequiredForCompletion = (int)(secondsDuration * minimumRotationsPerSecond);
|
||||
int halfSpinsRequiredForCompletion = (int)(secondsDuration * minimum_rotations_per_second);
|
||||
// To be able to receive bonus points, the spinner must be rotated another 1.5 times.
|
||||
int halfSpinsRequiredBeforeBonus = halfSpinsRequiredForCompletion + 3;
|
||||
|
||||
for (int i = 0; i <= totalHalfSpinsPossible; i++)
|
||||
{
|
||||
if (i > halfSpinsRequiredBeforeBonus && (i - halfSpinsRequiredBeforeBonus) % 2 == 0)
|
||||
simulateHit(new SpinnerBonusTick());
|
||||
simulateHit(new SpinnerBonusTick(), ref attributes);
|
||||
else if (i > 1 && i % 2 == 0)
|
||||
simulateHit(new SpinnerTick());
|
||||
simulateHit(new SpinnerTick(), ref attributes);
|
||||
}
|
||||
|
||||
scoreIncrease = 300;
|
||||
@ -159,16 +160,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (addScoreComboMultiplier)
|
||||
{
|
||||
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
|
||||
ComboScore += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * scoreMultiplier));
|
||||
attributes.ComboScore += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * scoreMultiplier));
|
||||
}
|
||||
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
modernBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
}
|
||||
else
|
||||
AccuracyScore += scoreIncrease;
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
if (increaseCombo)
|
||||
combo++;
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
InternalChild = content = new Container
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
CornerRadius = Size.X / 2;
|
||||
CornerExponent = 2;
|
||||
|
@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public HitReceptor()
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private void load()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
AddInternal(scaleContainer = new Container
|
||||
{
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private void load()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
||||
|
@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public const float OBJECT_RADIUS = 64;
|
||||
|
||||
/// <summary>
|
||||
/// The width and height any element participating in display of a hitcircle (or similarly sized object) should be.
|
||||
/// </summary>
|
||||
public static readonly Vector2 OBJECT_DIMENSIONS = new Vector2(OBJECT_RADIUS * 2);
|
||||
|
||||
/// <summary>
|
||||
/// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track).
|
||||
/// </summary>
|
||||
|
@ -18,6 +18,16 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class Spinner : OsuHitObject, IHasDuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The RPM required to clear the spinner at ODs [ 0, 5, 10 ].
|
||||
/// </summary>
|
||||
private static readonly (int min, int mid, int max) clear_rpm_range = (90, 150, 225);
|
||||
|
||||
/// <summary>
|
||||
/// The RPM required to complete the spinner and receive full score at ODs [ 0, 5, 10 ].
|
||||
/// </summary>
|
||||
private static readonly (int min, int mid, int max) complete_rpm_range = (250, 380, 430);
|
||||
|
||||
public double EndTime
|
||||
{
|
||||
get => StartTime + Duration;
|
||||
@ -52,13 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
const double maximum_rotations_per_second = 477f / 60f;
|
||||
// The average RPS required over the length of the spinner to clear the spinner.
|
||||
double minRps = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, clear_rpm_range) / 60;
|
||||
|
||||
// The RPS required over the length of the spinner to receive full score (all normal + bonus ticks).
|
||||
double maxRps = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, complete_rpm_range) / 60;
|
||||
|
||||
double secondsDuration = Duration / 1000;
|
||||
double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 1.5, 2.5, 3.75);
|
||||
|
||||
SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond);
|
||||
MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration) - bonus_spins_gap;
|
||||
SpinsRequired = (int)(minRps * secondsDuration);
|
||||
MaximumBonusSpins = Math.Max(0, (int)(maxRps * secondsDuration) - SpinsRequired - bonus_spins_gap);
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
|
@ -33,6 +33,7 @@ using osu.Game.Rulesets.Osu.Statistics;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
|
||||
private Bindable<bool> configHitLighting = null!;
|
||||
|
||||
private static readonly Vector2 circle_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
private static readonly Vector2 circle_size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
public CirclePiece()
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
Masking = true;
|
||||
|
||||
CornerRadius = Size.X / 2;
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
public ExplodePiece()
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public FlashPiece()
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
public MainCirclePiece()
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Child = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public RingPiece(float thickness = 9)
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
@ -5,12 +5,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
// todo: this should probably not be a SkinnableSprite, as this is always created for legacy skins and is recreated on skin change.
|
||||
public partial class LegacyApproachCircle : SkinnableSprite
|
||||
{
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||
|
||||
public LegacyApproachCircle()
|
||||
: base("Gameplay/osu/approachcircle")
|
||||
: base("Gameplay/osu/approachcircle", OsuHitObject.OBJECT_DIMENSIONS)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
this.priorityLookupPrefix = priorityLookupPrefix;
|
||||
this.hasNumber = hasNumber;
|
||||
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png.
|
||||
InternalChildren = new[]
|
||||
{
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -77,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d, maxSize: OsuHitObject.OBJECT_DIMENSIONS))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
||||
|
||||
InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true) ?? Empty());
|
||||
InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true, maxSize: OsuHitObject.OBJECT_DIMENSIONS) ?? Empty());
|
||||
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = skin.GetTexture("sliderb-nd"),
|
||||
Texture = skin.GetTexture("sliderb-nd")?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS),
|
||||
Colour = new Color4(5, 5, 5, 255),
|
||||
},
|
||||
LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d =>
|
||||
@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = skin.GetTexture("sliderb-spec"),
|
||||
Texture = skin.GetTexture("sliderb-spec")?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS),
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
/// Their hittable area is 128px, but the actual circle portion is 118px.
|
||||
/// We must account for some gameplay elements such as slider bodies, where this padding is not present.
|
||||
/// </summary>
|
||||
public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
|
||||
public const float LEGACY_CIRCLE_RADIUS = OsuHitObject.OBJECT_RADIUS - 5;
|
||||
|
||||
public OsuLegacySkinTransformer(ISkin skin)
|
||||
: base(skin)
|
||||
@ -41,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: new Vector2(308f));
|
||||
if (followCircleContent != null)
|
||||
return new LegacyFollowCircle(followCircleContent);
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "", maxSize: OsuHitObject.OBJECT_DIMENSIONS);
|
||||
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
@ -138,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
|
||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||
return new LegacySpriteText(LegacyFont.HitCircle, OsuHitObject.OBJECT_DIMENSIONS)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
new RingPiece(3)
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2),
|
||||
Size = OsuHitObject.OBJECT_DIMENSIONS,
|
||||
Alpha = 0.1f,
|
||||
}
|
||||
};
|
||||
|
@ -25,12 +25,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
public override int Version => 20220902;
|
||||
|
||||
private readonly IWorkingBeatmap workingBeatmap;
|
||||
|
||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
workingBeatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
@ -99,15 +96,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||
};
|
||||
|
||||
if (ComputeLegacyScoringValues)
|
||||
{
|
||||
TaikoLegacyScoreSimulator sv1Simulator = new TaikoLegacyScoreSimulator();
|
||||
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
|
||||
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
|
||||
attributes.LegacyComboScore = sv1Simulator.ComboScore;
|
||||
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
@ -2,39 +2,29 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
public int AccuracyScore { get; private set; }
|
||||
|
||||
public int ComboScore { get; private set; }
|
||||
|
||||
public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore;
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int modernBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
|
||||
private double modMultiplier;
|
||||
private int difficultyPeppyStars;
|
||||
private IBeatmap playableBeatmap = null!;
|
||||
private IReadOnlyList<Mod> mods = null!;
|
||||
|
||||
public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
|
||||
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||
{
|
||||
this.playableBeatmap = playableBeatmap;
|
||||
this.mods = mods;
|
||||
|
||||
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
||||
|
||||
@ -76,13 +66,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
+ baseBeatmap.Difficulty.CircleSize
|
||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
||||
|
||||
modMultiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
|
||||
LegacyScoreAttributes attributes = new LegacyScoreAttributes();
|
||||
|
||||
foreach (var obj in playableBeatmap.HitObjects)
|
||||
simulateHit(obj);
|
||||
simulateHit(obj, ref attributes);
|
||||
|
||||
attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void simulateHit(HitObject hitObject)
|
||||
private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attributes)
|
||||
{
|
||||
bool increaseCombo = true;
|
||||
bool addScoreComboMultiplier = false;
|
||||
@ -109,21 +103,24 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
case Swell swell:
|
||||
// The taiko swell generally does not match the osu-stable implementation in any way.
|
||||
// We'll redo the calculations to match osu-stable here...
|
||||
double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(playableBeatmap.Difficulty.OverallDifficulty, 3, 5, 7.5);
|
||||
double secondsDuration = swell.Duration / 1000;
|
||||
|
||||
// Normally, this value depends on the final overall difficulty. For simplicity, we'll only consider the worst case that maximises rotations.
|
||||
const double minimum_rotations_per_second = 7.5;
|
||||
|
||||
// The amount of half spins that are required to successfully complete the spinner (i.e. get a 300).
|
||||
int halfSpinsRequiredForCompletion = (int)(secondsDuration * minimumRotationsPerSecond);
|
||||
|
||||
int halfSpinsRequiredForCompletion = (int)(swell.Duration / 1000 * minimum_rotations_per_second);
|
||||
halfSpinsRequiredForCompletion = (int)Math.Max(1, halfSpinsRequiredForCompletion * 1.65f);
|
||||
|
||||
if (mods.Any(m => m is ModDoubleTime))
|
||||
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 0.75f));
|
||||
if (mods.Any(m => m is ModHalfTime))
|
||||
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 1.5f));
|
||||
//
|
||||
// Normally, this multiplier depends on the active mods (DT = 0.75, HT = 1.5). For simplicity, we'll only consider the worst case that maximises rotations.
|
||||
// This way, scores remain beatable at the cost of the conversion being slightly inaccurate.
|
||||
// - A perfect DT/NM score will have less than 1M total score (excluding bonus).
|
||||
// - A perfect HT score will have 1M total score (excluding bonus).
|
||||
//
|
||||
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 1.5f));
|
||||
|
||||
for (int i = 0; i <= halfSpinsRequiredForCompletion; i++)
|
||||
simulateHit(new SwellTick());
|
||||
simulateHit(new SwellTick(), ref attributes);
|
||||
|
||||
scoreIncrease = 300;
|
||||
addScoreComboMultiplier = true;
|
||||
@ -139,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
case DrumRoll:
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
simulateHit(nested);
|
||||
simulateHit(nested, ref attributes);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -159,8 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
int oldScoreIncrease = scoreIncrease;
|
||||
|
||||
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
|
||||
scoreIncrease += (int)(scoreIncrease / 35 * 2 * (difficultyPeppyStars + 1) * modMultiplier) * (Math.Min(100, combo) / 10);
|
||||
scoreIncrease += scoreIncrease / 35 * 2 * (difficultyPeppyStars + 1) * (Math.Min(100, combo) / 10);
|
||||
|
||||
if (hitObject is Swell)
|
||||
{
|
||||
@ -185,15 +181,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
scoreIncrease -= comboScoreIncrease;
|
||||
|
||||
if (addScoreComboMultiplier)
|
||||
ComboScore += comboScoreIncrease;
|
||||
attributes.ComboScore += comboScoreIncrease;
|
||||
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
modernBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
}
|
||||
else
|
||||
AccuracyScore += scoreIncrease;
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
if (increaseCombo)
|
||||
combo++;
|
||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
private static readonly Vector2 circle_piece_size = new Vector2(128);
|
||||
|
||||
private Drawable backgroundLayer = null!;
|
||||
private Drawable? foregroundLayer;
|
||||
|
||||
@ -52,9 +54,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
|
||||
string prefix = ((drawableHitObject.HitObject as TaikoStrongableHitObject)?.IsStrong ?? false) ? big_hit : normal_hit;
|
||||
|
||||
return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
|
||||
return skin.GetAnimation($"{prefix}{lookup}", true, false, maxSize: circle_piece_size) ??
|
||||
// fallback to regular size if "big" version doesn't exist.
|
||||
skin.GetAnimation($"{normal_hit}{lookup}", true, false);
|
||||
skin.GetAnimation($"{normal_hit}{lookup}", true, false, maxSize: circle_piece_size);
|
||||
}
|
||||
|
||||
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||
@ -96,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
// Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay".
|
||||
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
||||
foreach (var c in InternalChildren)
|
||||
c.Scale = new Vector2(DrawHeight / 128);
|
||||
c.Scale = new Vector2(DrawHeight / circle_piece_size.Y);
|
||||
|
||||
if (foregroundLayer is IFramedAnimation animatableForegroundLayer)
|
||||
animateForegroundLayer(animatableForegroundLayer);
|
||||
|
@ -34,6 +34,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
|
@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
});
|
||||
|
||||
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
||||
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||
AddUntilStep("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||
|
||||
AddStep("test play", () => Editor.TestGameplay());
|
||||
|
||||
|
@ -2,17 +2,23 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -21,17 +27,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
[Cached]
|
||||
private Storyboard storyboard { get; set; } = new Storyboard();
|
||||
[Cached(typeof(Storyboard))]
|
||||
private TestStoryboard storyboard { get; set; } = new TestStoryboard();
|
||||
|
||||
private IEnumerable<DrawableStoryboardSprite> sprites => this.ChildrenOfType<DrawableStoryboardSprite>();
|
||||
|
||||
private const string lookup_name = "hitcircleoverlay";
|
||||
|
||||
[Test]
|
||||
public void TestSkinSpriteDisallowedByDefault()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false);
|
||||
AddStep("disallow all lookups", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = false;
|
||||
storyboard.AlwaysProvideTexture = false;
|
||||
});
|
||||
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
|
||||
@ -40,11 +50,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllowLookupFromSkin()
|
||||
public void TestLookupFromStoryboard()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||
AddStep("allow storyboard lookup", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = false;
|
||||
storyboard.AlwaysProvideTexture = true;
|
||||
});
|
||||
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
|
||||
@ -52,16 +64,54 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("sprite found texture", () =>
|
||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture != null)));
|
||||
|
||||
AddAssert("skinnable sprite has correct size", () =>
|
||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Size == new Vector2(128))));
|
||||
assertStoryboardSourced();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkinLookupPreferredOverStoryboard()
|
||||
{
|
||||
AddStep("allow all lookups", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = true;
|
||||
storyboard.AlwaysProvideTexture = true;
|
||||
});
|
||||
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
|
||||
// Only checking for at least one sprite that succeeded, as not all skins in this test provide the hitcircleoverlay texture.
|
||||
AddAssert("sprite found texture", () =>
|
||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture != null)));
|
||||
|
||||
assertSkinSourced();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllowLookupFromSkin()
|
||||
{
|
||||
AddStep("allow skin lookup", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = true;
|
||||
storyboard.AlwaysProvideTexture = false;
|
||||
});
|
||||
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
|
||||
// Only checking for at least one sprite that succeeded, as not all skins in this test provide the hitcircleoverlay texture.
|
||||
AddAssert("sprite found texture", () =>
|
||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture != null)));
|
||||
|
||||
assertSkinSourced();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFlippedSprite()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
AddStep("allow all lookups", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = true;
|
||||
storyboard.AlwaysProvideTexture = true;
|
||||
});
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
AddStep("flip sprites", () => sprites.ForEach(s =>
|
||||
{
|
||||
@ -74,9 +124,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestZeroScale()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
AddStep("allow all lookups", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = true;
|
||||
storyboard.AlwaysProvideTexture = true;
|
||||
});
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
AddAssert("sprites present", () => sprites.All(s => s.IsPresent));
|
||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1)));
|
||||
@ -86,9 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestNegativeScale()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
AddStep("allow all lookups", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = true;
|
||||
storyboard.AlwaysProvideTexture = true;
|
||||
});
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
|
||||
AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
|
||||
@ -97,9 +153,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestNegativeScaleWithFlippedSprite()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
AddStep("allow all lookups", () =>
|
||||
{
|
||||
storyboard.UseSkinSprites = true;
|
||||
storyboard.AlwaysProvideTexture = true;
|
||||
});
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
|
||||
AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
|
||||
@ -111,13 +170,78 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft));
|
||||
}
|
||||
|
||||
private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
|
||||
=> new DrawableStoryboardSprite(
|
||||
new StoryboardSprite(lookupName, origin, initialPosition)
|
||||
).With(s =>
|
||||
private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
|
||||
{
|
||||
var layer = storyboard.GetLayer("Background");
|
||||
|
||||
var sprite = new StoryboardSprite(lookupName, origin, initialPosition);
|
||||
sprite.AddLoop(Time.Current, 100).Alpha.Add(Easing.None, 0, 10000, 1, 1);
|
||||
|
||||
layer.Elements.Clear();
|
||||
layer.Add(sprite);
|
||||
|
||||
return storyboard.CreateDrawable().With(s => s.RelativeSizeAxes = Axes.Both);
|
||||
}
|
||||
|
||||
private void assertStoryboardSourced()
|
||||
{
|
||||
AddAssert("sprite came from storyboard", () =>
|
||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Size == new Vector2(200))));
|
||||
}
|
||||
|
||||
private void assertSkinSourced()
|
||||
{
|
||||
AddAssert("sprite came from skin", () =>
|
||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Size == new Vector2(128))));
|
||||
}
|
||||
|
||||
private partial class TestStoryboard : Storyboard
|
||||
{
|
||||
public override DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null)
|
||||
{
|
||||
s.LifetimeStart = double.MinValue;
|
||||
s.LifetimeEnd = double.MaxValue;
|
||||
});
|
||||
return new TestDrawableStoryboard(this, mods);
|
||||
}
|
||||
|
||||
public bool AlwaysProvideTexture { get; set; }
|
||||
|
||||
public override string GetStoragePathFromStoryboardPath(string path) => AlwaysProvideTexture ? path : string.Empty;
|
||||
|
||||
private partial class TestDrawableStoryboard : DrawableStoryboard
|
||||
{
|
||||
private readonly bool alwaysProvideTexture;
|
||||
|
||||
public TestDrawableStoryboard(TestStoryboard storyboard, IReadOnlyList<Mod>? mods)
|
||||
: base(storyboard, mods)
|
||||
{
|
||||
alwaysProvideTexture = storyboard.AlwaysProvideTexture;
|
||||
}
|
||||
|
||||
protected override IResourceStore<byte[]> CreateResourceLookupStore() => alwaysProvideTexture
|
||||
? new AlwaysReturnsTextureStore()
|
||||
: new ResourceStore<byte[]>();
|
||||
|
||||
internal class AlwaysReturnsTextureStore : IResourceStore<byte[]>
|
||||
{
|
||||
private const string test_image = "Resources/Textures/test-image.png";
|
||||
|
||||
private readonly DllResourceStore store;
|
||||
|
||||
public AlwaysReturnsTextureStore()
|
||||
{
|
||||
store = TestResources.GetStore();
|
||||
}
|
||||
|
||||
public void Dispose() => store.Dispose();
|
||||
|
||||
public byte[] Get(string name) => store.Get(test_image);
|
||||
|
||||
public Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => store.GetAsync(test_image, cancellationToken);
|
||||
|
||||
public Stream GetStream(string name) => store.GetStream(test_image);
|
||||
|
||||
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
// 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.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// Upscales all gameplay sprites by a huge amount, to aid in manually checking skin texture size limits
|
||||
/// on individual elements.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The HUD is hidden as it does't really affect game balance if HUD elements are larger than they should be.
|
||||
/// </remarks>
|
||||
public partial class TestScenePlayerMaxDimensions : TestSceneAllRulesetPlayers
|
||||
{
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
// for now this only applies to legacy skins, as modern skins don't have texture-based gameplay elements yet.
|
||||
dependencies.CacheAs<ISkinSource>(new UpscaledLegacySkin(dependencies.Get<SkinManager>()));
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
protected override void AddCheckSteps()
|
||||
{
|
||||
}
|
||||
|
||||
protected override Player CreatePlayer(Ruleset ruleset)
|
||||
{
|
||||
var player = base.CreatePlayer(ruleset);
|
||||
player.OnLoadComplete += _ =>
|
||||
{
|
||||
// this test scene focuses on gameplay elements, so let's hide the hud.
|
||||
var hudOverlay = player.ChildrenOfType<HUDOverlay>().Single();
|
||||
hudOverlay.ShowHud.Value = false;
|
||||
hudOverlay.ShowHud.Disabled = true;
|
||||
};
|
||||
return player;
|
||||
}
|
||||
|
||||
private class UpscaledLegacySkin : DefaultLegacySkin, ISkinSource
|
||||
{
|
||||
public UpscaledLegacySkin(IStorageResourceProvider resources)
|
||||
: base(resources)
|
||||
{
|
||||
}
|
||||
|
||||
public event Action? SourceChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
{
|
||||
var texture = base.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
|
||||
if (texture != null)
|
||||
texture.ScaleAdjust /= 8f;
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => this;
|
||||
public IEnumerable<ISkin> AllSources => new[] { this };
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@ -222,15 +223,9 @@ namespace osu.Game.Database
|
||||
throw new InvalidOperationException("Beatmap contains no hit objects!");
|
||||
|
||||
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
||||
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
||||
|
||||
sv1Simulator.Simulate(beatmap, playableBeatmap, mods);
|
||||
|
||||
return ConvertFromLegacyTotalScore(score, new DifficultyAttributes
|
||||
{
|
||||
LegacyAccuracyScore = sv1Simulator.AccuracyScore,
|
||||
LegacyComboScore = sv1Simulator.ComboScore,
|
||||
LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio
|
||||
});
|
||||
return ConvertFromLegacyTotalScore(score, attributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -241,20 +236,21 @@ namespace osu.Game.Database
|
||||
/// (<see cref="DifficultyAttributes.LegacyAccuracyScore"/>, <see cref="DifficultyAttributes.LegacyComboScore"/>, and <see cref="DifficultyAttributes.LegacyBonusScoreRatio"/>)
|
||||
/// for the beatmap which the score was set on.</param>
|
||||
/// <returns>The standardised total score.</returns>
|
||||
public static long ConvertFromLegacyTotalScore(ScoreInfo score, DifficultyAttributes attributes)
|
||||
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyScoreAttributes attributes)
|
||||
{
|
||||
if (!score.IsLegacyScore)
|
||||
return score.TotalScore;
|
||||
|
||||
Debug.Assert(score.LegacyTotalScore != null);
|
||||
|
||||
int maximumLegacyAccuracyScore = attributes.LegacyAccuracyScore;
|
||||
int maximumLegacyComboScore = attributes.LegacyComboScore;
|
||||
double maximumLegacyBonusRatio = attributes.LegacyBonusScoreRatio;
|
||||
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
|
||||
|
||||
int maximumLegacyAccuracyScore = attributes.AccuracyScore;
|
||||
long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * modMultiplier);
|
||||
double maximumLegacyBonusRatio = attributes.BonusScoreRatio;
|
||||
|
||||
// The part of total score that doesn't include bonus.
|
||||
int maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
|
||||
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
|
||||
|
||||
// The combo proportion is calculated as a proportion of maximumLegacyBaseScore.
|
||||
double comboProportion = Math.Min(1, (double)score.LegacyTotalScore / maximumLegacyBaseScore);
|
||||
|
@ -27,9 +27,6 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
protected const int ATTRIB_ID_FLASHLIGHT = 17;
|
||||
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
|
||||
protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21;
|
||||
protected const int ATTRIB_ID_LEGACY_ACCURACY_SCORE = 23;
|
||||
protected const int ATTRIB_ID_LEGACY_COMBO_SCORE = 25;
|
||||
protected const int ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO = 27;
|
||||
|
||||
/// <summary>
|
||||
/// The mods which were applied to the beatmap.
|
||||
@ -91,9 +88,6 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
public virtual IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
|
||||
{
|
||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||
yield return (ATTRIB_ID_LEGACY_ACCURACY_SCORE, LegacyAccuracyScore);
|
||||
yield return (ATTRIB_ID_LEGACY_COMBO_SCORE, LegacyComboScore);
|
||||
yield return (ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO, LegacyBonusScoreRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -104,11 +98,6 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||
{
|
||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||
|
||||
// Temporarily allow these attributes to not exist so as to not block releases of server-side components while these attributes aren't populated/used yet.
|
||||
LegacyAccuracyScore = (int)values.GetValueOrDefault(ATTRIB_ID_LEGACY_ACCURACY_SCORE);
|
||||
LegacyComboScore = (int)values.GetValueOrDefault(ATTRIB_ID_LEGACY_COMBO_SCORE);
|
||||
LegacyBonusScoreRatio = values.GetValueOrDefault(ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,6 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public abstract class DifficultyCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether legacy scoring values (ScoreV1) should be computed to populate the difficulty attributes
|
||||
/// <see cref="DifficultyAttributes.LegacyAccuracyScore"/>, <see cref="DifficultyAttributes.LegacyComboScore"/>,
|
||||
/// and <see cref="DifficultyAttributes.LegacyBonusScoreRatio"/>.
|
||||
/// </summary>
|
||||
public bool ComputeLegacyScoringValues;
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap for which difficulty will be calculated.
|
||||
/// </summary>
|
||||
|
@ -1,7 +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 osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
{
|
||||
|
@ -1,40 +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.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates attributes which are required to calculate old-style Score V1 scores.
|
||||
/// </summary>
|
||||
public interface ILegacyScoreSimulator
|
||||
{
|
||||
/// <summary>
|
||||
/// The accuracy portion of the legacy (ScoreV1) total score.
|
||||
/// </summary>
|
||||
int AccuracyScore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The combo-multiplied portion of the legacy (ScoreV1) total score.
|
||||
/// </summary>
|
||||
int ComboScore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A ratio of <c>new_bonus_score / old_bonus_score</c> for converting the bonus score of legacy scores to the new scoring.
|
||||
/// This is made up of all judgements that would be <see cref="HitResult.SmallBonus"/> or <see cref="HitResult.LargeBonus"/>.
|
||||
/// </summary>
|
||||
double BonusScoreRatio { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Performs the simulation, computing the maximum <see cref="AccuracyScore"/>, <see cref="ComboScore"/>,
|
||||
/// and <see cref="BonusScoreRatio"/> achievable for the given beatmap.
|
||||
/// </summary>
|
||||
/// <param name="workingBeatmap">The working beatmap.</param>
|
||||
/// <param name="playableBeatmap">A playable version of the beatmap for the ruleset.</param>
|
||||
/// <param name="mods">The applied mods.</param>
|
||||
void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods);
|
||||
}
|
||||
}
|
20
osu.Game/Rulesets/Scoring/Legacy/ILegacyScoreSimulator.cs
Normal file
20
osu.Game/Rulesets/Scoring/Legacy/ILegacyScoreSimulator.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates attributes which are required to calculate old-style Score V1 scores.
|
||||
/// </summary>
|
||||
public interface ILegacyScoreSimulator
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the simulation, computing the maximum scoring values achievable for the given beatmap.
|
||||
/// </summary>
|
||||
/// <param name="workingBeatmap">The working beatmap.</param>
|
||||
/// <param name="playableBeatmap">A playable version of the beatmap for the ruleset.</param>
|
||||
LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap);
|
||||
}
|
||||
}
|
23
osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs
Normal file
23
osu.Game/Rulesets/Scoring/Legacy/LegacyScoreAttributes.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring.Legacy
|
||||
{
|
||||
public struct LegacyScoreAttributes
|
||||
{
|
||||
/// <summary>
|
||||
/// The accuracy portion of the legacy (ScoreV1) total score.
|
||||
/// </summary>
|
||||
public int AccuracyScore;
|
||||
|
||||
/// <summary>
|
||||
/// The combo-multiplied portion of the legacy (ScoreV1) total score.
|
||||
/// </summary>
|
||||
public long ComboScore;
|
||||
|
||||
/// <summary>
|
||||
/// A ratio of standardised score to legacy score for the bonus part of total score.
|
||||
/// </summary>
|
||||
public double BonusScoreRatio;
|
||||
}
|
||||
}
|
@ -452,7 +452,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
|
||||
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID);
|
||||
|
||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
|
||||
UserModsSelectOverlay.Beatmap = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMods()
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Timing;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
@ -13,7 +14,7 @@ namespace osu.Game.Skinning
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should not be used to start an animation immediately at the current time.
|
||||
/// To do so, use <see cref="LegacySkinExtensions.GetAnimation(ISkin, string, WrapMode, WrapMode, bool, bool, bool, string, bool, double?)"/> with <code>startAtCurrentTime = true</code> instead.
|
||||
/// To do so, use <see cref="LegacySkinExtensions.GetAnimation(ISkin, string, WrapMode, WrapMode, bool, bool, bool, string, bool, double?, Vector2?)"/> with <code>startAtCurrentTime = true</code> instead.
|
||||
/// </remarks>
|
||||
[Cached]
|
||||
public interface IAnimationTimeReference
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osuTK;
|
||||
using static osu.Game.Skinning.SkinConfiguration;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
@ -18,16 +19,16 @@ namespace osu.Game.Skinning
|
||||
public static partial class LegacySkinExtensions
|
||||
{
|
||||
public static Drawable? GetAnimation(this ISkin? source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
|
||||
bool startAtCurrentTime = true, double? frameLength = null)
|
||||
=> source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength);
|
||||
bool startAtCurrentTime = true, double? frameLength = null, Vector2? maxSize = null)
|
||||
=> source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength, maxSize);
|
||||
|
||||
public static Drawable? GetAnimation(this ISkin? source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, bool looping, bool applyConfigFrameRate = false,
|
||||
string animationSeparator = "-", bool startAtCurrentTime = true, double? frameLength = null)
|
||||
string animationSeparator = "-", bool startAtCurrentTime = true, double? frameLength = null, Vector2? maxSize = null)
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
var textures = GetTextures(source, componentName, wrapModeS, wrapModeT, animatable, animationSeparator, out var retrievalSource);
|
||||
var textures = GetTextures(source, componentName, wrapModeS, wrapModeT, animatable, animationSeparator, maxSize, out var retrievalSource);
|
||||
|
||||
switch (textures.Length)
|
||||
{
|
||||
@ -53,7 +54,7 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture[] GetTextures(this ISkin? source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, string animationSeparator, out ISkin? retrievalSource)
|
||||
public static Texture[] GetTextures(this ISkin? source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, string animationSeparator, Vector2? maxSize, out ISkin? retrievalSource)
|
||||
{
|
||||
retrievalSource = null;
|
||||
|
||||
@ -80,6 +81,9 @@ namespace osu.Game.Skinning
|
||||
// if an animation was not allowed or not found, fall back to a sprite retrieval.
|
||||
var singleTexture = retrievalSource.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
|
||||
if (singleTexture != null && maxSize != null)
|
||||
singleTexture = singleTexture.WithMaximumSize(maxSize.Value);
|
||||
|
||||
return singleTexture != null
|
||||
? new[] { singleTexture }
|
||||
: Array.Empty<Texture>();
|
||||
@ -88,11 +92,14 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
for (int i = 0; true; i++)
|
||||
{
|
||||
Texture? texture;
|
||||
var texture = skin.GetTexture(getFrameName(i), wrapModeS, wrapModeT);
|
||||
|
||||
if ((texture = skin.GetTexture(getFrameName(i), wrapModeS, wrapModeT)) == null)
|
||||
if (texture == null)
|
||||
break;
|
||||
|
||||
if (maxSize != null)
|
||||
texture = texture.WithMaximumSize(maxSize.Value);
|
||||
|
||||
yield return texture;
|
||||
}
|
||||
}
|
||||
@ -100,6 +107,16 @@ namespace osu.Game.Skinning
|
||||
string getFrameName(int frameIndex) => $"{componentName}{animationSeparator}{frameIndex}";
|
||||
}
|
||||
|
||||
public static Texture WithMaximumSize(this Texture texture, Vector2 maxSize)
|
||||
{
|
||||
if (texture.DisplayWidth <= maxSize.X && texture.DisplayHeight <= maxSize.Y)
|
||||
return texture;
|
||||
|
||||
// use scale adjust property for downscaling the texture in order to meet the specified maximum dimensions.
|
||||
texture.ScaleAdjust *= Math.Max(texture.DisplayWidth / maxSize.X, texture.DisplayHeight / maxSize.Y);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static bool HasFont(this ISkin source, LegacyFont font)
|
||||
{
|
||||
return source.GetTexture($"{source.GetFontPrefix(font)}-0") != null;
|
||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Skinning
|
||||
public sealed partial class LegacySpriteText : OsuSpriteText
|
||||
{
|
||||
private readonly LegacyFont font;
|
||||
private readonly Vector2? maxSizePerGlyph;
|
||||
|
||||
private LegacyGlyphStore glyphStore = null!;
|
||||
|
||||
@ -20,9 +21,11 @@ namespace osu.Game.Skinning
|
||||
|
||||
protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' };
|
||||
|
||||
public LegacySpriteText(LegacyFont font)
|
||||
public LegacySpriteText(LegacyFont font, Vector2? maxSizePerGlyph = null)
|
||||
{
|
||||
this.font = font;
|
||||
this.maxSizePerGlyph = maxSizePerGlyph;
|
||||
|
||||
Shadow = false;
|
||||
UseFullGlyphHeight = false;
|
||||
}
|
||||
@ -33,7 +36,7 @@ namespace osu.Game.Skinning
|
||||
Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true);
|
||||
Spacing = new Vector2(-skin.GetFontOverlap(font), 0);
|
||||
|
||||
glyphStore = new LegacyGlyphStore(skin);
|
||||
glyphStore = new LegacyGlyphStore(skin, maxSizePerGlyph);
|
||||
}
|
||||
|
||||
protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);
|
||||
@ -41,10 +44,12 @@ namespace osu.Game.Skinning
|
||||
private class LegacyGlyphStore : ITexturedGlyphLookupStore
|
||||
{
|
||||
private readonly ISkin skin;
|
||||
private readonly Vector2? maxSize;
|
||||
|
||||
public LegacyGlyphStore(ISkin skin)
|
||||
public LegacyGlyphStore(ISkin skin, Vector2? maxSize)
|
||||
{
|
||||
this.skin = skin;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
public ITexturedCharacterGlyph? Get(string fontName, char character)
|
||||
@ -56,6 +61,9 @@ namespace osu.Game.Skinning
|
||||
if (texture == null)
|
||||
return null;
|
||||
|
||||
if (maxSize != null)
|
||||
texture = texture.WithMaximumSize(maxSize.Value);
|
||||
|
||||
return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust);
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,7 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
// This fallback is important for user skins which use SkinnableSprites.
|
||||
case SkinnableSprite.SpriteComponentLookup sprite:
|
||||
return this.GetAnimation(sprite.LookupName, false, false);
|
||||
return this.GetAnimation(sprite.LookupName, false, false, maxSize: sprite.MaxSize);
|
||||
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
|
||||
|
@ -34,8 +34,8 @@ namespace osu.Game.Skinning
|
||||
[Resolved]
|
||||
private ISkinSource source { get; set; } = null!;
|
||||
|
||||
public SkinnableSprite(string textureName, ConfineMode confineMode = ConfineMode.NoScaling)
|
||||
: base(new SpriteComponentLookup(textureName), confineMode)
|
||||
public SkinnableSprite(string textureName, Vector2? maxSize = null, ConfineMode confineMode = ConfineMode.NoScaling)
|
||||
: base(new SpriteComponentLookup(textureName, maxSize), confineMode)
|
||||
{
|
||||
SpriteName.Value = textureName;
|
||||
}
|
||||
@ -56,10 +56,14 @@ namespace osu.Game.Skinning
|
||||
|
||||
protected override Drawable CreateDefault(ISkinComponentLookup lookup)
|
||||
{
|
||||
var texture = textures.Get(((SpriteComponentLookup)lookup).LookupName);
|
||||
var spriteLookup = (SpriteComponentLookup)lookup;
|
||||
var texture = textures.Get(spriteLookup.LookupName);
|
||||
|
||||
if (texture == null)
|
||||
return new SpriteNotFound(((SpriteComponentLookup)lookup).LookupName);
|
||||
return new SpriteNotFound(spriteLookup.LookupName);
|
||||
|
||||
if (spriteLookup.MaxSize != null)
|
||||
texture = texture.WithMaximumSize(spriteLookup.MaxSize.Value);
|
||||
|
||||
return new Sprite { Texture = texture };
|
||||
}
|
||||
@ -69,10 +73,12 @@ namespace osu.Game.Skinning
|
||||
internal class SpriteComponentLookup : ISkinComponentLookup
|
||||
{
|
||||
public string LookupName { get; set; }
|
||||
public Vector2? MaxSize { get; set; }
|
||||
|
||||
public SpriteComponentLookup(string textureName)
|
||||
public SpriteComponentLookup(string textureName, Vector2? maxSize = null)
|
||||
{
|
||||
LookupName = textureName;
|
||||
MaxSize = maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public partial class DrawableStoryboard : Container<DrawableStoryboardLayer>
|
||||
{
|
||||
[Cached]
|
||||
[Cached(typeof(Storyboard))]
|
||||
public Storyboard Storyboard { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -94,25 +94,19 @@ namespace osu.Game.Storyboards.Drawables
|
||||
[Resolved]
|
||||
private IBeatSyncProvider beatSyncProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private TextureStore textureStore { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textureStore, Storyboard storyboard)
|
||||
private void load(Storyboard storyboard)
|
||||
{
|
||||
int frameIndex = 0;
|
||||
|
||||
Texture frameTexture = textureStore.Get(getFramePath(frameIndex));
|
||||
|
||||
if (frameTexture != null)
|
||||
if (storyboard.UseSkinSprites)
|
||||
{
|
||||
// sourcing from storyboard.
|
||||
for (frameIndex = 0; frameIndex < Animation.FrameCount; frameIndex++)
|
||||
AddFrame(textureStore.Get(getFramePath(frameIndex)), Animation.FrameDelay);
|
||||
}
|
||||
else if (storyboard.UseSkinSprites)
|
||||
{
|
||||
// fallback to skin if required.
|
||||
skin.SourceChanged += skinSourceChanged;
|
||||
skinSourceChanged();
|
||||
}
|
||||
else
|
||||
addFramesFromStoryboardSource();
|
||||
|
||||
Animation.ApplyTransforms(this);
|
||||
}
|
||||
@ -135,11 +129,28 @@ namespace osu.Game.Storyboards.Drawables
|
||||
|
||||
// When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored
|
||||
// and resources are retrieved until the end of the animation.
|
||||
foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, out _))
|
||||
AddFrame(texture, Animation.FrameDelay);
|
||||
var skinTextures = skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, null, out _);
|
||||
|
||||
if (skinTextures.Length > 0)
|
||||
{
|
||||
foreach (var texture in skinTextures)
|
||||
AddFrame(texture, Animation.FrameDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
addFramesFromStoryboardSource();
|
||||
}
|
||||
}
|
||||
|
||||
private string getFramePath(int i) => Animation.Path.Replace(".", $"{i}.");
|
||||
private void addFramesFromStoryboardSource()
|
||||
{
|
||||
int frameIndex;
|
||||
// sourcing from storyboard.
|
||||
for (frameIndex = 0; frameIndex < Animation.FrameCount; frameIndex++)
|
||||
AddFrame(textureStore.Get(getFramePath(frameIndex)), Animation.FrameDelay);
|
||||
|
||||
string getFramePath(int i) => Animation.Path.Replace(".", $"{i}.");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
@ -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.Collections.Generic;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -30,10 +31,12 @@ namespace osu.Game.Storyboards.Drawables
|
||||
InternalChild = ElementContainer = new LayerElementContainer(layer);
|
||||
}
|
||||
|
||||
protected partial class LayerElementContainer : LifetimeManagementContainer
|
||||
public partial class LayerElementContainer : LifetimeManagementContainer
|
||||
{
|
||||
private readonly StoryboardLayer storyboardLayer;
|
||||
|
||||
public IEnumerable<Drawable> Elements => InternalChildren;
|
||||
|
||||
public LayerElementContainer(StoryboardLayer layer)
|
||||
{
|
||||
storyboardLayer = layer;
|
||||
|
@ -74,6 +74,12 @@ namespace osu.Game.Storyboards.Drawables
|
||||
public override bool IsPresent
|
||||
=> !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent;
|
||||
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private TextureStore textureStore { get; set; } = null!;
|
||||
|
||||
public DrawableStoryboardSprite(StoryboardSprite sprite)
|
||||
{
|
||||
Sprite = sprite;
|
||||
@ -84,24 +90,28 @@ namespace osu.Game.Storyboards.Drawables
|
||||
LifetimeEnd = sprite.EndTimeForDisplay;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textureStore, Storyboard storyboard)
|
||||
private void load(Storyboard storyboard)
|
||||
{
|
||||
Texture = textureStore.Get(Sprite.Path);
|
||||
|
||||
if (Texture == null && storyboard.UseSkinSprites)
|
||||
if (storyboard.UseSkinSprites)
|
||||
{
|
||||
skin.SourceChanged += skinSourceChanged;
|
||||
skinSourceChanged();
|
||||
}
|
||||
else
|
||||
Texture = textureStore.Get(Sprite.Path);
|
||||
|
||||
Sprite.ApplyTransforms(this);
|
||||
}
|
||||
|
||||
private void skinSourceChanged() => Texture = skin.GetTexture(Sprite.Path);
|
||||
private void skinSourceChanged()
|
||||
{
|
||||
Texture = skin.GetTexture(Sprite.Path) ?? textureStore.Get(Sprite.Path);
|
||||
|
||||
// Setting texture will only update the size if it's zero.
|
||||
// So let's force an explicit update.
|
||||
Size = new Vector2(Texture?.DisplayWidth ?? 0, Texture?.DisplayHeight ?? 0);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Storyboards
|
||||
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the storyboard can fall back to skin sprites in case no matching storyboard sprites are found.
|
||||
/// Whether the storyboard should prefer textures from the current skin before using local storyboard textures.
|
||||
/// </summary>
|
||||
public bool UseSkinSprites { get; set; }
|
||||
|
||||
@ -86,7 +86,7 @@ namespace osu.Game.Storyboards
|
||||
}
|
||||
}
|
||||
|
||||
public DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null) =>
|
||||
public virtual DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null) =>
|
||||
new DrawableStoryboard(this, mods);
|
||||
|
||||
private static readonly string[] image_extensions = { @".png", @".jpg" };
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Storyboards
|
||||
|
||||
public double StartTime { get; }
|
||||
|
||||
public StoryboardVideo(string path, int offset)
|
||||
public StoryboardVideo(string path, double offset)
|
||||
{
|
||||
Path = path;
|
||||
StartTime = offset;
|
||||
|
Loading…
Reference in New Issue
Block a user