mirror of
https://github.com/ppy/osu.git
synced 2025-02-09 05:23:03 +08:00
Merge branch 'master' into cognition
This commit is contained in:
commit
ca55fec053
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.810.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.805.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.810.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.6369583000323935d, 206, "diffcalc-test")]
|
[TestCase(6.7115569159190587d, 206, "diffcalc-test")]
|
||||||
[TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
|
[TestCase(1.4391311903612753d, 45, "zero-length-sliders")]
|
||||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||||
|
|
||||||
[TestCase(8.8816128335486386d, 206, "diffcalc-test")]
|
[TestCase(8.9757300665532966d, 206, "diffcalc-test")]
|
||||||
[TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
|
[TestCase(1.7437232654020756d, 45, "zero-length-sliders")]
|
||||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
||||||
|
|
||||||
[TestCase(6.6369583000323935d, 239, "diffcalc-test")]
|
[TestCase(6.7115569159190587d, 239, "diffcalc-test")]
|
||||||
[TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
|
[TestCase(1.4391311903612753d, 54, "zero-length-sliders")]
|
||||||
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
public static class AimEvaluator
|
public static class AimEvaluator
|
||||||
{
|
{
|
||||||
private const double wide_angle_multiplier = 1.5;
|
private const double wide_angle_multiplier = 1.5;
|
||||||
private const double acute_angle_multiplier = 2.0;
|
private const double acute_angle_multiplier = 1.95;
|
||||||
private const double slider_multiplier = 1.5;
|
private const double slider_multiplier = 1.35;
|
||||||
private const double velocity_change_multiplier = 0.75;
|
private const double velocity_change_multiplier = 0.75;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (osuLastObj.TravelTime != 0)
|
if (osuLastObj.BaseObject is Slider)
|
||||||
{
|
{
|
||||||
// Reward sliders based on velocity.
|
// Reward sliders based on velocity.
|
||||||
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||||
|
@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
private const double max_opacity_bonus = 0.4;
|
private const double max_opacity_bonus = 0.4;
|
||||||
private const double hidden_bonus = 0.2;
|
private const double hidden_bonus = 0.2;
|
||||||
|
|
||||||
|
private const double min_velocity = 0.5;
|
||||||
|
private const double slider_multiplier = 1.3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><description>distance between the previous and current object,</description></item>
|
/// <item><description>distance between a number of previous objects and the current object,</description></item>
|
||||||
/// <item><description>the visual opacity of the current object,</description></item>
|
/// <item><description>the visual opacity of the current object,</description></item>
|
||||||
|
/// <item><description>length and speed of the current object (for sliders),</description></item>
|
||||||
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -73,6 +77,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
if (hidden)
|
if (hidden)
|
||||||
result *= 1.0 + hidden_bonus;
|
result *= 1.0 + hidden_bonus;
|
||||||
|
|
||||||
|
double sliderBonus = 0.0;
|
||||||
|
|
||||||
|
if (osuCurrent.BaseObject is Slider osuSlider)
|
||||||
|
{
|
||||||
|
// Invert the scaling factor to determine the true travel distance independent of circle size.
|
||||||
|
double pixelTravelDistance = osuSlider.LazyTravelDistance / scalingFactor;
|
||||||
|
|
||||||
|
// Reward sliders based on velocity.
|
||||||
|
sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5);
|
||||||
|
|
||||||
|
// Longer sliders require more memorisation.
|
||||||
|
sliderBonus *= pixelTravelDistance;
|
||||||
|
|
||||||
|
// Nerf sliders with repeats, as less memorisation is required.
|
||||||
|
if (osuSlider.RepeatCount > 0)
|
||||||
|
sliderBonus /= (osuSlider.RepeatCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
result += sliderBonus * slider_multiplier;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModRelax))
|
if (mods.Any(h => h is OsuModRelax))
|
||||||
|
{
|
||||||
|
aimRating *= 0.9;
|
||||||
speedRating = 0.0;
|
speedRating = 0.0;
|
||||||
|
flashlightRating *= 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||||
@ -66,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
Math.Pow(baseCognitionPerformance, 1.1), 1.0 / 1.1
|
Math.Pow(baseCognitionPerformance, 1.1), 1.0 / 1.1
|
||||||
);
|
);
|
||||||
|
|
||||||
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
||||||
|
|
||||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||||
double drainRate = beatmap.Difficulty.DrainRate;
|
double drainRate = beatmap.Difficulty.DrainRate;
|
||||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||||
{
|
{
|
||||||
|
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||||
|
|
||||||
private double accuracy;
|
private double accuracy;
|
||||||
private int scoreMaxCombo;
|
private int scoreMaxCombo;
|
||||||
private int countGreat;
|
private int countGreat;
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
||||||
|
|
||||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModNoFail))
|
if (score.Mods.Any(m => m is OsuModNoFail))
|
||||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
||||||
@ -51,10 +53,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
if (score.Mods.Any(h => h is OsuModRelax))
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
{
|
{
|
||||||
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
// https://www.desmos.com/calculator/bc9eybdthb
|
||||||
effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
|
// we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0
|
||||||
|
// this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11)
|
||||||
|
double okMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 1.8) : 1.0);
|
||||||
|
double mehMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 5) : 1.0);
|
||||||
|
|
||||||
multiplier *= 0.6;
|
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
||||||
|
effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits);
|
||||||
}
|
}
|
||||||
|
|
||||||
double aimValue = computeAimValue(score, osuAttributes);
|
double aimValue = computeAimValue(score, osuAttributes);
|
||||||
@ -124,6 +130,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||||
|
|
||||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
@ -153,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
||||||
|
|
||||||
// Scale the speed value with # of 50s to punish doubletapping.
|
// Scale the speed value with # of 50s to punish doubletapping.
|
||||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||||
|
|
||||||
return speedValue;
|
return speedValue;
|
||||||
}
|
}
|
||||||
@ -266,6 +275,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
||||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||||
{
|
{
|
||||||
private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
/// <summary>
|
||||||
|
/// A distance by which all distances should be scaled in order to assume a uniform circle size.
|
||||||
|
/// </summary>
|
||||||
|
public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||||
|
|
||||||
private const int min_delta_time = 25;
|
private const int min_delta_time = 25;
|
||||||
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f;
|
||||||
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
private const float assumed_slider_radius = NORMALISED_RADIUS * 1.8f;
|
||||||
|
|
||||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
public double TravelDistance { get; private set; }
|
public double TravelDistance { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for a non-zero distance.
|
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for <see cref="Slider"/> objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TravelTime { get; private set; }
|
public double TravelTime { get; private set; }
|
||||||
|
|
||||||
@ -125,7 +129,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
if (BaseObject is Slider currentSlider)
|
if (BaseObject is Slider currentSlider)
|
||||||
{
|
{
|
||||||
computeSliderCursorPosition(currentSlider);
|
computeSliderCursorPosition(currentSlider);
|
||||||
TravelDistance = currentSlider.LazyTravelDistance;
|
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
||||||
|
TravelDistance = currentSlider.LazyTravelDistance * (float)Math.Pow(1 + currentSlider.RepeatCount / 2.5, 1.0 / 2.5);
|
||||||
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
float scalingFactor = normalised_radius / (float)BaseObject.Radius;
|
float scalingFactor = NORMALISED_RADIUS / (float)BaseObject.Radius;
|
||||||
|
|
||||||
if (BaseObject.Radius < 30)
|
if (BaseObject.Radius < 30)
|
||||||
{
|
{
|
||||||
@ -208,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
|
|
||||||
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
||||||
var currCursorPosition = slider.StackedPosition;
|
var currCursorPosition = slider.StackedPosition;
|
||||||
double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
double scalingFactor = NORMALISED_RADIUS / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||||
|
|
||||||
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
@ -236,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
else if (currMovementObj is SliderRepeat)
|
else if (currMovementObj is SliderRepeat)
|
||||||
{
|
{
|
||||||
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
||||||
requiredMovement = normalised_radius;
|
requiredMovement = NORMALISED_RADIUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currMovementLength > requiredMovement)
|
if (currMovementLength > requiredMovement)
|
||||||
@ -250,8 +255,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
if (i == slider.NestedHitObjects.Count - 1)
|
if (i == slider.NestedHitObjects.Count - 1)
|
||||||
slider.LazyEndPosition = currCursorPosition;
|
slider.LazyEndPosition = currCursorPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
slider.LazyTravelDistance *= (float)Math.Pow(1 + slider.RepeatCount / 2.5, 1.0 / 2.5); // Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
|
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
private double currentStrain;
|
private double currentStrain;
|
||||||
|
|
||||||
private double skillMultiplier => 23.25;
|
private double skillMultiplier => 23.55;
|
||||||
private double strainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
@ -206,7 +206,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
|
private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
|
||||||
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()) == expectedDistance);
|
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()), () => Is.EqualTo(expectedDistance));
|
||||||
|
|
||||||
private void assertDurationToDistance(double duration, float expectedDistance)
|
private void assertDurationToDistance(double duration, float expectedDistance)
|
||||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
|
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
|
||||||
|
@ -36,6 +36,23 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualityCaseSensitivity()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
// empty by default so let's set it..
|
||||||
|
beatmapSetA.Beatmaps.First().Metadata.AudioFile = "audio.mp3";
|
||||||
|
beatmapSetB.Beatmaps.First().Metadata.AudioFile = "audio.mp3";
|
||||||
|
|
||||||
|
addAudioFile(beatmapSetA, "abc", "AuDiO.mP3");
|
||||||
|
addAudioFile(beatmapSetB, "abc", "audio.mp3");
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAudioEqualitySameHash()
|
public void TestAudioEqualitySameHash()
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
AddStep("reset clock", () => Clock.Seek(0));
|
AddStep("reset clock", () => Clock.Seek(0));
|
||||||
|
|
||||||
AddStep("start clock", Clock.Start);
|
AddStep("start clock", () => Clock.Start());
|
||||||
AddAssert("clock running", () => Clock.IsRunning);
|
AddAssert("clock running", () => Clock.IsRunning);
|
||||||
|
|
||||||
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
|
AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
|
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
|
||||||
|
|
||||||
AddStep("start clock again", Clock.Start);
|
AddStep("start clock again", () => Clock.Start());
|
||||||
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,20 +76,20 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
AddStep("reset clock", () => Clock.Seek(0));
|
AddStep("reset clock", () => Clock.Seek(0));
|
||||||
|
|
||||||
AddStep("stop clock", Clock.Stop);
|
AddStep("stop clock", () => Clock.Stop());
|
||||||
AddAssert("clock stopped", () => !Clock.IsRunning);
|
AddAssert("clock stopped", () => !Clock.IsRunning);
|
||||||
|
|
||||||
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
|
AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
|
||||||
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
|
AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
|
||||||
|
|
||||||
AddStep("start clock again", Clock.Start);
|
AddStep("start clock again", () => Clock.Start());
|
||||||
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestClampWhenSeekOutsideBeatmapBounds()
|
public void TestClampWhenSeekOutsideBeatmapBounds()
|
||||||
{
|
{
|
||||||
AddStep("stop clock", Clock.Stop);
|
AddStep("stop clock", () => Clock.Stop());
|
||||||
|
|
||||||
AddStep("seek before start time", () => Clock.Seek(-1000));
|
AddStep("seek before start time", () => Clock.Seek(-1000));
|
||||||
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
|
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
|
||||||
|
@ -60,17 +60,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
// Forwards
|
// Forwards
|
||||||
AddStep("Seek(0)", () => Clock.Seek(0));
|
AddStep("Seek(0)", () => Clock.Seek(0));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
AddStep("Seek(33)", () => Clock.Seek(33));
|
AddStep("Seek(33)", () => Clock.Seek(33));
|
||||||
AddAssert("Time = 33", () => Clock.CurrentTime == 33);
|
checkTime(33);
|
||||||
AddStep("Seek(89)", () => Clock.Seek(89));
|
AddStep("Seek(89)", () => Clock.Seek(89));
|
||||||
AddAssert("Time = 89", () => Clock.CurrentTime == 89);
|
checkTime(89);
|
||||||
|
|
||||||
// Backwards
|
// Backwards
|
||||||
AddStep("Seek(25)", () => Clock.Seek(25));
|
AddStep("Seek(25)", () => Clock.Seek(25));
|
||||||
AddAssert("Time = 25", () => Clock.CurrentTime == 25);
|
checkTime(25);
|
||||||
AddStep("Seek(0)", () => Clock.Seek(0));
|
AddStep("Seek(0)", () => Clock.Seek(0));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -83,19 +83,19 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
|
AddStep("Seek(0), Snap", () => Clock.SeekSnapped(0));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
|
AddStep("Seek(50), Snap", () => Clock.SeekSnapped(50));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
|
AddStep("Seek(100), Snap", () => Clock.SeekSnapped(100));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
|
AddStep("Seek(175), Snap", () => Clock.SeekSnapped(175));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
|
AddStep("Seek(350), Snap", () => Clock.SeekSnapped(350));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
|
AddStep("Seek(400), Snap", () => Clock.SeekSnapped(400));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
|
AddStep("Seek(450), Snap", () => Clock.SeekSnapped(450));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -108,17 +108,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
|
AddStep("Seek(24), Snap", () => Clock.SeekSnapped(24));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
|
AddStep("Seek(26), Snap", () => Clock.SeekSnapped(26));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
|
AddStep("Seek(150), Snap", () => Clock.SeekSnapped(150));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
|
AddStep("Seek(170), Snap", () => Clock.SeekSnapped(170));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
|
AddStep("Seek(274), Snap", () => Clock.SeekSnapped(274));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
|
AddStep("Seek(276), Snap", () => Clock.SeekSnapped(276));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -130,15 +130,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 200", () => Clock.CurrentTime == 200);
|
checkTime(200);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekForward", () => Clock.SeekForward());
|
AddStep("SeekForward", () => Clock.SeekForward());
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -150,17 +150,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
reset();
|
reset();
|
||||||
|
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -174,28 +174,28 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(49)", () => Clock.Seek(49));
|
AddStep("Seek(49)", () => Clock.Seek(49));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
|
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(99)", () => Clock.Seek(99));
|
AddStep("Seek(99)", () => Clock.Seek(99));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
|
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 150);
|
checkTime(150);
|
||||||
AddStep("Seek(174)", () => Clock.Seek(174));
|
AddStep("Seek(174)", () => Clock.Seek(174));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("Seek(349)", () => Clock.Seek(349));
|
AddStep("Seek(349)", () => Clock.Seek(349));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("Seek(399)", () => Clock.Seek(399));
|
AddStep("Seek(399)", () => Clock.Seek(399));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("Seek(449)", () => Clock.Seek(449));
|
AddStep("Seek(449)", () => Clock.Seek(449));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -208,15 +208,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(450)", () => Clock.Seek(450));
|
AddStep("Seek(450)", () => Clock.Seek(450));
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 150", () => Clock.CurrentTime == 150);
|
checkTime(150);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekBackward", () => Clock.SeekBackward());
|
AddStep("SeekBackward", () => Clock.SeekBackward());
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -229,17 +229,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(450)", () => Clock.Seek(450));
|
AddStep("Seek(450)", () => Clock.Seek(450));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 350", () => Clock.CurrentTime == 350);
|
checkTime(350);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
checkTime(175);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
checkTime(100);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
checkTime(50);
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -253,16 +253,16 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Seek(451)", () => Clock.Seek(451));
|
AddStep("Seek(451)", () => Clock.Seek(451));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
|
AddStep("Seek(450.999)", () => Clock.Seek(450.999));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 450", () => Clock.CurrentTime == 450);
|
checkTime(450);
|
||||||
AddStep("Seek(401)", () => Clock.Seek(401));
|
AddStep("Seek(401)", () => Clock.Seek(401));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
|
AddStep("Seek(401.999)", () => Clock.Seek(401.999));
|
||||||
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
AddStep("SeekBackward, Snap", () => Clock.SeekBackward(true));
|
||||||
AddAssert("Time = 400", () => Clock.CurrentTime == 400);
|
checkTime(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -297,9 +297,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime);
|
AddAssert("Time < lastTime", () => Clock.CurrentTime < lastTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddAssert("Time = 0", () => Clock.CurrentTime == 0);
|
checkTime(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkTime(double expectedTime) => AddAssert($"Current time is {expectedTime}", () => Clock.CurrentTime, () => Is.EqualTo(expectedTime));
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
{
|
{
|
||||||
AddStep("Reset", () => Clock.Seek(0));
|
AddStep("Reset", () => Clock.Seek(0));
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private void pressAndCheckTime(Key key, double expectedTime)
|
private void pressAndCheckTime(Key key, double expectedTime)
|
||||||
{
|
{
|
||||||
AddStep($"press {key}", () => InputManager.Key(key));
|
AddStep($"press {key}", () => InputManager.Key(key));
|
||||||
AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1));
|
AddUntilStep($"time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime).Within(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,8 +199,8 @@ namespace osu.Game.Beatmaps
|
|||||||
Debug.Assert(x.BeatmapSet != null);
|
Debug.Assert(x.BeatmapSet != null);
|
||||||
Debug.Assert(y.BeatmapSet != null);
|
Debug.Assert(y.BeatmapSet != null);
|
||||||
|
|
||||||
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.Metadata))?.File.Hash;
|
string? fileHashX = x.BeatmapSet.GetFile(getFilename(x.Metadata))?.File.Hash;
|
||||||
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.Metadata))?.File.Hash;
|
string? fileHashY = y.BeatmapSet.GetFile(getFilename(y.Metadata))?.File.Hash;
|
||||||
|
|
||||||
return fileHashX == fileHashY;
|
return fileHashX == fileHashY;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps
|
|||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
// AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
|
// AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity.
|
||||||
var existingFileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase));
|
var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null;
|
||||||
string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);
|
string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo);
|
||||||
|
|
||||||
// ensure that two difficulties from the set don't point at the same beatmap file.
|
// ensure that two difficulties from the set don't point at the same beatmap file.
|
||||||
|
@ -84,13 +84,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
|
||||||
/// The path returned is relative to the user file storage.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filename">The name of the file to get the storage path of.</param>
|
|
||||||
public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
|
|
||||||
|
|
||||||
public bool Equals(BeatmapSetInfo? other)
|
public bool Equals(BeatmapSetInfo? other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(this, other)) return true;
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
33
osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs
Normal file
33
osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Models;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
public static class BeatmapSetInfoExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
||||||
|
/// The path returned is relative to the user file storage.
|
||||||
|
/// The lookup is case insensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model to operate on.</param>
|
||||||
|
/// <param name="filename">The name of the file to get the storage path of.</param>
|
||||||
|
public static string? GetPathForFile(this IHasRealmFiles model, string filename) => model.GetFile(filename)?.File.GetStoragePath();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the file usage for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
||||||
|
/// The path returned is relative to the user file storage.
|
||||||
|
/// The lookup is case insensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model to operate on.</param>
|
||||||
|
/// <param name="filename">The name of the file to get the storage path of.</param>
|
||||||
|
public static RealmNamedFileUsage? GetFile(this IHasRealmFiles model, string filename) =>
|
||||||
|
model.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
@ -3,16 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum BackgroundSource
|
public enum BackgroundSource
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(SkinSettingsStrings), nameof(SkinSettingsStrings.SkinSectionHeader))]
|
||||||
Skin,
|
Skin,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.BeatmapHeader))]
|
||||||
Beatmap,
|
Beatmap,
|
||||||
|
|
||||||
[Description("Beatmap (with storyboard / video)")]
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.BeatmapWithStoryboard))]
|
||||||
BeatmapWithStoryboard,
|
BeatmapWithStoryboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum DiscordRichPresenceMode
|
public enum DiscordRichPresenceMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.DiscordPresenceOff))]
|
||||||
Off,
|
Off,
|
||||||
|
|
||||||
[Description("Hide identifiable information")]
|
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.HideIdentifiableInformation))]
|
||||||
Limited,
|
Limited,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(OnlineSettingsStrings), nameof(OnlineSettingsStrings.DiscordPresenceFull))]
|
||||||
Full
|
Full
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum HUDVisibilityMode
|
public enum HUDVisibilityMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.NeverShowHUD))]
|
||||||
Never,
|
Never,
|
||||||
|
|
||||||
[Description("Hide during gameplay")]
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.HideDuringGameplay))]
|
||||||
HideDuringGameplay,
|
HideDuringGameplay,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.AlwaysShowHUD))]
|
||||||
Always
|
Always
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum RandomSelectAlgorithm
|
public enum RandomSelectAlgorithm
|
||||||
{
|
{
|
||||||
[Description("Never repeat")]
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.NeverRepeat))]
|
||||||
RandomPermutation,
|
RandomPermutation,
|
||||||
|
|
||||||
[Description("True Random")]
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.TrueRandom))]
|
||||||
Random
|
Random
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,23 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum ScalingMode
|
public enum ScalingMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScalingOff))]
|
||||||
Off,
|
Off,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleEverything))]
|
||||||
Everything,
|
Everything,
|
||||||
|
|
||||||
[Description("Excluding overlays")]
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleEverythingExcludingOverlays))]
|
||||||
ExcludeOverlays,
|
ExcludeOverlays,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(LayoutSettingsStrings), nameof(LayoutSettingsStrings.ScaleGameplay))]
|
||||||
Gameplay,
|
Gameplay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum ScreenshotFormat
|
public enum ScreenshotFormat
|
||||||
{
|
{
|
||||||
[Description("JPG (web-friendly)")]
|
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.Jpg))]
|
||||||
Jpg = 1,
|
Jpg = 1,
|
||||||
|
|
||||||
[Description("PNG (lossless)")]
|
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.Png))]
|
||||||
Png = 2
|
Png = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum SeasonalBackgroundMode
|
public enum SeasonalBackgroundMode
|
||||||
@ -10,16 +13,19 @@ namespace osu.Game.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seasonal backgrounds are shown regardless of season, if at all available.
|
/// Seasonal backgrounds are shown regardless of season, if at all available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.AlwaysSeasonalBackground))]
|
||||||
Always,
|
Always,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seasonal backgrounds are shown only during their corresponding season.
|
/// Seasonal backgrounds are shown only during their corresponding season.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SometimesSeasonalBackground))]
|
||||||
Sometimes,
|
Sometimes,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seasonal backgrounds are never shown.
|
/// Seasonal backgrounds are never shown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.NeverSeasonalBackground))]
|
||||||
Never
|
Never
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 osu.Game.Beatmaps;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
@ -11,8 +12,16 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasRealmFiles
|
public interface IHasRealmFiles
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Available files in this model, with locally filenames.
|
||||||
|
/// When performing lookups, consider using <see cref="BeatmapSetInfoExtensions.GetFile"/> or <see cref="BeatmapSetInfoExtensions.GetPathForFile"/> to do case-insensitive lookups.
|
||||||
|
/// </summary>
|
||||||
IList<RealmNamedFileUsage> Files { get; }
|
IList<RealmNamedFileUsage> Files { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A combined hash representing the model, based on the files it contains.
|
||||||
|
/// Implementation specific.
|
||||||
|
/// </summary>
|
||||||
string Hash { get; set; }
|
string Hash { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Database
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private RealmAccess realmAccess { get; set; } = null!;
|
private RealmAccess realmAccess { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved(canBeNull: true)] // canBeNull required while we remain on mono for mobile platforms.
|
||||||
private DesktopGameHost? desktopGameHost { get; set; }
|
private DesktopGameHost? desktopGameHost { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
@ -79,7 +80,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddFile(TModel item, Stream contents, string filename, Realm realm)
|
public void AddFile(TModel item, Stream contents, string filename, Realm realm)
|
||||||
{
|
{
|
||||||
var existing = item.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase));
|
var existing = item.GetFile(filename);
|
||||||
|
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
|
@ -173,6 +173,11 @@ namespace osu.Game.Database
|
|||||||
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
||||||
Filename += realm_extension;
|
Filename += realm_extension;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (!DebugUtils.IsNUnitRunning)
|
||||||
|
applyFilenameSchemaSuffix(ref Filename);
|
||||||
|
#endif
|
||||||
|
|
||||||
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
|
string newerVersionFilename = $"{Filename.Replace(realm_extension, string.Empty)}_newer_version{realm_extension}";
|
||||||
|
|
||||||
// Attempt to recover a newer database version if available.
|
// Attempt to recover a newer database version if available.
|
||||||
@ -212,6 +217,51 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Some developers may be annoyed if a newer version migration (ie. caused by testing a pull request)
|
||||||
|
/// cause their test database to be unusable with previous versions.
|
||||||
|
/// To get around this, store development databases against their realm version.
|
||||||
|
/// Note that this means changes made on newer realm versions will disappear.
|
||||||
|
/// </summary>
|
||||||
|
private void applyFilenameSchemaSuffix(ref string filename)
|
||||||
|
{
|
||||||
|
string originalFilename = filename;
|
||||||
|
|
||||||
|
filename = getVersionedFilename(schema_version);
|
||||||
|
|
||||||
|
// First check if the current realm version already exists...
|
||||||
|
if (storage.Exists(filename))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check for a previous version we can use as a base database to migrate from...
|
||||||
|
for (int i = schema_version - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
string previousFilename = getVersionedFilename(i);
|
||||||
|
|
||||||
|
if (storage.Exists(previousFilename))
|
||||||
|
{
|
||||||
|
copyPreviousVersion(previousFilename, filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, check for a non-versioned file exists (aka before this method was added)...
|
||||||
|
if (storage.Exists(originalFilename))
|
||||||
|
copyPreviousVersion(originalFilename, filename);
|
||||||
|
|
||||||
|
void copyPreviousVersion(string previousFilename, string newFilename)
|
||||||
|
{
|
||||||
|
using (var previous = storage.GetStream(previousFilename))
|
||||||
|
using (var current = storage.CreateFileSafely(newFilename))
|
||||||
|
{
|
||||||
|
Logger.Log(@$"Copying previous realm database {previousFilename} to {newFilename} for migration to schema version {schema_version}");
|
||||||
|
previous.CopyTo(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string getVersionedFilename(int version) => originalFilename.Replace(realm_extension, $"_{version}{realm_extension}");
|
||||||
|
}
|
||||||
|
|
||||||
private void attemptRecoverFromFile(string recoveryFilename)
|
private void attemptRecoverFromFile(string recoveryFilename)
|
||||||
{
|
{
|
||||||
Logger.Log($@"Performing recovery from {recoveryFilename}", LoggingTarget.Database);
|
Logger.Log($@"Performing recovery from {recoveryFilename}", LoggingTarget.Database);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -15,10 +13,11 @@ namespace osu.Game.Input.Bindings
|
|||||||
{
|
{
|
||||||
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
||||||
{
|
{
|
||||||
private readonly Drawable handler;
|
private readonly Drawable? handler;
|
||||||
private InputManager parentInputManager;
|
|
||||||
|
|
||||||
public GlobalActionContainer(OsuGameBase game)
|
private InputManager? parentInputManager;
|
||||||
|
|
||||||
|
public GlobalActionContainer(OsuGameBase? game)
|
||||||
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
||||||
{
|
{
|
||||||
if (game is IKeyBindingHandler<GlobalAction>)
|
if (game is IKeyBindingHandler<GlobalAction>)
|
||||||
@ -32,7 +31,10 @@ namespace osu.Game.Input.Bindings
|
|||||||
parentInputManager = GetContainingInputManager();
|
parentInputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: Do not change the order of key bindings in this list.
|
||||||
|
// It is used to decide the order of precedence (see note in DatabasedKeyBindingContainer).
|
||||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||||
|
.Concat(OverlayKeyBindings)
|
||||||
.Concat(EditorKeyBindings)
|
.Concat(EditorKeyBindings)
|
||||||
.Concat(InGameKeyBindings)
|
.Concat(InGameKeyBindings)
|
||||||
.Concat(SongSelectKeyBindings)
|
.Concat(SongSelectKeyBindings)
|
||||||
@ -40,25 +42,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(InputKey.F6, GlobalAction.ToggleNowPlaying),
|
|
||||||
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
|
|
||||||
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
|
|
||||||
new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
|
|
||||||
new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
|
||||||
|
|
||||||
new KeyBinding(InputKey.Escape, GlobalAction.Back),
|
|
||||||
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
|
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home),
|
|
||||||
|
|
||||||
new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious),
|
new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious),
|
||||||
new KeyBinding(InputKey.Down, GlobalAction.SelectNext),
|
new KeyBinding(InputKey.Down, GlobalAction.SelectNext),
|
||||||
|
|
||||||
@ -69,7 +52,31 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(InputKey.Enter, GlobalAction.Select),
|
new KeyBinding(InputKey.Enter, GlobalAction.Select),
|
||||||
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
|
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
|
||||||
|
|
||||||
|
new KeyBinding(InputKey.Escape, GlobalAction.Back),
|
||||||
|
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.R }, GlobalAction.RandomSkin),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.R }, GlobalAction.RandomSkin),
|
||||||
|
|
||||||
|
new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
|
||||||
|
new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> OverlayKeyBindings => new[]
|
||||||
|
{
|
||||||
|
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
|
||||||
|
new KeyBinding(InputKey.F6, GlobalAction.ToggleNowPlaying),
|
||||||
|
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> EditorKeyBindings => new[]
|
public IEnumerable<KeyBinding> EditorKeyBindings => new[]
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Input
|
namespace osu.Game.Input
|
||||||
{
|
{
|
||||||
@ -17,18 +18,20 @@ namespace osu.Game.Input
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mouse cursor will be free to move outside the game window.
|
/// The mouse cursor will be free to move outside the game window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(MouseSettingsStrings), nameof(MouseSettingsStrings.NeverConfine))]
|
||||||
Never,
|
Never,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mouse cursor will be locked to the window bounds during gameplay,
|
/// The mouse cursor will be locked to the window bounds during gameplay,
|
||||||
/// but may otherwise move freely.
|
/// but may otherwise move freely.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("During Gameplay")]
|
[LocalisableDescription(typeof(MouseSettingsStrings), nameof(MouseSettingsStrings.ConfineDuringGameplay))]
|
||||||
DuringGameplay,
|
DuringGameplay,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mouse cursor will always be locked to the window bounds while the game has focus.
|
/// The mouse cursor will always be locked to the window bounds while the game has focus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(MouseSettingsStrings), nameof(MouseSettingsStrings.AlwaysConfine))]
|
||||||
Always
|
Always
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,31 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
|
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Hide during gameplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HideDuringGameplay => new TranslatableString(getKey(@"hide_during_gameplay"), @"Hide during gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Always"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AlwaysShowHUD => new TranslatableString(getKey(@"always_show_hud"), @"Always");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverShowHUD => new TranslatableString(getKey(@"never_show_hud"), @"Never");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Standardised"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString StandardisedScoreDisplay => new TranslatableString(getKey(@"standardised_score_display"), @"Standardised");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Classic"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ClassicScoreDisplay => new TranslatableString(getKey(@"classic_score_display"), @"Classic");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString UseHardwareAcceleration => new TranslatableString(getKey(@"use_hardware_acceleration"), @"Use hardware acceleration");
|
public static LocalisableString UseHardwareAcceleration => new TranslatableString(getKey(@"use_hardware_acceleration"), @"Use hardware acceleration");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "JPG (web-friendly)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Jpg => new TranslatableString(getKey(@"jpg_web_friendly"), @"JPG (web-friendly)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "PNG (lossless)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Png => new TranslatableString(getKey(@"png_lossless"), @"PNG (lossless)");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
|
public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Overlays"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString OverlaysSection => new TranslatableString(getKey(@"overlays_section"), @"Overlays");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Song Select"
|
/// "Song Select"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -29,6 +29,26 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Excluding overlays"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScaleEverythingExcludingOverlays => new TranslatableString(getKey(@"scale_everything_excluding_overlays"), @"Excluding overlays");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Everything"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScaleEverything => new TranslatableString(getKey(@"scale_everything"), @"Everything");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Gameplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScaleGameplay => new TranslatableString(getKey(@"scale_gameplay"), @"Gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Off"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ScalingOff => new TranslatableString(getKey(@"scaling_off"), @"Off");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HighPrecisionPlatformWarning => new TranslatableString(getKey(@"high_precision_platform_warning"), @"This setting has known issues on your platform. If you encounter problems, it is recommended to adjust sensitivity externally and keep this disabled for now.");
|
public static LocalisableString HighPrecisionPlatformWarning => new TranslatableString(getKey(@"high_precision_platform_warning"), @"This setting has known issues on your platform. If you encounter problems, it is recommended to adjust sensitivity externally and keep this disabled for now.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Always"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AlwaysConfine => new TranslatableString(getKey(@"always_confine"), @"Always");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "During Gameplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ConfineDuringGameplay => new TranslatableString(getKey(@"confine_during_gameplay"), @"During Gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverConfine => new TranslatableString(getKey(@"never_confine"), @"Never");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
|
public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Hide identifiable information"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HideIdentifiableInformation => new TranslatableString(getKey(@"hide_identifiable_information"), @"Hide identifiable information");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Full"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DiscordPresenceFull => new TranslatableString(getKey(@"discord_presence_full"), @"Full");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Off"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DiscordPresenceOff => new TranslatableString(getKey(@"discord_presence_off"), @"Off");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,21 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
|
public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "None"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BorderNone => new TranslatableString(getKey(@"no_borders"), @"None");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Corners"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BorderCorners => new TranslatableString(getKey(@"corner_borders"), @"Corners");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Full"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BorderFull => new TranslatableString(getKey(@"full_borders"), @"Full");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,46 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
|
public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Beatmap (with storyboard / video)"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BeatmapWithStoryboard => new TranslatableString(getKey(@"beatmap_with_storyboard"), @"Beatmap (with storyboard / video)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Always"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AlwaysSeasonalBackground => new TranslatableString(getKey(@"always_seasonal_backgrounds"), @"Always");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverSeasonalBackground => new TranslatableString(getKey(@"never_seasonal_backgrounds"), @"Never");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Sometimes"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SometimesSeasonalBackground => new TranslatableString(getKey(@"sometimes_seasonal_backgrounds"), @"Sometimes");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Sequential"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SequentialHotkeyStyle => new TranslatableString(getKey(@"mods_sequential_hotkeys"), @"Sequential");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Classic"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ClassicHotkeyStyle => new TranslatableString(getKey(@"mods_classic_hotkeys"), @"Classic");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Never repeat"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NeverRepeat => new TranslatableString(getKey(@"never_repeat_random"), @"Never repeat");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "True Random"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TrueRandom => new TranslatableString(getKey(@"true_random"), @"True Random");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,120 +104,39 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int failureCount;
|
private int failureCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main API thread loop, which will continue to run until the game is shut down.
|
||||||
|
/// </summary>
|
||||||
private void run()
|
private void run()
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
switch (State.Value)
|
if (state.Value == APIState.Failing)
|
||||||
{
|
{
|
||||||
case APIState.Failing:
|
// To recover from a failing state, falling through and running the full reconnection process seems safest for now.
|
||||||
//todo: replace this with a ping request.
|
// This could probably be replaced with a ping-style request if we want to avoid the reconnection overheads.
|
||||||
log.Add(@"In a failing state, waiting a bit before we try again...");
|
log.Add($@"{nameof(APIAccess)} is in a failing state, waiting a bit before we try again...");
|
||||||
Thread.Sleep(5000);
|
Thread.Sleep(5000);
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsLoggedIn) goto case APIState.Connecting;
|
// Ensure that we have valid credentials.
|
||||||
|
// If not, setting the offline state will allow the game to prompt the user to provide new credentials.
|
||||||
|
if (!HasLogin)
|
||||||
|
{
|
||||||
|
state.Value = APIState.Offline;
|
||||||
|
Thread.Sleep(50);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (queue.Count == 0)
|
Debug.Assert(HasLogin);
|
||||||
{
|
|
||||||
log.Add(@"Queueing a ping request");
|
|
||||||
Queue(new GetUserRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
// Ensure that we are in an online state. If not, attempt a connect.
|
||||||
|
if (state.Value != APIState.Online)
|
||||||
|
{
|
||||||
|
attemptConnect();
|
||||||
|
|
||||||
case APIState.Offline:
|
if (state.Value != APIState.Online)
|
||||||
case APIState.Connecting:
|
continue;
|
||||||
// work to restore a connection...
|
|
||||||
if (!HasLogin)
|
|
||||||
{
|
|
||||||
state.Value = APIState.Offline;
|
|
||||||
Thread.Sleep(50);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Value = APIState.Connecting;
|
|
||||||
|
|
||||||
// save the username at this point, if the user requested for it to be.
|
|
||||||
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
|
||||||
|
|
||||||
if (!authentication.HasValidAccessToken)
|
|
||||||
{
|
|
||||||
LastLoginError = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
authentication.AuthenticateWithLogin(ProvidedUsername, password);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
//todo: this fails even on network-related issues. we should probably handle those differently.
|
|
||||||
LastLoginError = e;
|
|
||||||
log.Add(@"Login failed!");
|
|
||||||
password = null;
|
|
||||||
authentication.Clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var userReq = new GetUserRequest();
|
|
||||||
|
|
||||||
userReq.Failure += ex =>
|
|
||||||
{
|
|
||||||
if (ex is APIException)
|
|
||||||
{
|
|
||||||
LastLoginError = ex;
|
|
||||||
log.Add("Login failed on local user retrieval!");
|
|
||||||
Logout();
|
|
||||||
}
|
|
||||||
else if (ex is WebException webException && webException.Message == @"Unauthorized")
|
|
||||||
{
|
|
||||||
log.Add(@"Login no longer valid");
|
|
||||||
Logout();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
failConnectionProcess();
|
|
||||||
};
|
|
||||||
userReq.Success += u =>
|
|
||||||
{
|
|
||||||
localUser.Value = u;
|
|
||||||
|
|
||||||
// todo: save/pull from settings
|
|
||||||
localUser.Value.Status.Value = new UserStatusOnline();
|
|
||||||
|
|
||||||
failureCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!handleRequest(userReq))
|
|
||||||
{
|
|
||||||
failConnectionProcess();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// getting user's friends is considered part of the connection process.
|
|
||||||
var friendsReq = new GetFriendsRequest();
|
|
||||||
|
|
||||||
friendsReq.Failure += _ => failConnectionProcess();
|
|
||||||
friendsReq.Success += res =>
|
|
||||||
{
|
|
||||||
friends.AddRange(res);
|
|
||||||
|
|
||||||
//we're connected!
|
|
||||||
state.Value = APIState.Online;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!handleRequest(friendsReq))
|
|
||||||
{
|
|
||||||
failConnectionProcess();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
|
||||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
|
||||||
// before actually going online.
|
|
||||||
while (State.Value > APIState.Offline && State.Value < APIState.Online)
|
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hard bail if we can't get a valid access token.
|
// hard bail if we can't get a valid access token.
|
||||||
@ -227,31 +146,132 @@ namespace osu.Game.Online.API
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
processQueuedRequests();
|
||||||
{
|
|
||||||
APIRequest req;
|
|
||||||
|
|
||||||
lock (queue)
|
|
||||||
{
|
|
||||||
if (queue.Count == 0) break;
|
|
||||||
|
|
||||||
req = queue.Dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void failConnectionProcess()
|
/// <summary>
|
||||||
|
/// Dequeue from the queue and run each request synchronously until the queue is empty.
|
||||||
|
/// </summary>
|
||||||
|
private void processQueuedRequests()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
// if something went wrong during the connection process, we want to reset the state (but only if still connecting).
|
APIRequest req;
|
||||||
if (State.Value == APIState.Connecting)
|
|
||||||
state.Value = APIState.Failing;
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count == 0) return;
|
||||||
|
|
||||||
|
req = queue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequest(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// From a non-connected state, perform a full connection flow, obtaining OAuth tokens and populating the local user and friends.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method takes control of <see cref="state"/> and transitions from <see cref="APIState.Connecting"/> to either
|
||||||
|
/// - <see cref="APIState.Online"/> (successful connection)
|
||||||
|
/// - <see cref="APIState.Failing"/> (failed connection but retrying)
|
||||||
|
/// - <see cref="APIState.Offline"/> (failed and can't retry, clear credentials and require user interaction)
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>Whether the connection attempt was successful.</returns>
|
||||||
|
private void attemptConnect()
|
||||||
|
{
|
||||||
|
state.Value = APIState.Connecting;
|
||||||
|
|
||||||
|
if (localUser.IsDefault)
|
||||||
|
{
|
||||||
|
// Show a placeholder user if saved credentials are available.
|
||||||
|
// This is useful for storing local scores and showing a placeholder username after starting the game,
|
||||||
|
// until a valid connection has been established.
|
||||||
|
setLocalUser(new APIUser
|
||||||
|
{
|
||||||
|
Username = ProvidedUsername,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the username at this point, if the user requested for it to be.
|
||||||
|
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||||
|
|
||||||
|
if (!authentication.HasValidAccessToken)
|
||||||
|
{
|
||||||
|
LastLoginError = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
authentication.AuthenticateWithLogin(ProvidedUsername, password);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//todo: this fails even on network-related issues. we should probably handle those differently.
|
||||||
|
LastLoginError = e;
|
||||||
|
log.Add($@"Login failed for username {ProvidedUsername} ({LastLoginError.Message})!");
|
||||||
|
|
||||||
|
Logout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userReq = new GetUserRequest();
|
||||||
|
userReq.Failure += ex =>
|
||||||
|
{
|
||||||
|
if (ex is APIException)
|
||||||
|
{
|
||||||
|
LastLoginError = ex;
|
||||||
|
log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!");
|
||||||
|
Logout();
|
||||||
|
}
|
||||||
|
else if (ex is WebException webException && webException.Message == @"Unauthorized")
|
||||||
|
{
|
||||||
|
log.Add(@"Login no longer valid");
|
||||||
|
Logout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.Value = APIState.Failing;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
userReq.Success += user =>
|
||||||
|
{
|
||||||
|
// todo: save/pull from settings
|
||||||
|
user.Status.Value = new UserStatusOnline();
|
||||||
|
|
||||||
|
setLocalUser(user);
|
||||||
|
|
||||||
|
// we're connected!
|
||||||
|
state.Value = APIState.Online;
|
||||||
|
failureCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!handleRequest(userReq))
|
||||||
|
{
|
||||||
|
state.Value = APIState.Failing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var friendsReq = new GetFriendsRequest();
|
||||||
|
friendsReq.Failure += _ => state.Value = APIState.Failing;
|
||||||
|
friendsReq.Success += res => friends.AddRange(res);
|
||||||
|
|
||||||
|
if (!handleRequest(friendsReq))
|
||||||
|
{
|
||||||
|
state.Value = APIState.Failing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||||
|
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||||
|
// before actually going online.
|
||||||
|
while (State.Value == APIState.Connecting && !cancellationToken.IsCancellationRequested)
|
||||||
|
Thread.Sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
public void Perform(APIRequest request)
|
public void Perform(APIRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -327,8 +347,7 @@ namespace osu.Game.Online.API
|
|||||||
if (req.CompletionState != APIRequestCompletionState.Completed)
|
if (req.CompletionState != APIRequestCompletionState.Completed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// we could still be in initialisation, at which point we don't want to say we're Online yet.
|
// Reset failure count if this request succeeded.
|
||||||
if (IsLoggedIn) state.Value = APIState.Online;
|
|
||||||
failureCount = 0;
|
failureCount = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -402,7 +421,7 @@ namespace osu.Game.Online.API
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoggedIn => localUser.Value.Id > 1; // TODO: should this also be true if attempting to connect?
|
public bool IsLoggedIn => State.Value > APIState.Offline;
|
||||||
|
|
||||||
public void Queue(APIRequest request)
|
public void Queue(APIRequest request)
|
||||||
{
|
{
|
||||||
@ -442,7 +461,7 @@ namespace osu.Game.Online.API
|
|||||||
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
localUser.Value = createGuestUser();
|
setLocalUser(createGuestUser());
|
||||||
friends.Clear();
|
friends.Clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -452,6 +471,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private static APIUser createGuestUser() => new GuestUser();
|
private static APIUser createGuestUser() => new GuestUser();
|
||||||
|
|
||||||
|
private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -13,19 +13,16 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The local user.
|
/// The local user.
|
||||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindable<APIUser> LocalUser { get; }
|
IBindable<APIUser> LocalUser { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user's friends.
|
/// The user's friends.
|
||||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindableList<APIUser> Friends { get; }
|
IBindableList<APIUser> Friends { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current user's activity.
|
/// The current user's activity.
|
||||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBindable<UserActivity> Activity { get; }
|
IBindable<UserActivity> Activity { get; }
|
||||||
|
|
||||||
|
@ -196,6 +196,9 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||||
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
||||||
|
|
||||||
|
// The server will null out the end date upon the host joining the room, but the null value is never communicated to the client.
|
||||||
|
APIRoom.EndDate.Value = null;
|
||||||
|
|
||||||
Debug.Assert(LocalUser != null);
|
Debug.Assert(LocalUser != null);
|
||||||
addUserToAPIRoom(LocalUser);
|
addUserToAPIRoom(LocalUser);
|
||||||
|
|
||||||
|
@ -104,11 +104,11 @@ namespace osu.Game.Overlays
|
|||||||
filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged());
|
filterControl.CardSize.BindValueChanged(_ => onCardSizeChanged());
|
||||||
|
|
||||||
apiUser = api.LocalUser.GetBoundCopy();
|
apiUser = api.LocalUser.GetBoundCopy();
|
||||||
apiUser.BindValueChanged(_ =>
|
apiUser.BindValueChanged(_ => Schedule(() =>
|
||||||
{
|
{
|
||||||
if (api.IsLoggedIn)
|
if (api.IsLoggedIn)
|
||||||
addContentToResultsArea(Drawable.Empty());
|
addContentToResultsArea(Drawable.Empty());
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowWithSearch(string query)
|
public void ShowWithSearch(string query)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// 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.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods.Input
|
namespace osu.Game.Overlays.Mods.Input
|
||||||
{
|
{
|
||||||
@ -15,6 +17,7 @@ namespace osu.Game.Overlays.Mods.Input
|
|||||||
/// Individual letters in a row trigger the mods in a sequential fashion.
|
/// Individual letters in a row trigger the mods in a sequential fashion.
|
||||||
/// Uses <see cref="SequentialModHotkeyHandler"/>.
|
/// Uses <see cref="SequentialModHotkeyHandler"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SequentialHotkeyStyle))]
|
||||||
Sequential,
|
Sequential,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -22,6 +25,7 @@ namespace osu.Game.Overlays.Mods.Input
|
|||||||
/// One keybinding can toggle between what used to be <see cref="MultiMod"/>s on stable,
|
/// One keybinding can toggle between what used to be <see cref="MultiMod"/>s on stable,
|
||||||
/// and some mods in a column may not have any hotkeys at all.
|
/// and some mods in a column may not have any hotkeys at all.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.ClassicHotkeyStyle))]
|
||||||
Classic
|
Classic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -23,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
public GlobalKeyBindingsSection(GlobalActionContainer manager)
|
public GlobalKeyBindingsSection(GlobalActionContainer manager)
|
||||||
{
|
{
|
||||||
Add(new DefaultBindingsSubsection(manager));
|
Add(new DefaultBindingsSubsection(manager));
|
||||||
|
Add(new OverlayBindingsSubsection(manager));
|
||||||
Add(new AudioControlKeyBindingsSubsection(manager));
|
Add(new AudioControlKeyBindingsSubsection(manager));
|
||||||
Add(new SongSelectKeyBindingSubsection(manager));
|
Add(new SongSelectKeyBindingSubsection(manager));
|
||||||
Add(new InGameKeyBindingsSubsection(manager));
|
Add(new InGameKeyBindingsSubsection(manager));
|
||||||
@ -40,6 +39,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class OverlayBindingsSubsection : KeyBindingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => InputSettingsStrings.OverlaysSection;
|
||||||
|
|
||||||
|
public OverlayBindingsSubsection(GlobalActionContainer manager)
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
Defaults = manager.OverlayKeyBindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
|
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
|
protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.IO.FileAbstraction;
|
using osu.Game.IO.FileAbstraction;
|
||||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Checks
|
namespace osu.Game.Rulesets.Edit.Checks
|
||||||
|
@ -16,6 +16,8 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Scoring
|
namespace osu.Game.Rulesets.Scoring
|
||||||
{
|
{
|
||||||
@ -636,7 +638,10 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
public enum ScoringMode
|
public enum ScoringMode
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.StandardisedScoreDisplay))]
|
||||||
Standardised,
|
Standardised,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GameplaySettingsStrings), nameof(GameplaySettingsStrings.ClassicScoreDisplay))]
|
||||||
Classic
|
Classic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,20 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public enum PlayfieldBorderStyle
|
public enum PlayfieldBorderStyle
|
||||||
{
|
{
|
||||||
|
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.BorderNone))]
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.BorderCorners))]
|
||||||
Corners,
|
Corners,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.BorderFull))]
|
||||||
Full
|
Full
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
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;
|
||||||
@ -78,7 +77,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
// remove the previous background for now.
|
// remove the previous background for now.
|
||||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile);
|
var oldFile = set.GetFile(working.Value.Metadata.BackgroundFile);
|
||||||
|
|
||||||
using (var stream = source.OpenRead())
|
using (var stream = source.OpenRead())
|
||||||
{
|
{
|
||||||
@ -107,7 +106,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
// remove the previous audio track for now.
|
// remove the previous audio track for now.
|
||||||
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
|
||||||
var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.AudioFile);
|
var oldFile = set.GetFile(working.Value.Metadata.AudioFile);
|
||||||
|
|
||||||
using (var stream = source.OpenRead())
|
using (var stream = source.OpenRead())
|
||||||
{
|
{
|
||||||
|
@ -249,6 +249,9 @@ namespace osu.Game.Screens.Play
|
|||||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||||
GameplayClockContainer.Add(rulesetSkinProvider);
|
GameplayClockContainer.Add(rulesetSkinProvider);
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
rulesetSkinProvider.AddRange(new Drawable[]
|
rulesetSkinProvider.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
failAnimationLayer = new FailAnimation(DrawableRuleset)
|
failAnimationLayer = new FailAnimation(DrawableRuleset)
|
||||||
@ -279,6 +282,9 @@ namespace osu.Game.Screens.Play
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Configuration.AllowRestart)
|
if (Configuration.AllowRestart)
|
||||||
{
|
{
|
||||||
rulesetSkinProvider.Add(new HotkeyRetryOverlay
|
rulesetSkinProvider.Add(new HotkeyRetryOverlay
|
||||||
|
@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
@ -49,7 +50,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
protected override void Populate(SkinInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
|
protected override void Populate(SkinInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var skinInfoFile = model.Files.SingleOrDefault(f => f.Filename == skin_info_file);
|
var skinInfoFile = model.GetFile(skin_info_file);
|
||||||
|
|
||||||
if (skinInfoFile != null)
|
if (skinInfoFile != null)
|
||||||
{
|
{
|
||||||
@ -129,7 +130,7 @@ namespace osu.Game.Skinning
|
|||||||
authorLine,
|
authorLine,
|
||||||
};
|
};
|
||||||
|
|
||||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
var existingFile = item.GetFile(@"skin.ini");
|
||||||
|
|
||||||
if (existingFile == null)
|
if (existingFile == null)
|
||||||
{
|
{
|
||||||
@ -163,7 +164,7 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
|
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
|
||||||
|
|
||||||
var existingIni = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
var existingIni = item.GetFile(@"skin.ini");
|
||||||
if (existingIni != null)
|
if (existingIni != null)
|
||||||
item.Files.Remove(existingIni);
|
item.Files.Remove(existingIni);
|
||||||
|
|
||||||
@ -248,7 +249,7 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
string filename = @$"{drawableInfo.Key}.json";
|
string filename = @$"{drawableInfo.Key}.json";
|
||||||
|
|
||||||
var oldFile = s.Files.FirstOrDefault(f => f.Filename == filename);
|
var oldFile = s.GetFile(filename);
|
||||||
|
|
||||||
if (oldFile != null)
|
if (oldFile != null)
|
||||||
modelManager.ReplaceFile(oldFile, streamContent, s.Realm);
|
modelManager.ReplaceFile(oldFile, streamContent, s.Realm);
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
// 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.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;
|
||||||
@ -12,14 +8,14 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Graphics.Video;
|
using osu.Framework.Graphics.Video;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
|
||||||
|
|
||||||
namespace osu.Game.Storyboards.Drawables
|
namespace osu.Game.Storyboards.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableStoryboardVideo : CompositeDrawable
|
public class DrawableStoryboardVideo : CompositeDrawable
|
||||||
{
|
{
|
||||||
public readonly StoryboardVideo Video;
|
public readonly StoryboardVideo Video;
|
||||||
private Video video;
|
|
||||||
|
private Video? drawableVideo;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
|
||||||
@ -33,7 +29,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
|
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
|
||||||
{
|
{
|
||||||
string path = beatmap.Value.BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
|
string? path = beatmap.Value.BeatmapSetInfo?.GetPathForFile(Video.Path);
|
||||||
|
|
||||||
if (path == null)
|
if (path == null)
|
||||||
return;
|
return;
|
||||||
@ -43,7 +39,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
if (stream == null)
|
if (stream == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
InternalChild = video = new Video(stream, false)
|
InternalChild = drawableVideo = new Video(stream, false)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
@ -57,12 +53,12 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (video == null) return;
|
if (drawableVideo == null) return;
|
||||||
|
|
||||||
using (video.BeginAbsoluteSequence(Video.StartTime))
|
using (drawableVideo.BeginAbsoluteSequence(Video.StartTime))
|
||||||
{
|
{
|
||||||
Schedule(() => video.PlaybackPosition = Time.Current - Video.StartTime);
|
Schedule(() => drawableVideo.PlaybackPosition = Time.Current - Video.StartTime);
|
||||||
video.FadeIn(500);
|
drawableVideo.FadeIn(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
@ -90,12 +86,12 @@ namespace osu.Game.Storyboards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableStoryboard CreateDrawable(IReadOnlyList<Mod> mods = null) =>
|
public DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null) =>
|
||||||
new DrawableStoryboard(this, mods);
|
new DrawableStoryboard(this, mods);
|
||||||
|
|
||||||
public Texture GetTextureFromPath(string path, TextureStore textureStore)
|
public Texture? GetTextureFromPath(string path, TextureStore textureStore)
|
||||||
{
|
{
|
||||||
string storyboardPath = BeatmapInfo.BeatmapSet?.Files.FirstOrDefault(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
|
string? storyboardPath = BeatmapInfo.BeatmapSet?.GetPathForFile(path);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(storyboardPath))
|
if (!string.IsNullOrEmpty(storyboardPath))
|
||||||
return textureStore.Get(storyboardPath);
|
return textureStore.Get(storyboardPath);
|
||||||
|
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
// Specific to tests, the player can be disposed without OnExiting() ever being called.
|
// Specific to tests, the player can be disposed without OnExiting() ever being called.
|
||||||
// We should make sure that the gameplay session has finished even in this case.
|
// We should make sure that the gameplay session has finished even in this case.
|
||||||
if (LoadedBeatmapSuccessfully)
|
if (LoadedBeatmapSuccessfully)
|
||||||
spectatorClient.EndPlaying(GameplayState);
|
spectatorClient?.EndPlaying(GameplayState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.14.0" />
|
<PackageReference Include="Realm" Version="10.14.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.805.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.810.2" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.810.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
@ -61,8 +61,8 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.805.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.810.2" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.810.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.805.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.810.2" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user