mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 11:23:00 +08:00
Merge branch 'master' into health-animates-in-intro
This commit is contained in:
commit
bd71403309
@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
X = xPositionData?.X ?? 0,
|
X = xPositionData?.X ?? 0,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
|
|
||||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
@ -25,6 +25,7 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
@ -25,12 +25,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
public override int Version => 20220701;
|
public override int Version => 20220701;
|
||||||
|
|
||||||
private readonly IWorkingBeatmap workingBeatmap;
|
|
||||||
|
|
||||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
workingBeatmap = beatmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
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)),
|
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;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,33 +2,26 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
{
|
{
|
||||||
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
|
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 legacyBonusScore;
|
||||||
private int modernBonusScore;
|
private int standardisedBonusScore;
|
||||||
private int combo;
|
private int combo;
|
||||||
|
|
||||||
private double scoreMultiplier;
|
private double scoreMultiplier;
|
||||||
|
|
||||||
public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
|
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
||||||
|
|
||||||
@ -70,13 +63,19 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
+ baseBeatmap.Difficulty.CircleSize
|
+ baseBeatmap.Difficulty.CircleSize
|
||||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
+ 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)
|
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 increaseCombo = true;
|
||||||
bool addScoreComboMultiplier = false;
|
bool addScoreComboMultiplier = false;
|
||||||
@ -112,28 +111,28 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
case JuiceStream:
|
case JuiceStream:
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
foreach (var nested in hitObject.NestedHitObjects)
|
||||||
simulateHit(nested);
|
simulateHit(nested, ref attributes);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case BananaShower:
|
case BananaShower:
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
foreach (var nested in hitObject.NestedHitObjects)
|
||||||
simulateHit(nested);
|
simulateHit(nested, ref attributes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addScoreComboMultiplier)
|
if (addScoreComboMultiplier)
|
||||||
{
|
{
|
||||||
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
|
// 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)
|
if (isBonus)
|
||||||
{
|
{
|
||||||
legacyBonusScore += scoreIncrease;
|
legacyBonusScore += scoreIncrease;
|
||||||
modernBonusScore += Judgement.ToNumericResult(bonusResult);
|
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
AccuracyScore += scoreIncrease;
|
attributes.AccuracyScore += scoreIncrease;
|
||||||
|
|
||||||
if (increaseCombo)
|
if (increaseCombo)
|
||||||
combo++;
|
combo++;
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
int nodeIndex = 0;
|
int nodeIndex = 0;
|
||||||
SliderEventDescriptor? lastEvent = null;
|
SliderEventDescriptor? lastEvent = null;
|
||||||
|
|
||||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken))
|
||||||
{
|
{
|
||||||
// generate tiny droplets since the last point
|
// generate tiny droplets since the last point
|
||||||
if (lastEvent != null)
|
if (lastEvent != null)
|
||||||
@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this also includes LegacyLastTick and this is used for TinyDroplet generation above.
|
// this also includes LastTick and this is used for TinyDroplet generation above.
|
||||||
// this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied.
|
// this means that the final segment of TinyDroplets are increasingly mistimed where LastTick is being applied.
|
||||||
lastEvent = e;
|
lastEvent = e;
|
||||||
|
|
||||||
switch (e.Type)
|
switch (e.Type)
|
||||||
@ -162,7 +162,5 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public double Distance => Path.Distance;
|
public double Distance => Path.Distance;
|
||||||
|
|
||||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||||
|
|
||||||
public double? LegacyLastTickOffset { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,21 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 banana_max_size = new Vector2(128);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Texture? texture = Skin.GetTexture("fruit-bananas");
|
Texture? texture = Skin.GetTexture("fruit-bananas")?.WithMaximumSize(banana_max_size);
|
||||||
Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay");
|
Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay")?.WithMaximumSize(banana_max_size);
|
||||||
|
|
||||||
SetTexture(texture, overlayTexture);
|
SetTexture(texture, overlayTexture);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 droplet_max_size = new Vector2(82, 103);
|
||||||
|
|
||||||
public LegacyDropletPiece()
|
public LegacyDropletPiece()
|
||||||
{
|
{
|
||||||
Scale = new Vector2(0.8f);
|
Scale = new Vector2(0.8f);
|
||||||
@ -17,8 +20,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Texture? texture = Skin.GetTexture("fruit-drop");
|
Texture? texture = Skin.GetTexture("fruit-drop")?.WithMaximumSize(droplet_max_size);
|
||||||
Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay");
|
Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay")?.WithMaximumSize(droplet_max_size);
|
||||||
|
|
||||||
SetTexture(texture, overlayTexture);
|
SetTexture(texture, overlayTexture);
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 fruit_max_size = new Vector2(128);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -22,21 +26,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
switch (visualRepresentation)
|
switch (visualRepresentation)
|
||||||
{
|
{
|
||||||
case FruitVisualRepresentation.Pear:
|
case FruitVisualRepresentation.Pear:
|
||||||
SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay"));
|
setTextures("pear");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FruitVisualRepresentation.Grape:
|
case FruitVisualRepresentation.Grape:
|
||||||
SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay"));
|
setTextures("grapes");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FruitVisualRepresentation.Pineapple:
|
case FruitVisualRepresentation.Pineapple:
|
||||||
SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay"));
|
setTextures("apple");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FruitVisualRepresentation.Raspberry:
|
case FruitVisualRepresentation.Raspberry:
|
||||||
SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay"));
|
setTextures("orange");
|
||||||
break;
|
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;
|
public override int Version => 20220902;
|
||||||
|
|
||||||
private readonly IWorkingBeatmap workingBeatmap;
|
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
workingBeatmap = beatmap;
|
|
||||||
|
|
||||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
|
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
|
||||||
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
|
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
|
||||||
}
|
}
|
||||||
@ -60,15 +56,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject),
|
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;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||||
{
|
{
|
||||||
internal class ManiaLegacyScoreSimulator : ILegacyScoreSimulator
|
internal class ManiaLegacyScoreSimulator : ILegacyScoreSimulator
|
||||||
{
|
{
|
||||||
public int AccuracyScore => 0;
|
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||||
public int ComboScore { get; private set; }
|
|
||||||
public double BonusScoreRatio => 0;
|
|
||||||
|
|
||||||
public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
|
|
||||||
{
|
{
|
||||||
double multiplier = mods.Where(m => m is not (ModHidden or ModHardRock or ModDoubleTime or ModFlashlight or ManiaModFadeIn))
|
return new LegacyScoreAttributes { ComboScore = 1000000 };
|
||||||
.Select(m => m.ScoreMultiplier)
|
|
||||||
.Aggregate(1.0, (c, n) => c * n);
|
|
||||||
|
|
||||||
ComboScore = (int)(1000000 * multiplier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ using osu.Game.Rulesets.Mania.UI;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
},
|
},
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
|
Keywords = new[] { "color" },
|
||||||
LabelText = RulesetSettingsStrings.TimingBasedColouring,
|
LabelText = RulesetSettingsStrings.TimingBasedColouring,
|
||||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
@ -25,33 +26,42 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
// Avoid flickering due to no anti-aliasing of boxes by default.
|
||||||
|
var edgeSmoothness = new Vector2(0.3f);
|
||||||
|
|
||||||
AddInternal(mainLine = new Box
|
AddInternal(mainLine = new Box
|
||||||
{
|
{
|
||||||
Name = "Bar line",
|
Name = "Bar line",
|
||||||
|
EdgeSmoothness = edgeSmoothness,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
});
|
});
|
||||||
|
|
||||||
Vector2 size = new Vector2(22, 6);
|
const float major_extension = 10;
|
||||||
const float line_offset = 4;
|
|
||||||
|
|
||||||
AddInternal(leftAnchor = new Circle
|
AddInternal(leftAnchor = new Box
|
||||||
{
|
{
|
||||||
Name = "Left anchor",
|
Name = "Left anchor",
|
||||||
|
EdgeSmoothness = edgeSmoothness,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Size = size,
|
Width = major_extension,
|
||||||
X = -line_offset,
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Colour = ColourInfo.GradientHorizontal(Colour4.Transparent, Colour4.White),
|
||||||
});
|
});
|
||||||
|
|
||||||
AddInternal(rightAnchor = new Circle
|
AddInternal(rightAnchor = new Box
|
||||||
{
|
{
|
||||||
Name = "Right anchor",
|
Name = "Right anchor",
|
||||||
|
EdgeSmoothness = edgeSmoothness,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Size = size,
|
Width = major_extension,
|
||||||
X = line_offset,
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Colour = ColourInfo.GradientHorizontal(Colour4.White, Colour4.Transparent),
|
||||||
});
|
});
|
||||||
|
|
||||||
major = ((DrawableBarLine)drawableHitObject).Major.GetBoundCopy();
|
major = ((DrawableBarLine)drawableHitObject).Major.GetBoundCopy();
|
||||||
@ -66,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
|
|||||||
private void updateMajor(ValueChangedEvent<bool> major)
|
private void updateMajor(ValueChangedEvent<bool> major)
|
||||||
{
|
{
|
||||||
mainLine.Alpha = major.NewValue ? 0.5f : 0.2f;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
slider = new Slider
|
slider = new Slider
|
||||||
{
|
{
|
||||||
Position = new Vector2(0, 50),
|
Position = new Vector2(0, 50),
|
||||||
LegacyLastTickOffset = 36, // This is necessary for undo to retain the sample control point
|
|
||||||
Path = new SliderPath(new[]
|
Path = new SliderPath(new[]
|
||||||
{
|
{
|
||||||
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||||
|
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/reversearrow.png
Normal file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/reversearrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
|
AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
|
||||||
AddUntilStep("Pitch increases", () => 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)]
|
[TestCase(false)]
|
||||||
@ -64,6 +65,39 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddUntilStep("Short spinner implicitly completes", () => drawableSpinner.Progress == 1);
|
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)
|
private Drawable testSingle(float circleSize, bool auto = false, double length = 3000)
|
||||||
{
|
{
|
||||||
const double delay = 2000;
|
const double delay = 2000;
|
||||||
|
@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
Position = positionData?.Position ?? Vector2.Zero,
|
Position = positionData?.Position ?? Vector2.Zero,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
|
||||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
|
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
|
||||||
|
@ -26,12 +26,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
public override int Version => 20220902;
|
public override int Version => 20220902;
|
||||||
|
|
||||||
private readonly IWorkingBeatmap workingBeatmap;
|
|
||||||
|
|
||||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
workingBeatmap = beatmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||||
@ -109,15 +106,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
SpinnerCount = spinnerCount,
|
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;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,37 +2,27 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||||
{
|
{
|
||||||
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
|
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 legacyBonusScore;
|
||||||
private int modernBonusScore;
|
private int standardisedBonusScore;
|
||||||
private int combo;
|
private int combo;
|
||||||
|
|
||||||
private double scoreMultiplier;
|
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;
|
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
||||||
|
|
||||||
int countNormal = 0;
|
int countNormal = 0;
|
||||||
@ -73,13 +63,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
+ baseBeatmap.Difficulty.CircleSize
|
+ baseBeatmap.Difficulty.CircleSize
|
||||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
+ 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)
|
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 increaseCombo = true;
|
||||||
bool addScoreComboMultiplier = false;
|
bool addScoreComboMultiplier = false;
|
||||||
@ -122,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
case Slider:
|
case Slider:
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
foreach (var nested in hitObject.NestedHitObjects)
|
||||||
simulateHit(nested);
|
simulateHit(nested, ref attributes);
|
||||||
|
|
||||||
scoreIncrease = 300;
|
scoreIncrease = 300;
|
||||||
increaseCombo = false;
|
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.
|
// The spinner object applies a lenience because gameplay mechanics differ from osu-stable.
|
||||||
// We'll redo the calculations to match osu-stable here...
|
// We'll redo the calculations to match osu-stable here...
|
||||||
const double maximum_rotations_per_second = 477.0 / 60;
|
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;
|
double secondsDuration = spinner.Duration / 1000;
|
||||||
|
|
||||||
// The total amount of half spins possible for the entire spinner.
|
// The total amount of half spins possible for the entire spinner.
|
||||||
int totalHalfSpinsPossible = (int)(secondsDuration * maximum_rotations_per_second * 2);
|
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).
|
// 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.
|
// To be able to receive bonus points, the spinner must be rotated another 1.5 times.
|
||||||
int halfSpinsRequiredBeforeBonus = halfSpinsRequiredForCompletion + 3;
|
int halfSpinsRequiredBeforeBonus = halfSpinsRequiredForCompletion + 3;
|
||||||
|
|
||||||
for (int i = 0; i <= totalHalfSpinsPossible; i++)
|
for (int i = 0; i <= totalHalfSpinsPossible; i++)
|
||||||
{
|
{
|
||||||
if (i > halfSpinsRequiredBeforeBonus && (i - halfSpinsRequiredBeforeBonus) % 2 == 0)
|
if (i > halfSpinsRequiredBeforeBonus && (i - halfSpinsRequiredBeforeBonus) % 2 == 0)
|
||||||
simulateHit(new SpinnerBonusTick());
|
simulateHit(new SpinnerBonusTick(), ref attributes);
|
||||||
else if (i > 1 && i % 2 == 0)
|
else if (i > 1 && i % 2 == 0)
|
||||||
simulateHit(new SpinnerTick());
|
simulateHit(new SpinnerTick(), ref attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
scoreIncrease = 300;
|
scoreIncrease = 300;
|
||||||
@ -159,16 +160,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (addScoreComboMultiplier)
|
if (addScoreComboMultiplier)
|
||||||
{
|
{
|
||||||
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
|
// 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)
|
if (isBonus)
|
||||||
{
|
{
|
||||||
legacyBonusScore += scoreIncrease;
|
legacyBonusScore += scoreIncrease;
|
||||||
modernBonusScore += Judgement.ToNumericResult(bonusResult);
|
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
AccuracyScore += scoreIncrease;
|
attributes.AccuracyScore += scoreIncrease;
|
||||||
|
|
||||||
if (increaseCombo)
|
if (increaseCombo)
|
||||||
combo++;
|
combo++;
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
InternalChild = content = new Container
|
InternalChild = content = new Container
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
CornerRadius = Size.X / 2;
|
CornerRadius = Size.X / 2;
|
||||||
CornerExponent = 2;
|
CornerExponent = 2;
|
||||||
|
@ -315,7 +315,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
StartTime = HitObject.StartTime,
|
StartTime = HitObject.StartTime,
|
||||||
Position = HitObject.Position + splitControlPoints[0].Position,
|
Position = HitObject.Position + splitControlPoints[0].Position,
|
||||||
NewCombo = HitObject.NewCombo,
|
NewCombo = HitObject.NewCombo,
|
||||||
LegacyLastTickOffset = HitObject.LegacyLastTickOffset,
|
|
||||||
Samples = HitObject.Samples.Select(s => s.With()).ToList(),
|
Samples = HitObject.Samples.Select(s => s.With()).ToList(),
|
||||||
RepeatCount = HitObject.RepeatCount,
|
RepeatCount = HitObject.RepeatCount,
|
||||||
NodeSamples = HitObject.NodeSamples.Select(n => (IList<HitSampleInfo>)n.Select(s => s.With()).ToList()).ToList(),
|
NodeSamples = HitObject.NodeSamples.Select(n => (IList<HitSampleInfo>)n.Select(s => s.With()).ToList()).ToList(),
|
||||||
|
@ -61,10 +61,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
|
OsuHitObject firstObject = drawableRuleset.Beatmap.HitObjects.First();
|
||||||
|
|
||||||
// Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen
|
// Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen
|
||||||
// Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size
|
// Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size
|
||||||
bubbleSize = (float)(drawableRuleset.Beatmap.HitObjects.OfType<HitCircle>().First().Radius * 1.90f);
|
bubbleSize = (float)firstObject.Radius * 1.90f;
|
||||||
bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType<HitCircle>().First().TimePreempt * 2;
|
bubbleFade = firstObject.TimePreempt * 2;
|
||||||
|
|
||||||
// We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering)
|
// We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering)
|
||||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
|
@ -96,14 +96,13 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
Position = original.Position;
|
Position = original.Position;
|
||||||
NewCombo = original.NewCombo;
|
NewCombo = original.NewCombo;
|
||||||
ComboOffset = original.ComboOffset;
|
ComboOffset = original.ComboOffset;
|
||||||
LegacyLastTickOffset = original.LegacyLastTickOffset;
|
|
||||||
TickDistanceMultiplier = original.TickDistanceMultiplier;
|
TickDistanceMultiplier = original.TickDistanceMultiplier;
|
||||||
SliderVelocityMultiplier = original.SliderVelocityMultiplier;
|
SliderVelocityMultiplier = original.SliderVelocityMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken);
|
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken);
|
||||||
|
|
||||||
foreach (var e in sliderEvents)
|
foreach (var e in sliderEvents)
|
||||||
{
|
{
|
||||||
@ -130,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderEventType.LegacyLastTick:
|
case SliderEventType.LastTick:
|
||||||
AddNested(TailCircle = new StrictTrackingSliderTailCircle(this)
|
AddNested(TailCircle = new StrictTrackingSliderTailCircle(this)
|
||||||
{
|
{
|
||||||
RepeatIndex = e.SpanIndex,
|
RepeatIndex = e.SpanIndex,
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
d.HitObjectApplied += _ =>
|
d.HitObjectApplied += _ =>
|
||||||
{
|
{
|
||||||
// slider tails are a painful edge case, as their start time is offset 36ms back (see `LegacyLastTick`).
|
// slider tails are a painful edge case, as their start time is offset 36ms back (see `LastTick`).
|
||||||
// to work around this, look up the slider tail's parenting slider's end time instead to ensure proper snap.
|
// to work around this, look up the slider tail's parenting slider's end time instead to ensure proper snap.
|
||||||
double snapTime = d is DrawableSliderTail tail
|
double snapTime = d is DrawableSliderTail tail
|
||||||
? tail.Slider.GetEndTime()
|
? tail.Slider.GetEndTime()
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -34,6 +35,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public HitReceptor HitArea { get; private set; }
|
public HitReceptor HitArea { get; private set; }
|
||||||
public SkinnableDrawable CirclePiece { get; private set; }
|
public SkinnableDrawable CirclePiece { get; private set; }
|
||||||
|
|
||||||
|
protected override IEnumerable<Drawable> DimmablePieces => new[]
|
||||||
|
{
|
||||||
|
CirclePiece,
|
||||||
|
};
|
||||||
|
|
||||||
Drawable IHasApproachCircle.ApproachCircle => ApproachCircle;
|
Drawable IHasApproachCircle.ApproachCircle => ApproachCircle;
|
||||||
|
|
||||||
private Container scaleContainer;
|
private Container scaleContainer;
|
||||||
@ -191,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
|
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
|
||||||
|
|
||||||
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
|
ApproachCircle.FadeTo(0.9f, Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
|
||||||
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
|
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
|
||||||
ApproachCircle.Expire(true);
|
ApproachCircle.Expire(true);
|
||||||
}
|
}
|
||||||
@ -244,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public HitReceptor()
|
public HitReceptor()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -71,20 +73,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual IEnumerable<Drawable> DimmablePieces => Enumerable.Empty<Drawable>();
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
// Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
|
foreach (var piece in DimmablePieces)
|
||||||
if (ParentHitObject == null)
|
|
||||||
{
|
{
|
||||||
// Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
|
piece.FadeColour(new Color4(195, 195, 195, 255));
|
||||||
// For now this is applied across all skins, and matches stable.
|
using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
||||||
// For simplicity, dim colour is applied to the DrawableHitObject itself.
|
piece.FadeColour(Color4.White, 100);
|
||||||
// We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
|
|
||||||
this.FadeColour(new Color4(195, 195, 195, 255));
|
|
||||||
using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
|
||||||
this.FadeColour(Color4.White, 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -35,6 +36,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private ShakeContainer shakeContainer;
|
private ShakeContainer shakeContainer;
|
||||||
|
|
||||||
|
protected override IEnumerable<Drawable> DimmablePieces => new Drawable[]
|
||||||
|
{
|
||||||
|
HeadCircle,
|
||||||
|
TailCircle,
|
||||||
|
Body,
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A target container which can be used to add top level elements to the slider's display.
|
/// A target container which can be used to add top level elements to the slider's display.
|
||||||
/// Intended to be used for proxy purposes only.
|
/// Intended to be used for proxy purposes only.
|
||||||
@ -288,7 +296,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public override void PlaySamples()
|
public override void PlaySamples()
|
||||||
{
|
{
|
||||||
// rather than doing it this way, we should probably attach the sample to the tail circle.
|
// rather than doing it this way, we should probably attach the sample to the tail circle.
|
||||||
// this can only be done after we stop using LegacyLastTick.
|
// this can only be done if we stop using LastTick.
|
||||||
if (!TailCircle.SamplePlaysOnlyOnHit || TailCircle.IsHit)
|
if (!TailCircle.SamplePlaysOnlyOnHit || TailCircle.IsHit)
|
||||||
base.PlaySamples();
|
base.PlaySamples();
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public SkinnableDrawable CirclePiece { get; private set; }
|
public SkinnableDrawable CirclePiece { get; private set; }
|
||||||
|
|
||||||
public ReverseArrowPiece Arrow { get; private set; }
|
public SkinnableDrawable Arrow { get; private set; }
|
||||||
|
|
||||||
private Drawable scaleContainer;
|
private Drawable scaleContainer;
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
AddInternal(scaleContainer = new Container
|
AddInternal(scaleContainer = new Container
|
||||||
{
|
{
|
||||||
@ -65,7 +65,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
Arrow = new ReverseArrowPiece(),
|
Arrow = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow), _ => new DefaultReverseArrow())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
||||||
|
@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float OBJECT_RADIUS = 64;
|
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>
|
/// <summary>
|
||||||
/// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track).
|
/// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double? LegacyLastTickOffset { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit
|
/// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit
|
||||||
/// with as few movements as possible. This is set and used by difficulty calculation.
|
/// with as few movements as possible. This is set and used by difficulty calculation.
|
||||||
@ -179,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken);
|
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken);
|
||||||
|
|
||||||
foreach (var e in sliderEvents)
|
foreach (var e in sliderEvents)
|
||||||
{
|
{
|
||||||
@ -206,10 +204,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderEventType.LegacyLastTick:
|
case SliderEventType.LastTick:
|
||||||
// we need to use the LegacyLastTick here for compatibility reasons (difficulty).
|
// Of note, we are directly mapping LastTick (instead of `SliderEventType.Tail`) to SliderTailCircle.
|
||||||
// it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay.
|
// It is required as difficulty calculation and gameplay relies on reading this value.
|
||||||
// if this is to change, we should revisit this.
|
// (although it is displayed in classic skins, which may be a concern).
|
||||||
|
// If this is to change, we should revisit this.
|
||||||
AddNested(TailCircle = new SliderTailCircle(this)
|
AddNested(TailCircle = new SliderTailCircle(this)
|
||||||
{
|
{
|
||||||
RepeatIndex = e.SpanIndex,
|
RepeatIndex = e.SpanIndex,
|
||||||
@ -264,7 +263,9 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
if (HeadCircle != null)
|
if (HeadCircle != null)
|
||||||
HeadCircle.Samples = this.GetNodeSamples(0);
|
HeadCircle.Samples = this.GetNodeSamples(0);
|
||||||
|
|
||||||
// The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to.
|
// The samples should be attached to the slider tail, however this can only be done if LastTick is removed otherwise they would play earlier than they're intended to.
|
||||||
|
// (see mapping logic in `CreateNestedHitObjects` above)
|
||||||
|
//
|
||||||
// For now, the samples are played by the slider itself at the correct end time.
|
// For now, the samples are played by the slider itself at the correct end time.
|
||||||
TailSamples = this.GetNodeSamples(repeatCount + 1);
|
TailSamples = this.GetNodeSamples(repeatCount + 1);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Note that this should not be used for timing correctness.
|
/// Note that this should not be used for timing correctness.
|
||||||
/// See <see cref="SliderEventType.LegacyLastTick"/> usage in <see cref="Slider"/> for more information.
|
/// See <see cref="SliderEventType.LastTick"/> usage in <see cref="Slider"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SliderTailCircle : SliderEndCircle
|
public class SliderTailCircle : SliderEndCircle
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,16 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
public class Spinner : OsuHitObject, IHasDuration
|
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
|
public double EndTime
|
||||||
{
|
{
|
||||||
get => StartTime + Duration;
|
get => StartTime + Duration;
|
||||||
@ -52,13 +62,19 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
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 secondsDuration = Duration / 1000;
|
||||||
double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 1.5, 2.5, 3.75);
|
|
||||||
|
|
||||||
SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond);
|
// Allow a 0.1ms floating point precision error in the calculation of the duration.
|
||||||
MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration) - bonus_spins_gap;
|
const double duration_error = 0.0001;
|
||||||
|
|
||||||
|
SpinsRequired = (int)(minRps * secondsDuration + duration_error);
|
||||||
|
MaximumBonusSpins = Math.Max(0, (int)(maxRps * secondsDuration + duration_error) - SpinsRequired - bonus_spins_gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
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.Osu.UI;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
|
|
||||||
private Bindable<bool> configHitLighting = null!;
|
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]
|
[Resolved]
|
||||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -17,38 +19,92 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
{
|
{
|
||||||
public partial class ArgonReverseArrow : CompositeDrawable
|
public partial class ArgonReverseArrow : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
private Bindable<Color4> accentColour = null!;
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
private SpriteIcon icon = null!;
|
private SpriteIcon icon = null!;
|
||||||
|
private Container main = null!;
|
||||||
|
private Sprite side = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject hitObject)
|
private void load(TextureStore textures)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Circle
|
main = new Container
|
||||||
{
|
{
|
||||||
Size = new Vector2(40, 20),
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.White,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Size = new Vector2(40, 20),
|
||||||
|
Colour = Color4.White,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.AngleDoubleRight,
|
||||||
|
Size = new Vector2(16),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
icon = new SpriteIcon
|
side = new Sprite
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.AngleDoubleRight,
|
|
||||||
Size = new Vector2(16),
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
Texture = textures.Get("Gameplay/osu/repeat-edge-piece"),
|
||||||
|
Size = new Vector2(ArgonMainCirclePiece.OUTER_GRADIENT_SIZE),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
accentColour = hitObject.AccentColour.GetBoundCopy();
|
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
||||||
|
|
||||||
|
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
const float move_distance = -12;
|
||||||
|
const double move_out_duration = 35;
|
||||||
|
const double move_in_duration = 250;
|
||||||
|
const double total = 300;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Idle:
|
||||||
|
main.ScaleTo(1.3f, move_out_duration, Easing.Out)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(1f, move_in_duration, Easing.Out)
|
||||||
|
.Loop(total - (move_in_duration + move_out_duration));
|
||||||
|
side
|
||||||
|
.MoveToX(move_distance, move_out_duration, Easing.Out)
|
||||||
|
.Then()
|
||||||
|
.MoveToX(0, move_in_duration, Easing.Out)
|
||||||
|
.Loop(total - (move_in_duration + move_out_duration));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject.IsNotNull())
|
||||||
|
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
public CirclePiece()
|
public CirclePiece()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
CornerRadius = Size.X / 2;
|
CornerRadius = Size.X / 2;
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
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 osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
|
{
|
||||||
|
public partial class DefaultReverseArrow : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
|
public DefaultReverseArrow()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
|
InternalChild = new SpriteIcon
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(0.35f),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
const double move_out_duration = 35;
|
||||||
|
const double move_in_duration = 250;
|
||||||
|
const double total = 300;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Idle:
|
||||||
|
InternalChild.ScaleTo(1.3f, move_out_duration, Easing.Out)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(1f, move_in_duration, Easing.Out)
|
||||||
|
.Loop(total - (move_in_duration + move_out_duration));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject.IsNotNull())
|
||||||
|
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
public ExplodePiece()
|
public ExplodePiece()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public FlashPiece()
|
public FlashPiece()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
public MainCirclePiece()
|
public MainCirclePiece()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
@ -1,51 +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 osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|
||||||
{
|
|
||||||
public partial class ReverseArrowPiece : BeatSyncedContainer
|
|
||||||
{
|
|
||||||
[Resolved]
|
|
||||||
private DrawableHitObject drawableRepeat { get; set; } = null!;
|
|
||||||
|
|
||||||
public ReverseArrowPiece()
|
|
||||||
{
|
|
||||||
Divisor = 2;
|
|
||||||
MinimumBeatLength = 200;
|
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
|
||||||
|
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Icon = FontAwesome.Solid.ChevronRight,
|
|
||||||
Size = new Vector2(0.35f)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
|
||||||
{
|
|
||||||
if (!drawableRepeat.IsHit)
|
|
||||||
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public RingPiece(float thickness = 9)
|
public RingPiece(float thickness = 9)
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
@ -5,12 +5,14 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
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
|
public partial class LegacyApproachCircle : SkinnableSprite
|
||||||
{
|
{
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
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!;
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
public LegacyApproachCircle()
|
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;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
this.priorityLookupPrefix = priorityLookupPrefix;
|
this.priorityLookupPrefix = priorityLookupPrefix;
|
||||||
this.hasNumber = hasNumber;
|
this.hasNumber = hasNumber;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = OsuHitObject.OBJECT_DIMENSIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[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.
|
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png.
|
||||||
InternalChildren = new[]
|
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,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -77,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -4,9 +4,11 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -15,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public partial class LegacyReverseArrow : CompositeDrawable
|
public partial class LegacyReverseArrow : CompositeDrawable
|
||||||
{
|
{
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
private DrawableHitObject? drawableHitObject { get; set; }
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
private Drawable proxy = null!;
|
private Drawable proxy = null!;
|
||||||
|
|
||||||
@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
private Drawable arrow = null!;
|
private Drawable arrow = null!;
|
||||||
|
|
||||||
|
private bool shouldRotate;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skinSource)
|
private void load(ISkinSource skinSource)
|
||||||
{
|
{
|
||||||
@ -35,8 +39,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
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()).With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.Centre;
|
||||||
|
d.Origin = Anchor.Centre;
|
||||||
|
});
|
||||||
|
|
||||||
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
||||||
|
|
||||||
|
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
|
||||||
|
shouldRotate = skinSource.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -45,17 +58,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
proxy = CreateProxy();
|
proxy = CreateProxy();
|
||||||
|
|
||||||
if (drawableHitObject != null)
|
drawableObject.HitObjectApplied += onHitObjectApplied;
|
||||||
{
|
onHitObjectApplied(drawableObject);
|
||||||
drawableHitObject.HitObjectApplied += onHitObjectApplied;
|
|
||||||
onHitObjectApplied(drawableHitObject);
|
|
||||||
|
|
||||||
accentColour = drawableHitObject.AccentColour.GetBoundCopy();
|
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(c =>
|
accentColour.BindValueChanged(c =>
|
||||||
{
|
{
|
||||||
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White;
|
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onHitObjectApplied(DrawableHitObject drawableObject)
|
private void onHitObjectApplied(DrawableHitObject drawableObject)
|
||||||
@ -67,11 +77,43 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
.OverlayElementContainer.Add(proxy);
|
.OverlayElementContainer.Add(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
const double duration = 300;
|
||||||
|
const float rotation = 5.625f;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Idle:
|
||||||
|
if (shouldRotate)
|
||||||
|
{
|
||||||
|
InternalChild.ScaleTo(1.3f)
|
||||||
|
.RotateTo(rotation)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(1f, duration)
|
||||||
|
.RotateTo(-rotation, duration)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InternalChild.ScaleTo(1.3f).Then()
|
||||||
|
.ScaleTo(1f, duration, Easing.Out)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
if (drawableHitObject != null)
|
|
||||||
drawableHitObject.HitObjectApplied -= onHitObjectApplied;
|
if (drawableObject.IsNotNull())
|
||||||
|
{
|
||||||
|
drawableObject.HitObjectApplied -= onHitObjectApplied;
|
||||||
|
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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),
|
Colour = new Color4(5, 5, 5, 255),
|
||||||
},
|
},
|
||||||
LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d =>
|
LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d =>
|
||||||
@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = skin.GetTexture("sliderb-spec"),
|
Texture = skin.GetTexture("sliderb-spec")?.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS),
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
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.
|
/// 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.
|
/// We must account for some gameplay elements such as slider bodies, where this padding is not present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
|
public const float LEGACY_CIRCLE_RADIUS = OsuHitObject.OBJECT_RADIUS - 5;
|
||||||
|
|
||||||
public OsuLegacySkinTransformer(ISkin skin)
|
public OsuLegacySkinTransformer(ISkin skin)
|
||||||
: base(skin)
|
: base(skin)
|
||||||
@ -41,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
return this.GetAnimation("sliderscorepoint", false, false);
|
return this.GetAnimation("sliderscorepoint", false, false);
|
||||||
|
|
||||||
case OsuSkinComponents.SliderFollowCircle:
|
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)
|
if (followCircleContent != null)
|
||||||
return new LegacyFollowCircle(followCircleContent);
|
return new LegacyFollowCircle(followCircleContent);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.SliderBall:
|
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
|
// todo: slider ball has a custom frame delay based on velocity
|
||||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
// 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))
|
if (!this.HasFont(LegacyFont.HitCircle))
|
||||||
return null;
|
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
|
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||||
Scale = new Vector2(0.8f),
|
Scale = new Vector2(0.8f),
|
||||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
new RingPiece(3)
|
new RingPiece(3)
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2),
|
Size = OsuHitObject.OBJECT_DIMENSIONS,
|
||||||
Alpha = 0.1f,
|
Alpha = 0.1f,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -25,12 +25,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
public override int Version => 20220902;
|
public override int Version => 20220902;
|
||||||
|
|
||||||
private readonly IWorkingBeatmap workingBeatmap;
|
|
||||||
|
|
||||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
workingBeatmap = beatmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
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),
|
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;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,39 +2,29 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||||
{
|
{
|
||||||
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
|
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 legacyBonusScore;
|
||||||
private int modernBonusScore;
|
private int standardisedBonusScore;
|
||||||
private int combo;
|
private int combo;
|
||||||
|
|
||||||
private double modMultiplier;
|
|
||||||
private int difficultyPeppyStars;
|
private int difficultyPeppyStars;
|
||||||
private IBeatmap playableBeatmap = null!;
|
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.playableBeatmap = playableBeatmap;
|
||||||
this.mods = mods;
|
|
||||||
|
|
||||||
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
IBeatmap baseBeatmap = workingBeatmap.Beatmap;
|
||||||
|
|
||||||
@ -76,13 +66,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
+ baseBeatmap.Difficulty.CircleSize
|
+ baseBeatmap.Difficulty.CircleSize
|
||||||
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
|
+ 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)
|
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 increaseCombo = true;
|
||||||
bool addScoreComboMultiplier = false;
|
bool addScoreComboMultiplier = false;
|
||||||
@ -109,21 +103,24 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
case Swell swell:
|
case Swell swell:
|
||||||
// The taiko swell generally does not match the osu-stable implementation in any way.
|
// The taiko swell generally does not match the osu-stable implementation in any way.
|
||||||
// We'll redo the calculations to match osu-stable here...
|
// 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).
|
// 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);
|
halfSpinsRequiredForCompletion = (int)Math.Max(1, halfSpinsRequiredForCompletion * 1.65f);
|
||||||
|
|
||||||
if (mods.Any(m => m is ModDoubleTime))
|
//
|
||||||
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 0.75f));
|
// 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.
|
||||||
if (mods.Any(m => m is ModHalfTime))
|
// This way, scores remain beatable at the cost of the conversion being slightly inaccurate.
|
||||||
halfSpinsRequiredForCompletion = Math.Max(1, (int)(halfSpinsRequiredForCompletion * 1.5f));
|
// - 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++)
|
for (int i = 0; i <= halfSpinsRequiredForCompletion; i++)
|
||||||
simulateHit(new SwellTick());
|
simulateHit(new SwellTick(), ref attributes);
|
||||||
|
|
||||||
scoreIncrease = 300;
|
scoreIncrease = 300;
|
||||||
addScoreComboMultiplier = true;
|
addScoreComboMultiplier = true;
|
||||||
@ -139,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
case DrumRoll:
|
case DrumRoll:
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
foreach (var nested in hitObject.NestedHitObjects)
|
||||||
simulateHit(nested);
|
simulateHit(nested, ref attributes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,8 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
int oldScoreIncrease = scoreIncrease;
|
int oldScoreIncrease = scoreIncrease;
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
|
scoreIncrease += scoreIncrease / 35 * 2 * (difficultyPeppyStars + 1) * (Math.Min(100, combo) / 10);
|
||||||
scoreIncrease += (int)(scoreIncrease / 35 * 2 * (difficultyPeppyStars + 1) * modMultiplier) * (Math.Min(100, combo) / 10);
|
|
||||||
|
|
||||||
if (hitObject is Swell)
|
if (hitObject is Swell)
|
||||||
{
|
{
|
||||||
@ -185,15 +181,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
scoreIncrease -= comboScoreIncrease;
|
scoreIncrease -= comboScoreIncrease;
|
||||||
|
|
||||||
if (addScoreComboMultiplier)
|
if (addScoreComboMultiplier)
|
||||||
ComboScore += comboScoreIncrease;
|
attributes.ComboScore += comboScoreIncrease;
|
||||||
|
|
||||||
if (isBonus)
|
if (isBonus)
|
||||||
{
|
{
|
||||||
legacyBonusScore += scoreIncrease;
|
legacyBonusScore += scoreIncrease;
|
||||||
modernBonusScore += Judgement.ToNumericResult(bonusResult);
|
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
AccuracyScore += scoreIncrease;
|
attributes.AccuracyScore += scoreIncrease;
|
||||||
|
|
||||||
if (increaseCombo)
|
if (increaseCombo)
|
||||||
combo++;
|
combo++;
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public partial class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
public partial class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 circle_piece_size = new Vector2(128);
|
||||||
|
|
||||||
private Drawable backgroundLayer = null!;
|
private Drawable backgroundLayer = null!;
|
||||||
private Drawable? foregroundLayer;
|
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;
|
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.
|
// 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.
|
// 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".
|
// 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.
|
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
||||||
foreach (var c in InternalChildren)
|
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)
|
if (foregroundLayer is IFramedAnimation animatableForegroundLayer)
|
||||||
animateForegroundLayer(animatableForegroundLayer);
|
animateForegroundLayer(animatableForegroundLayer);
|
||||||
|
@ -34,6 +34,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.Taiko.Configuration;
|
using osu.Game.Rulesets.Taiko.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSingleSpan()
|
public void TestSingleSpan()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1).ToArray();
|
||||||
|
|
||||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||||
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRepeat()
|
public void TestRepeat()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2).ToArray();
|
||||||
|
|
||||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||||
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestNonEvenTicks()
|
public void TestNonEvenTicks()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2).ToArray();
|
||||||
|
|
||||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||||
@ -83,12 +83,12 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLegacyLastTickOffset()
|
public void TestLastTickOffset()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1).ToArray();
|
||||||
|
|
||||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
|
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LastTick));
|
||||||
Assert.That(events[2].Time, Is.EqualTo(900));
|
Assert.That(events[2].Time, Is.EqualTo(span_duration + SliderEventGenerator.LAST_TICK_OFFSET));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
const double velocity = 5;
|
const double velocity = 5;
|
||||||
const double min_distance = velocity * 10;
|
const double min_distance = velocity * 10;
|
||||||
|
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2).ToArray();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
@ -44,17 +44,23 @@ namespace osu.Game.Tests.Database
|
|||||||
createFile(subdirectory2, Path.Combine("beatmap5", "beatmap.osu"));
|
createFile(subdirectory2, Path.Combine("beatmap5", "beatmap.osu"));
|
||||||
createFile(subdirectory2, Path.Combine("beatmap6", "beatmap.osu"));
|
createFile(subdirectory2, Path.Combine("beatmap6", "beatmap.osu"));
|
||||||
|
|
||||||
|
// songs subdirectory with random file
|
||||||
|
var subdirectory3 = songsStorage.GetStorageForDirectory("subdirectory3");
|
||||||
|
createFile(subdirectory3, "silly readme.txt");
|
||||||
|
createFile(subdirectory3, Path.Combine("beatmap7", "beatmap.osu"));
|
||||||
|
|
||||||
// empty songs subdirectory
|
// empty songs subdirectory
|
||||||
songsStorage.GetStorageForDirectory("subdirectory3");
|
songsStorage.GetStorageForDirectory("subdirectory3");
|
||||||
|
|
||||||
string[] paths = importer.GetStableImportPaths(songsStorage).ToArray();
|
string[] paths = importer.GetStableImportPaths(songsStorage).ToArray();
|
||||||
Assert.That(paths.Length, Is.EqualTo(6));
|
Assert.That(paths.Length, Is.EqualTo(7));
|
||||||
Assert.That(paths.Contains(songsStorage.GetFullPath("beatmap1")));
|
Assert.That(paths.Contains(songsStorage.GetFullPath("beatmap1")));
|
||||||
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "beatmap2"))));
|
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "beatmap2"))));
|
||||||
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "beatmap3"))));
|
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "beatmap3"))));
|
||||||
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "sub-subdirectory", "beatmap4"))));
|
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory", "sub-subdirectory", "beatmap4"))));
|
||||||
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory2", "beatmap5"))));
|
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory2", "beatmap5"))));
|
||||||
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory2", "beatmap6"))));
|
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory2", "beatmap6"))));
|
||||||
|
Assert.That(paths.Contains(songsStorage.GetFullPath(Path.Combine("subdirectory3", "beatmap7"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void createFile(Storage storage, string path)
|
static void createFile(Storage storage, string path)
|
||||||
|
@ -45,9 +45,9 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
[TestCase(ScoringMode.Standardised, HitResult.Meh, 116_667)]
|
[TestCase(ScoringMode.Standardised, HitResult.Meh, 116_667)]
|
||||||
[TestCase(ScoringMode.Standardised, HitResult.Ok, 233_338)]
|
[TestCase(ScoringMode.Standardised, HitResult.Ok, 233_338)]
|
||||||
[TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)]
|
[TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Meh, 0)]
|
[TestCase(ScoringMode.Classic, HitResult.Meh, 11_670)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Ok, 2)]
|
[TestCase(ScoringMode.Classic, HitResult.Ok, 23_341)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Great, 36)]
|
[TestCase(ScoringMode.Classic, HitResult.Great, 100_033)]
|
||||||
public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore)
|
public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore)
|
||||||
{
|
{
|
||||||
scoreProcessor.ApplyBeatmap(beatmap);
|
scoreProcessor.ApplyBeatmap(beatmap);
|
||||||
@ -84,17 +84,17 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
[TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)]
|
[TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)]
|
||||||
[TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)]
|
[TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)]
|
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 4)]
|
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 7_975)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 15)]
|
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 15_949)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 53)]
|
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 30_398)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 140)]
|
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 49_546)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 140)]
|
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 49_546)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)]
|
[TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 11)]
|
[TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 54_189)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)]
|
[TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 9)]
|
[TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 49_289)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 36)]
|
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)]
|
||||||
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 36)]
|
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)]
|
||||||
public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore)
|
public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore)
|
||||||
{
|
{
|
||||||
var minResult = new TestJudgement(hitResult).MinResult;
|
var minResult = new TestJudgement(hitResult).MinResult;
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
|
||||||
|
|
||||||
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
assertCorrectMetadata(import1, "test skin [skin]", "skinner", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk"));
|
||||||
|
|
||||||
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
assertCorrectMetadata(import1, "test skin [skin]", "skinner", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
|
||||||
|
|
||||||
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
assertCorrectMetadata(import1, "test skin [skin]", "skinner", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -54,7 +54,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "test skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "test skin.osk"));
|
||||||
|
|
||||||
// When the import filename matches it shouldn't be appended.
|
// When the import filename matches it shouldn't be appended.
|
||||||
assertCorrectMetadata(import1, "test skin", "skinner", osu);
|
assertCorrectMetadata(import1, "test skin", "skinner", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithNonIniFile(), "test skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithNonIniFile(), "test skin.osk"));
|
||||||
|
|
||||||
// When the import filename matches it shouldn't be appended.
|
// When the import filename matches it shouldn't be appended.
|
||||||
assertCorrectMetadata(import1, "test skin", "Unknown", osu);
|
assertCorrectMetadata(import1, "test skin", "Unknown", SkinConfiguration.LATEST_VERSION, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -72,7 +72,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createEmptyOsk(), "test skin.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createEmptyOsk(), "test skin.osk"));
|
||||||
|
|
||||||
// When the import filename matches it shouldn't be appended.
|
// When the import filename matches it shouldn't be appended.
|
||||||
assertCorrectMetadata(import1, "test skin", "Unknown", osu);
|
assertCorrectMetadata(import1, "test skin", "Unknown", SkinConfiguration.LATEST_VERSION, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -102,7 +102,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu =>
|
public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.OsK"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.OsK"));
|
||||||
assertCorrectMetadata(import1, "name 1", "author 1", osu);
|
assertCorrectMetadata(import1, "name 1", "author 1", 1.0m, osu);
|
||||||
|
|
||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.oSK"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.oSK"));
|
||||||
|
|
||||||
@ -115,14 +115,14 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
MemoryStream exportStream = new MemoryStream();
|
MemoryStream exportStream = new MemoryStream();
|
||||||
|
|
||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "custom.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "custom.osk"));
|
||||||
assertCorrectMetadata(import1, "name 1 [custom]", "author 1", osu);
|
assertCorrectMetadata(import1, "name 1 [custom]", "author 1", 1.0m, osu);
|
||||||
|
|
||||||
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(import1, exportStream);
|
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(import1, exportStream);
|
||||||
|
|
||||||
string exportFilename = import1.GetDisplayString();
|
string exportFilename = import1.GetDisplayString();
|
||||||
|
|
||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
||||||
assertCorrectMetadata(import2, "name 1 [custom]", "author 1", osu);
|
assertCorrectMetadata(import2, "name 1 [custom]", "author 1", 1.0m, osu);
|
||||||
|
|
||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
});
|
});
|
||||||
@ -133,14 +133,14 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
MemoryStream exportStream = new MemoryStream();
|
MemoryStream exportStream = new MemoryStream();
|
||||||
|
|
||||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
||||||
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", osu);
|
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", 1.0m, osu);
|
||||||
|
|
||||||
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(import1, exportStream);
|
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(import1, exportStream);
|
||||||
|
|
||||||
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
||||||
|
|
||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
||||||
assertCorrectMetadata(import2, "name 『1』 [custom]", "author 1", osu);
|
assertCorrectMetadata(import2, "name 『1』 [custom]", "author 1", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -150,7 +150,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport);
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport);
|
||||||
|
|
||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
|
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -183,8 +183,8 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2.1", "skinner"), "skin.osk"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2.1", "skinner"), "skin.osk"));
|
||||||
|
|
||||||
assertImportedBoth(import1, import2);
|
assertImportedBoth(import1, import2);
|
||||||
assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", osu);
|
assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", 1.0m, osu);
|
||||||
assertCorrectMetadata(import2, "test skin v2.1 [skin]", "skinner", osu);
|
assertCorrectMetadata(import2, "test skin v2.1 [skin]", "skinner", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -194,8 +194,8 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 2"));
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 2"));
|
||||||
|
|
||||||
assertImportedBoth(import1, import2);
|
assertImportedBoth(import1, import2);
|
||||||
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
|
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", 1.0m, osu);
|
||||||
assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", osu);
|
assertCorrectMetadata(import2, "name 1 [my custom skin 2]", "author 1", 1.0m, osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -264,7 +264,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void assertCorrectMetadata(Live<SkinInfo> import1, string name, string creator, OsuGameBase osu)
|
private void assertCorrectMetadata(Live<SkinInfo> import1, string name, string creator, decimal version, OsuGameBase osu)
|
||||||
{
|
{
|
||||||
import1.PerformRead(i =>
|
import1.PerformRead(i =>
|
||||||
{
|
{
|
||||||
@ -276,6 +276,7 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
|
|
||||||
Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
|
Assert.That(instance.Configuration.SkinInfo.Name, Is.EqualTo(name));
|
||||||
Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
|
Assert.That(instance.Configuration.SkinInfo.Creator, Is.EqualTo(creator));
|
||||||
|
Assert.That(instance.Configuration.LegacyVersion, Is.EqualTo(version));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
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());
|
AddStep("test play", () => Editor.TestGameplay());
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black,
|
Colour = Color4.Gray,
|
||||||
},
|
},
|
||||||
new ArgonHealthDisplay
|
new ArgonHealthDisplay
|
||||||
{
|
{
|
||||||
|
@ -2,17 +2,23 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -21,17 +27,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||||
|
|
||||||
[Cached]
|
[Cached(typeof(Storyboard))]
|
||||||
private Storyboard storyboard { get; set; } = new Storyboard();
|
private TestStoryboard storyboard { get; set; } = new TestStoryboard();
|
||||||
|
|
||||||
private IEnumerable<DrawableStoryboardSprite> sprites => this.ChildrenOfType<DrawableStoryboardSprite>();
|
private IEnumerable<DrawableStoryboardSprite> sprites => this.ChildrenOfType<DrawableStoryboardSprite>();
|
||||||
|
|
||||||
|
private const string lookup_name = "hitcircleoverlay";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSkinSpriteDisallowedByDefault()
|
public void TestSkinSpriteDisallowedByDefault()
|
||||||
{
|
{
|
||||||
const string lookup_name = "hitcircleoverlay";
|
AddStep("disallow all lookups", () =>
|
||||||
|
{
|
||||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false);
|
storyboard.UseSkinSprites = false;
|
||||||
|
storyboard.AlwaysProvideTexture = false;
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||||
|
|
||||||
@ -40,11 +50,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAllowLookupFromSkin()
|
public void TestLookupFromStoryboard()
|
||||||
{
|
{
|
||||||
const string lookup_name = "hitcircleoverlay";
|
AddStep("allow storyboard lookup", () =>
|
||||||
|
{
|
||||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
storyboard.UseSkinSprites = false;
|
||||||
|
storyboard.AlwaysProvideTexture = true;
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
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", () =>
|
AddAssert("sprite found texture", () =>
|
||||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture != null)));
|
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture != null)));
|
||||||
|
|
||||||
AddAssert("skinnable sprite has correct size", () =>
|
assertStoryboardSourced();
|
||||||
sprites.Any(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Size == new Vector2(128))));
|
}
|
||||||
|
|
||||||
|
[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]
|
[Test]
|
||||||
public void TestFlippedSprite()
|
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("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||||
AddStep("flip sprites", () => sprites.ForEach(s =>
|
AddStep("flip sprites", () => sprites.ForEach(s =>
|
||||||
{
|
{
|
||||||
@ -74,9 +124,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestZeroScale()
|
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)));
|
AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||||
AddAssert("sprites present", () => sprites.All(s => s.IsPresent));
|
AddAssert("sprites present", () => sprites.All(s => s.IsPresent));
|
||||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1)));
|
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1)));
|
||||||
@ -86,9 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestNegativeScale()
|
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("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
|
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
|
||||||
AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
|
AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
|
||||||
@ -97,9 +153,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestNegativeScaleWithFlippedSprite()
|
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("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||||
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
|
AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
|
||||||
AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
|
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));
|
AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
|
private DrawableStoryboard createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
|
||||||
=> new DrawableStoryboardSprite(
|
{
|
||||||
new StoryboardSprite(lookupName, origin, initialPosition)
|
var layer = storyboard.GetLayer("Background");
|
||||||
).With(s =>
|
|
||||||
|
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;
|
return new TestDrawableStoryboard(this, mods);
|
||||||
s.LifetimeEnd = double.MaxValue;
|
}
|
||||||
});
|
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -312,7 +312,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
addRulesetImportStep(0);
|
// We need to use one real beatmap to trigger the "same-track-transfer" logic that we're looking to test here.
|
||||||
|
// See `SongSelect.ensurePlayingSelected` and `WorkingBeatmap.TryTransferTrack`.
|
||||||
|
AddStep("import test beatmap", () => manager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).WaitSafely());
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
checkMusicPlaying(true);
|
checkMusicPlaying(true);
|
||||||
@ -321,6 +323,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddStep("manual pause", () => music.TogglePause());
|
AddStep("manual pause", () => music.TogglePause());
|
||||||
checkMusicPlaying(false);
|
checkMusicPlaying(false);
|
||||||
|
|
||||||
|
// Track should not have changed, so music should still not be playing.
|
||||||
AddStep("select next difficulty", () => songSelect!.Carousel.SelectNext(skipDifficulties: false));
|
AddStep("select next difficulty", () => songSelect!.Carousel.SelectNext(skipDifficulties: false));
|
||||||
checkMusicPlaying(false);
|
checkMusicPlaying(false);
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -25,6 +29,53 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)),
|
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("toggle selected", () =>
|
||||||
|
{
|
||||||
|
foreach (var icon in this.ChildrenOfType<ModIcon>())
|
||||||
|
icon.Selected.Toggle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowRateAdjusts()
|
||||||
|
{
|
||||||
|
AddStep("create mod icons", () =>
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Full,
|
||||||
|
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods()
|
||||||
|
.OfType<ModRateAdjust>()
|
||||||
|
.SelectMany(m =>
|
||||||
|
{
|
||||||
|
List<ModIcon> icons = new List<ModIcon> { new ModIcon(m) };
|
||||||
|
|
||||||
|
for (double i = m.SpeedChange.MinValue; i < m.SpeedChange.MaxValue; i += m.SpeedChange.Precision * 10)
|
||||||
|
{
|
||||||
|
m = (ModRateAdjust)m.DeepClone();
|
||||||
|
m.SpeedChange.Value = i;
|
||||||
|
icons.Add(new ModIcon(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
return icons;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("adjust rates", () =>
|
||||||
|
{
|
||||||
|
foreach (var icon in this.ChildrenOfType<ModIcon>())
|
||||||
|
{
|
||||||
|
if (icon.Mod is ModRateAdjust rateAdjust)
|
||||||
|
{
|
||||||
|
rateAdjust.SpeedChange.Value = RNG.NextDouble() > 0.9
|
||||||
|
? rateAdjust.SpeedChange.Default
|
||||||
|
: RNG.NextDouble(rateAdjust.SpeedChange.MinValue, rateAdjust.SpeedChange.MaxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -280,7 +280,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override string HumanisedModelName => "beatmap";
|
public override string HumanisedModelName => "beatmap";
|
||||||
|
|
||||||
protected override BeatmapSetInfo? CreateModel(ArchiveReader reader)
|
protected override BeatmapSetInfo? CreateModel(ArchiveReader reader, ImportParameters parameters)
|
||||||
{
|
{
|
||||||
// let's make sure there are actually .osu files to import.
|
// let's make sure there are actually .osu files to import.
|
||||||
string? mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu", StringComparison.OrdinalIgnoreCase));
|
string? mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu", StringComparison.OrdinalIgnoreCase));
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -34,9 +33,9 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!directoryStorage.GetFiles(string.Empty).ExcludeSystemFileNames().Any())
|
if (!directoryStorage.GetFiles(string.Empty, "*.osu").Any())
|
||||||
{
|
{
|
||||||
// if a directory doesn't contain files, attempt looking for beatmaps inside of that directory.
|
// if a directory doesn't contain any beatmap files, look for further nested beatmap directories.
|
||||||
// this is a special behaviour in stable for beatmaps only, see https://github.com/ppy/osu/issues/18615.
|
// this is a special behaviour in stable for beatmaps only, see https://github.com/ppy/osu/issues/18615.
|
||||||
foreach (string subDirectory in GetStableImportPaths(directoryStorage))
|
foreach (string subDirectory in GetStableImportPaths(directoryStorage))
|
||||||
paths.Add(subDirectory);
|
paths.Add(subDirectory);
|
||||||
|
@ -149,7 +149,7 @@ namespace osu.Game.Database
|
|||||||
return imported;
|
return imported;
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed!";
|
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information.";
|
||||||
notification.State = ProgressNotificationState.Cancelled;
|
notification.State = ProgressNotificationState.Cancelled;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -229,7 +229,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
model = CreateModel(archive);
|
model = CreateModel(archive, parameters);
|
||||||
|
|
||||||
if (model == null)
|
if (model == null)
|
||||||
return null;
|
return null;
|
||||||
@ -474,8 +474,9 @@ namespace osu.Game.Database
|
|||||||
/// Actual expensive population should be done in <see cref="Populate"/>; this should just prepare for duplicate checking.
|
/// Actual expensive population should be done in <see cref="Populate"/>; this should just prepare for duplicate checking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">The archive to create the model for.</param>
|
/// <param name="archive">The archive to create the model for.</param>
|
||||||
|
/// <param name="parameters">Parameters to further configure the import process.</param>
|
||||||
/// <returns>A model populated with minimal information. Returning a null will abort importing silently.</returns>
|
/// <returns>A model populated with minimal information. Returning a null will abort importing silently.</returns>
|
||||||
protected abstract TModel? CreateModel(ArchiveReader archive);
|
protected abstract TModel? CreateModel(ArchiveReader archive, ImportParameters parameters);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Populate the provided model completely from the given archive.
|
/// Populate the provided model completely from the given archive.
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
@ -222,15 +223,9 @@ namespace osu.Game.Database
|
|||||||
throw new InvalidOperationException("Beatmap contains no hit objects!");
|
throw new InvalidOperationException("Beatmap contains no hit objects!");
|
||||||
|
|
||||||
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
||||||
|
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
||||||
|
|
||||||
sv1Simulator.Simulate(beatmap, playableBeatmap, mods);
|
return ConvertFromLegacyTotalScore(score, attributes);
|
||||||
|
|
||||||
return ConvertFromLegacyTotalScore(score, new DifficultyAttributes
|
|
||||||
{
|
|
||||||
LegacyAccuracyScore = sv1Simulator.AccuracyScore,
|
|
||||||
LegacyComboScore = sv1Simulator.ComboScore,
|
|
||||||
LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -241,20 +236,21 @@ namespace osu.Game.Database
|
|||||||
/// (<see cref="DifficultyAttributes.LegacyAccuracyScore"/>, <see cref="DifficultyAttributes.LegacyComboScore"/>, and <see cref="DifficultyAttributes.LegacyBonusScoreRatio"/>)
|
/// (<see cref="DifficultyAttributes.LegacyAccuracyScore"/>, <see cref="DifficultyAttributes.LegacyComboScore"/>, and <see cref="DifficultyAttributes.LegacyBonusScoreRatio"/>)
|
||||||
/// for the beatmap which the score was set on.</param>
|
/// for the beatmap which the score was set on.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <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)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
|
|
||||||
Debug.Assert(score.LegacyTotalScore != null);
|
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);
|
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.
|
// 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.
|
// The combo proportion is calculated as a proportion of maximumLegacyBaseScore.
|
||||||
double comboProportion = Math.Min(1, (double)score.LegacyTotalScore / maximumLegacyBaseScore);
|
double comboProportion = Math.Min(1, (double)score.LegacyTotalScore / maximumLegacyBaseScore);
|
||||||
|
@ -89,6 +89,8 @@ namespace osu.Game.Graphics
|
|||||||
public static IconUsage ModSpunOut => Get(0xe046);
|
public static IconUsage ModSpunOut => Get(0xe046);
|
||||||
public static IconUsage ModSuddenDeath => Get(0xe047);
|
public static IconUsage ModSuddenDeath => Get(0xe047);
|
||||||
public static IconUsage ModTarget => Get(0xe048);
|
public static IconUsage ModTarget => Get(0xe048);
|
||||||
public static IconUsage ModBg => Get(0xe04a);
|
|
||||||
|
// Use "Icons/BeatmapDetails/mod-icon" instead
|
||||||
|
// public static IconUsage ModBg => Get(0xe04a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,14 @@ namespace osu.Game.Online.API
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
// Network failure.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
//todo: potentially only kill the refresh token on certain exception types.
|
// Force a full re-reauthentication.
|
||||||
Token.Value = null;
|
Token.Value = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ using osuTK;
|
|||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
@ -242,7 +243,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
ChildrenEnumerable = Score.Mods.AsOrdered().Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
topScoreStatistics.Clear();
|
topScoreStatistics.Clear();
|
||||||
bottomScoreStatistics.Clear();
|
bottomScoreStatistics.Clear();
|
||||||
|
|
||||||
foreach (var mod in score.Mods)
|
foreach (var mod in score.Mods.AsOrdered())
|
||||||
{
|
{
|
||||||
modStatistics.Add(new ModCell(mod));
|
modStatistics.Add(new ModCell(mod));
|
||||||
}
|
}
|
||||||
@ -210,7 +210,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
Spacing = new Vector2(2f, 0f),
|
Spacing = new Vector2(2f, 0f),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new ModIcon(mod, showTooltip: false).With(icon =>
|
new ModIcon(mod, showTooltip: false, showExtendedInformation: false).With(icon =>
|
||||||
{
|
{
|
||||||
icon.Origin = Anchor.CentreLeft;
|
icon.Origin = Anchor.CentreLeft;
|
||||||
icon.Anchor = Anchor.CentreLeft;
|
icon.Anchor = Anchor.CentreLeft;
|
||||||
|
@ -24,6 +24,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring.Drawables;
|
using osu.Game.Scoring.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||||
@ -195,7 +196,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Spacing = new Vector2(1),
|
Spacing = new Vector2(1),
|
||||||
ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m)
|
ChildrenEnumerable = score.Mods.AsOrdered().Select(m => new ModIcon(m)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Scale = new Vector2(0.3f)
|
Scale = new Vector2(0.3f)
|
||||||
|
@ -275,7 +275,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
modsContainer.Clear();
|
modsContainer.Clear();
|
||||||
modsContainer.Children = value.Select(mod => new ModIcon(mod)
|
modsContainer.Children = value.AsOrdered().Select(mod => new ModIcon(mod)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Scale = new Vector2(0.25f),
|
Scale = new Vector2(0.25f),
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -28,6 +31,9 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
|
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
|
||||||
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
|
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
|
||||||
|
|
||||||
|
private readonly Box flashLayer;
|
||||||
|
private Sample flashSample = null!;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
private readonly Container ring;
|
private readonly Container ring;
|
||||||
private readonly FillFlowContainer<PopupDialogButton> buttonsContainer;
|
private readonly FillFlowContainer<PopupDialogButton> buttonsContainer;
|
||||||
@ -208,6 +214,13 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
},
|
},
|
||||||
|
flashLayer = new Box
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Colour = Color4Extensions.FromHex(@"221a21"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -217,6 +230,12 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
Show();
|
Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
flashSample = audio.Samples.Get(@"UI/default-select-disabled");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Programmatically clicks the first <see cref="PopupDialogOkButton"/>.
|
/// Programmatically clicks the first <see cref="PopupDialogOkButton"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -232,6 +251,14 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
Scheduler.AddOnce(() => Buttons.OfType<T>().FirstOrDefault()?.TriggerClick());
|
Scheduler.AddOnce(() => Buttons.OfType<T>().FirstOrDefault()?.TriggerClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Flash()
|
||||||
|
{
|
||||||
|
flashLayer.FadeInFromZero(80, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.FadeOutFromOne(1500, Easing.OutQuint);
|
||||||
|
flashSample.Play();
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Repeat) return false;
|
if (e.Repeat) return false;
|
||||||
|
@ -159,7 +159,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
scrollContent.ChildrenEnumerable = saveableMods.Select(mod => new ModPresetRow(mod));
|
scrollContent.ChildrenEnumerable = saveableMods.AsOrdered().Select(mod => new ModPresetRow(mod));
|
||||||
useCurrentModsButton.Enabled.Value = checkSelectedModsDiffersFromSaved();
|
useCurrentModsButton.Enabled.Value = checkSelectedModsDiffersFromSaved();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
lastPreset = preset;
|
lastPreset = preset;
|
||||||
Content.ChildrenEnumerable = preset.Mods.Select(mod => new ModPresetRow(mod));
|
Content.ChildrenEnumerable = preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint);
|
protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint);
|
||||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
modSettingsFlow.Clear();
|
modSettingsFlow.Clear();
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OrderBy(mod => mod.Type).ThenBy(mod => mod.Acronym))
|
foreach (var mod in SelectedMods.Value.AsOrdered())
|
||||||
{
|
{
|
||||||
var settings = mod.CreateSettingsControls().ToList();
|
var settings = mod.CreateSettingsControls().ToList();
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring.Drawables;
|
using osu.Game.Scoring.Drawables;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
@ -48,6 +49,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(RulesetStore rulesets)
|
private void load(RulesetStore rulesets)
|
||||||
{
|
{
|
||||||
|
var ruleset = rulesets.GetRuleset(Score.RulesetID)?.CreateInstance() ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally");
|
||||||
|
|
||||||
AddInternal(new ProfileItemContainer
|
AddInternal(new ProfileItemContainer
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -132,14 +135,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
|||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Spacing = new Vector2(2),
|
Spacing = new Vector2(2),
|
||||||
Children = Score.Mods.Select(mod =>
|
Children = Score.Mods.Select(m => m.ToMod(ruleset)).AsOrdered().Select(mod => new ModIcon(mod)
|
||||||
{
|
{
|
||||||
var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally");
|
Scale = new Vector2(0.35f)
|
||||||
|
|
||||||
return new ModIcon(mod.ToMod(ruleset.CreateInstance()))
|
|
||||||
{
|
|
||||||
Scale = new Vector2(0.35f)
|
|
||||||
};
|
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
},
|
},
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
Keywords = new[] { "combo", "override" },
|
Keywords = new[] { "combo", "override", "color" },
|
||||||
LabelText = SkinSettingsStrings.BeatmapColours,
|
LabelText = SkinSettingsStrings.BeatmapColours,
|
||||||
Current = config.GetBindable<bool>(OsuSetting.BeatmapColours)
|
Current = config.GetBindable<bool>(OsuSetting.BeatmapColours)
|
||||||
},
|
},
|
||||||
@ -47,6 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
},
|
},
|
||||||
new SettingsSlider<float>
|
new SettingsSlider<float>
|
||||||
{
|
{
|
||||||
|
Keywords = new[] { "color" },
|
||||||
LabelText = GraphicsSettingsStrings.ComboColourNormalisation,
|
LabelText = GraphicsSettingsStrings.ComboColourNormalisation,
|
||||||
Current = comboColourNormalisation,
|
Current = comboColourNormalisation,
|
||||||
DisplayAsPercentage = true,
|
DisplayAsPercentage = true,
|
||||||
|
@ -27,9 +27,6 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
protected const int ATTRIB_ID_FLASHLIGHT = 17;
|
protected const int ATTRIB_ID_FLASHLIGHT = 17;
|
||||||
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
|
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
|
||||||
protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21;
|
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>
|
/// <summary>
|
||||||
/// The mods which were applied to the beatmap.
|
/// 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()
|
public virtual IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
|
||||||
{
|
{
|
||||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
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>
|
/// <summary>
|
||||||
@ -104,11 +98,6 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
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
|
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>
|
/// <summary>
|
||||||
/// The beatmap for which difficulty will be calculated.
|
/// The beatmap for which difficulty will be calculated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,13 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short important information to display on the mod icon. For example, a rate adjust mod's rate
|
||||||
|
/// or similarly important setting.
|
||||||
|
/// Use <see cref="string.Empty"/> if the icon should not display any additional info.
|
||||||
|
/// </summary>
|
||||||
|
string ExtendedIconInformation { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user readable description of this mod.
|
/// The user readable description of this mod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +27,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public abstract string Acronym { get; }
|
public abstract string Acronym { get; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual string ExtendedIconInformation => string.Empty;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public virtual IconUsage? Icon => null;
|
public virtual IconUsage? Icon => null;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -28,5 +29,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Mod> AsOrdered(this IEnumerable<Mod> mods) => mods
|
||||||
|
.OrderBy(m => m.Type)
|
||||||
|
.ThenBy(m => m.Acronym);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,5 +28,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp), typeof(ModAdaptiveSpeed), typeof(ModRateAdjust) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp), typeof(ModAdaptiveSpeed), typeof(ModRateAdjust) };
|
||||||
|
|
||||||
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
|
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
|
||||||
|
|
||||||
|
public override string ExtendedIconInformation => SettingDescription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Legacy
|
namespace osu.Game.Rulesets.Objects.Legacy
|
||||||
{
|
{
|
||||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, IHasSliderVelocity
|
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||||
@ -59,7 +59,5 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double LegacyLastTickOffset => 36;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,17 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
public static class SliderEventGenerator
|
public static class SliderEventGenerator
|
||||||
{
|
{
|
||||||
// ReSharper disable once MethodOverloadWithOptionalParameter
|
/// <summary>
|
||||||
|
/// Historically, slider's final tick (aka the place where the slider would receive a final judgement) was offset by -36 ms. Originally this was
|
||||||
|
/// done to workaround a technical detail (unimportant), but over the years it has become an expectation of players that you don't need to hold
|
||||||
|
/// until the true end of the slider. This very small amount of leniency makes it easier to jump away from fast sliders to the next hit object.
|
||||||
|
///
|
||||||
|
/// After discussion on how this should be handled going forward, players have unanimously stated that this lenience should remain in some way.
|
||||||
|
/// </summary>
|
||||||
|
public const double LAST_TICK_OFFSET = -36;
|
||||||
|
|
||||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||||
double? legacyLastTickOffset, CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// A very lenient maximum length of a slider for ticks to be generated.
|
// A very lenient maximum length of a slider for ticks to be generated.
|
||||||
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
|
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
|
||||||
@ -76,14 +84,14 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
int finalSpanIndex = spanCount - 1;
|
int finalSpanIndex = spanCount - 1;
|
||||||
double finalSpanStartTime = startTime + finalSpanIndex * spanDuration;
|
double finalSpanStartTime = startTime + finalSpanIndex * spanDuration;
|
||||||
double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0));
|
double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) + LAST_TICK_OFFSET);
|
||||||
double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration;
|
double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration;
|
||||||
|
|
||||||
if (spanCount % 2 == 0) finalProgress = 1 - finalProgress;
|
if (spanCount % 2 == 0) finalProgress = 1 - finalProgress;
|
||||||
|
|
||||||
yield return new SliderEventDescriptor
|
yield return new SliderEventDescriptor
|
||||||
{
|
{
|
||||||
Type = SliderEventType.LegacyLastTick,
|
Type = SliderEventType.LastTick,
|
||||||
SpanIndex = finalSpanIndex,
|
SpanIndex = finalSpanIndex,
|
||||||
SpanStartTime = finalSpanStartTime,
|
SpanStartTime = finalSpanStartTime,
|
||||||
Time = finalSpanEndTime,
|
Time = finalSpanEndTime,
|
||||||
@ -173,7 +181,11 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
public enum SliderEventType
|
public enum SliderEventType
|
||||||
{
|
{
|
||||||
Tick,
|
Tick,
|
||||||
LegacyLastTick,
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs just before the tail. See <see cref="SliderEventGenerator.LAST_TICK_OFFSET"/>.
|
||||||
|
/// </summary>
|
||||||
|
LastTick,
|
||||||
Head,
|
Head,
|
||||||
Tail,
|
Tail,
|
||||||
Repeat
|
Repeat
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A type of <see cref="HitObject"/> which may require the last tick to be offset.
|
|
||||||
/// This is specific to osu!stable conversion, and should not be used elsewhere.
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasLegacyLastTickOffset
|
|
||||||
{
|
|
||||||
double LegacyLastTickOffset { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,22 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Bindables;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
@ -27,22 +27,27 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
public readonly BindableBool Selected = new BindableBool();
|
public readonly BindableBool Selected = new BindableBool();
|
||||||
|
|
||||||
private readonly SpriteIcon modIcon;
|
private SpriteIcon modIcon = null!;
|
||||||
private readonly SpriteText modAcronym;
|
private SpriteText modAcronym = null!;
|
||||||
private readonly SpriteIcon background;
|
private Sprite background = null!;
|
||||||
|
|
||||||
private const float size = 80;
|
public static readonly Vector2 MOD_ICON_SIZE = new Vector2(80);
|
||||||
|
|
||||||
public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : null;
|
public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : string.Empty;
|
||||||
|
|
||||||
private IMod mod;
|
private IMod mod;
|
||||||
|
|
||||||
private readonly bool showTooltip;
|
private readonly bool showTooltip;
|
||||||
|
private readonly bool showExtendedInformation;
|
||||||
|
|
||||||
public IMod Mod
|
public IMod Mod
|
||||||
{
|
{
|
||||||
get => mod;
|
get => mod;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
if (mod == value)
|
||||||
|
return;
|
||||||
|
|
||||||
mod = value;
|
mod = value;
|
||||||
|
|
||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
@ -51,49 +56,103 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
private Color4 backgroundColour;
|
private Color4 backgroundColour;
|
||||||
|
|
||||||
|
private Sprite extendedBackground = null!;
|
||||||
|
|
||||||
|
private OsuSpriteText extendedText = null!;
|
||||||
|
|
||||||
|
private Container extendedContent = null!;
|
||||||
|
|
||||||
|
private ModSettingChangeTracker? modSettingsChangeTracker;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a new instance.
|
/// Construct a new instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mod">The mod to be displayed</param>
|
/// <param name="mod">The mod to be displayed</param>
|
||||||
/// <param name="showTooltip">Whether a tooltip describing the mod should display on hover.</param>
|
/// <param name="showTooltip">Whether a tooltip describing the mod should display on hover.</param>
|
||||||
public ModIcon(IMod mod, bool showTooltip = true)
|
/// <param name="showExtendedInformation">Whether to display a mod's extended information, if available.</param>
|
||||||
|
public ModIcon(IMod mod, bool showTooltip = true, bool showExtendedInformation = true)
|
||||||
{
|
{
|
||||||
|
// May expand due to expanded content, so autosize here.
|
||||||
|
AutoSizeAxes = Axes.X;
|
||||||
|
Height = MOD_ICON_SIZE.Y;
|
||||||
|
|
||||||
this.mod = mod ?? throw new ArgumentNullException(nameof(mod));
|
this.mod = mod ?? throw new ArgumentNullException(nameof(mod));
|
||||||
this.showTooltip = showTooltip;
|
this.showTooltip = showTooltip;
|
||||||
|
this.showExtendedInformation = showExtendedInformation;
|
||||||
|
}
|
||||||
|
|
||||||
Size = new Vector2(size);
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
background = new SpriteIcon
|
extendedContent = new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Name = "extended content",
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.CentreLeft,
|
||||||
Size = new Vector2(size),
|
Origin = Anchor.CentreLeft,
|
||||||
Icon = OsuIcon.ModBg,
|
Size = new Vector2(116, MOD_ICON_SIZE.Y),
|
||||||
Shadow = true,
|
X = MOD_ICON_SIZE.X - 22,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
extendedBackground = new Sprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
Texture = textures.Get("Icons/BeatmapDetails/mod-icon-extender"),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
extendedText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Default.With(size: 34f, weight: FontWeight.Bold),
|
||||||
|
UseFullGlyphHeight = false,
|
||||||
|
Text = mod.ExtendedIconInformation,
|
||||||
|
X = 6,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modAcronym = new OsuSpriteText
|
new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Anchor = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.Centre,
|
Origin = Anchor.CentreLeft,
|
||||||
Colour = OsuColour.Gray(84),
|
Name = "main content",
|
||||||
Alpha = 0,
|
Size = MOD_ICON_SIZE,
|
||||||
Font = OsuFont.Numeric.With(null, 22f),
|
Children = new Drawable[]
|
||||||
UseFullGlyphHeight = false,
|
{
|
||||||
Text = mod.Acronym
|
background = new Sprite
|
||||||
},
|
{
|
||||||
modIcon = new SpriteIcon
|
RelativeSizeAxes = Axes.Both,
|
||||||
{
|
FillMode = FillMode.Fit,
|
||||||
Origin = Anchor.Centre,
|
Texture = textures.Get("Icons/BeatmapDetails/mod-icon"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Colour = OsuColour.Gray(84),
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(45),
|
},
|
||||||
Icon = FontAwesome.Solid.Question
|
modAcronym = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = OsuColour.Gray(84),
|
||||||
|
Alpha = 0,
|
||||||
|
Font = OsuFont.Numeric.With(null, 22f),
|
||||||
|
UseFullGlyphHeight = false,
|
||||||
|
Text = mod.Acronym
|
||||||
|
},
|
||||||
|
modIcon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Colour = OsuColour.Gray(84),
|
||||||
|
Size = new Vector2(45),
|
||||||
|
Icon = FontAwesome.Solid.Question
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -109,6 +168,14 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private void updateMod(IMod value)
|
private void updateMod(IMod value)
|
||||||
{
|
{
|
||||||
|
modSettingsChangeTracker?.Dispose();
|
||||||
|
|
||||||
|
if (value is Mod actualMod)
|
||||||
|
{
|
||||||
|
modSettingsChangeTracker = new ModSettingChangeTracker(new[] { actualMod });
|
||||||
|
modSettingsChangeTracker.SettingChanged = _ => updateExtendedInformation();
|
||||||
|
}
|
||||||
|
|
||||||
modAcronym.Text = value.Acronym;
|
modAcronym.Text = value.Acronym;
|
||||||
modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question;
|
modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question;
|
||||||
|
|
||||||
@ -125,11 +192,28 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
backgroundColour = colours.ForModType(value.Type);
|
backgroundColour = colours.ForModType(value.Type);
|
||||||
updateColour();
|
updateColour();
|
||||||
|
|
||||||
|
updateExtendedInformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateExtendedInformation()
|
||||||
|
{
|
||||||
|
bool showExtended = showExtendedInformation && !string.IsNullOrEmpty(mod.ExtendedIconInformation);
|
||||||
|
|
||||||
|
extendedContent.Alpha = showExtended ? 1 : 0;
|
||||||
|
extendedText.Text = mod.ExtendedIconInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateColour()
|
private void updateColour()
|
||||||
{
|
{
|
||||||
background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
||||||
|
extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
modSettingsChangeTracker?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -23,8 +24,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private readonly IMod mod;
|
private readonly IMod mod;
|
||||||
|
|
||||||
private readonly SpriteIcon background;
|
private Drawable background = null!;
|
||||||
private readonly SpriteIcon? modIcon;
|
private SpriteIcon? modIcon;
|
||||||
|
|
||||||
private Color4 activeForegroundColour;
|
private Color4 activeForegroundColour;
|
||||||
private Color4 inactiveForegroundColour;
|
private Color4 inactiveForegroundColour;
|
||||||
@ -36,19 +37,24 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
Size = new Vector2(DEFAULT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures, OsuColour colours, OverlayColourProvider? colourProvider)
|
||||||
|
{
|
||||||
FillFlowContainer contentFlow;
|
FillFlowContainer contentFlow;
|
||||||
ModSwitchTiny tinySwitch;
|
ModSwitchTiny tinySwitch;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new SpriteIcon
|
background = new Sprite
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
Texture = textures.Get("Icons/BeatmapDetails/mod-icon"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(DEFAULT_SIZE),
|
|
||||||
Icon = OsuIcon.ModBg
|
|
||||||
},
|
},
|
||||||
contentFlow = new FillFlowContainer
|
contentFlow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -78,11 +84,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
});
|
});
|
||||||
tinySwitch.Scale = new Vector2(0.3f);
|
tinySwitch.Scale = new Vector2(0.3f);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
|
||||||
private void load(OsuColour colours, OverlayColourProvider? colourProvider)
|
|
||||||
{
|
|
||||||
inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3;
|
inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3;
|
||||||
activeForegroundColour = colours.ForModType(mod.Type);
|
activeForegroundColour = colours.ForModType(mod.Type);
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Scoring.Legacy
|
namespace osu.Game.Scoring.Legacy
|
||||||
@ -16,6 +17,9 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
public static long GetDisplayScore(this ScoreInfo scoreInfo, ScoringMode mode)
|
public static long GetDisplayScore(this ScoreInfo scoreInfo, ScoringMode mode)
|
||||||
=> getDisplayScore(scoreInfo.Ruleset.OnlineID, scoreInfo.TotalScore, mode, scoreInfo.MaximumStatistics);
|
=> getDisplayScore(scoreInfo.Ruleset.OnlineID, scoreInfo.TotalScore, mode, scoreInfo.MaximumStatistics);
|
||||||
|
|
||||||
|
public static long GetDisplayScore(this SoloScoreInfo soloScoreInfo, ScoringMode mode)
|
||||||
|
=> getDisplayScore(soloScoreInfo.RulesetID, soloScoreInfo.TotalScore, mode, soloScoreInfo.MaximumStatistics);
|
||||||
|
|
||||||
private static long getDisplayScore(int rulesetId, long score, ScoringMode mode, IReadOnlyDictionary<HitResult, int> maximumStatistics)
|
private static long getDisplayScore(int rulesetId, long score, ScoringMode mode, IReadOnlyDictionary<HitResult, int> maximumStatistics)
|
||||||
{
|
{
|
||||||
if (mode == ScoringMode.Standardised)
|
if (mode == ScoringMode.Standardised)
|
||||||
@ -27,44 +31,37 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
.DefaultIfEmpty(0)
|
.DefaultIfEmpty(0)
|
||||||
.Sum();
|
.Sum();
|
||||||
|
|
||||||
// This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring.
|
return convertStandardisedToClassic(rulesetId, score, maxBasicJudgements);
|
||||||
// The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes.
|
|
||||||
double scaledRawScore = score / ScoreProcessor.MAX_SCORE;
|
|
||||||
|
|
||||||
return (long)Math.Round(Math.Pow(scaledRawScore * Math.Max(1, maxBasicJudgements), 2) * getStandardisedToClassicMultiplier(rulesetId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a ballpark multiplier which gives a similar "feel" for how large scores should get when displayed in "classic" mode.
|
/// Returns a ballpark "classic" score which gives a similar "feel" to stable.
|
||||||
/// This is different per ruleset to match the different algorithms used in the scoring implementation.
|
/// This is different per ruleset to match the different algorithms used in the scoring implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static double getStandardisedToClassicMultiplier(int rulesetId)
|
/// <remarks>
|
||||||
|
/// The coefficients chosen here were determined by a least-squares fit performed over all beatmaps
|
||||||
|
/// with the goal of minimising the relative error of maximum possible base score (without bonus).
|
||||||
|
/// The constant coefficients (100000, 1 / 10d) - while being detrimental to the least-squares fit - are forced,
|
||||||
|
/// so that every 10 points in standardised mode converts to at least 1 point in classic mode.
|
||||||
|
/// This is done to account for bonus judgements in a way that does not reorder scores.
|
||||||
|
/// </remarks>
|
||||||
|
private static long convertStandardisedToClassic(int rulesetId, long standardisedTotalScore, int objectCount)
|
||||||
{
|
{
|
||||||
double multiplier;
|
|
||||||
|
|
||||||
switch (rulesetId)
|
switch (rulesetId)
|
||||||
{
|
{
|
||||||
// For non-legacy rulesets, just go with the same as the osu! ruleset.
|
|
||||||
// This is arbitrary, but at least allows the setting to do something to the score.
|
|
||||||
default:
|
|
||||||
case 0:
|
case 0:
|
||||||
multiplier = 36;
|
return (long)Math.Round((objectCount * objectCount * 32.57 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE);
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
multiplier = 22;
|
return (long)Math.Round((objectCount * 1109 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE);
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
multiplier = 28;
|
return (long)Math.Round(Math.Pow(standardisedTotalScore / ScoreProcessor.MAX_SCORE * objectCount, 2) * 21.62 + standardisedTotalScore / 10d);
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
multiplier = 16;
|
default:
|
||||||
break;
|
return standardisedTotalScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
return multiplier;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int? GetCountGeki(this ScoreInfo scoreInfo)
|
public static int? GetCountGeki(this ScoreInfo scoreInfo)
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Scoring
|
|||||||
this.api = api;
|
this.api = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScoreInfo? CreateModel(ArchiveReader archive)
|
protected override ScoreInfo? CreateModel(ArchiveReader archive, ImportParameters parameters)
|
||||||
{
|
{
|
||||||
string name = archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase));
|
string name = archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
@ -52,14 +52,23 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
return new DatabasedLegacyScoreDecoder(rulesets, beatmaps()).Parse(stream).ScoreInfo;
|
return new DatabasedLegacyScoreDecoder(rulesets, beatmaps()).Parse(stream).ScoreInfo;
|
||||||
}
|
}
|
||||||
catch (LegacyScoreDecoder.BeatmapNotFoundException e)
|
catch (LegacyScoreDecoder.BeatmapNotFoundException notFound)
|
||||||
{
|
{
|
||||||
Logger.Log($@"Score '{archive.Name}' failed to import: no corresponding beatmap with the hash '{e.Hash}' could be found.", LoggingTarget.Database);
|
Logger.Log($@"Score '{archive.Name}' failed to import: no corresponding beatmap with the hash '{notFound.Hash}' could be found.", LoggingTarget.Database);
|
||||||
|
|
||||||
// In the case of a missing beatmap, let's attempt to resolve it and show a prompt to the user to download the required beatmap.
|
if (!parameters.Batch)
|
||||||
var req = new GetBeatmapRequest(new BeatmapInfo { MD5Hash = e.Hash });
|
{
|
||||||
req.Success += res => PostNotification?.Invoke(new MissingBeatmapNotification(res, archive, e.Hash));
|
// In the case of a missing beatmap, let's attempt to resolve it and show a prompt to the user to download the required beatmap.
|
||||||
api.Queue(req);
|
var req = new GetBeatmapRequest(new BeatmapInfo { MD5Hash = notFound.Hash });
|
||||||
|
req.Success += res => PostNotification?.Invoke(new MissingBeatmapNotification(res, archive, notFound.Hash));
|
||||||
|
api.Queue(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($@"Failed to parse headers of score '{archive.Name}': {e}.", LoggingTarget.Database);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -714,8 +714,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the dialog is already displayed, block exiting until the user explicitly makes a decision.
|
// if the dialog is already displayed, block exiting until the user explicitly makes a decision.
|
||||||
if (dialogOverlay.CurrentDialog is PromptForSaveDialog)
|
if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog)
|
||||||
|
{
|
||||||
|
saveDialog.Flash();
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNewBeatmap || HasUnsavedChanges)
|
if (isNewBeatmap || HasUnsavedChanges)
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user