mirror of
https://github.com/osukey/osukey.git
synced 2025-05-24 23:17:25 +09:00
Merge branch 'master' into no-more-difficulty-control-points-info
This commit is contained in:
commit
e49f1f6e6b
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@ -79,9 +79,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
||||||
# FIXME: Suppress warnings from templates project
|
# FIXME: Suppress warnings from templates project
|
||||||
dotnet codefilesanity | while read -r line; do
|
exit_code=0
|
||||||
echo "::warning::$line"
|
while read -r line; do
|
||||||
done
|
if [[ ! -z "$line" ]]; then
|
||||||
|
echo "::error::$line"
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
done <<< $(dotnet codefilesanity)
|
||||||
|
exit $exit_code
|
||||||
|
|
||||||
# Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded.
|
# Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded.
|
||||||
# - name: .NET Format (Dry Run)
|
# - name: .NET Format (Dry Run)
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1004.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1012.0" />
|
||||||
</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. -->
|
||||||
|
@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||||
|
|
||||||
[TestCase(4.050601681491468d, "diffcalc-test")]
|
[TestCase(4.0505463516206195d, "diffcalc-test")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
[TestCase(5.169743871843191d, "diffcalc-test")]
|
[TestCase(5.1696411260785498d, "diffcalc-test")]
|
||||||
public void TestClockRateAdjusted(double expected, string name)
|
public void TestClockRateAdjusted(double expected, string name)
|
||||||
=> Test(expected, name, new CatchModDoubleTime());
|
=> Test(expected, name, new CatchModDoubleTime());
|
||||||
|
|
||||||
|
@ -47,8 +47,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
|
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate),
|
||||||
GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate),
|
|
||||||
ScoreMultiplier = getScoreMultiplier(mods),
|
ScoreMultiplier = getScoreMultiplier(mods),
|
||||||
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
||||||
Skills = skills
|
Skills = skills
|
||||||
|
@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.6634445062299665d, "diffcalc-test")]
|
[TestCase(6.5867229481955389d, "diffcalc-test")]
|
||||||
[TestCase(1.0414203870195022d, "zero-length-sliders")]
|
[TestCase(1.0416315570967911d, "zero-length-sliders")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
[TestCase(8.3858089051603368d, "diffcalc-test")]
|
[TestCase(8.2730989071947896d, "diffcalc-test")]
|
||||||
[TestCase(1.2723279173428435d, "zero-length-sliders")]
|
[TestCase(1.2726413186221039d, "zero-length-sliders")]
|
||||||
public void TestClockRateAdjusted(double expected, string name)
|
public void TestClockRateAdjusted(double expected, string name)
|
||||||
=> Test(expected, name, new OsuModDoubleTime());
|
=> Test(expected, name, new OsuModDoubleTime());
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneSpinnerRotation : TestSceneOsuPlayer
|
public class TestSceneSpinnerRotation : TestSceneOsuPlayer
|
||||||
{
|
{
|
||||||
|
private const double spinner_start_time = 100;
|
||||||
|
private const double spinner_duration = 6000;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audioManager { get; set; }
|
private AudioManager audioManager { get; set; }
|
||||||
|
|
||||||
@ -77,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
double finalTrackerRotation = 0, trackerRotationTolerance = 0;
|
double finalTrackerRotation = 0, trackerRotationTolerance = 0;
|
||||||
double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0;
|
double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0;
|
||||||
|
|
||||||
addSeekStep(5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddStep("retrieve disc rotation", () =>
|
AddStep("retrieve disc rotation", () =>
|
||||||
{
|
{
|
||||||
finalTrackerRotation = drawableSpinner.RotationTracker.Rotation;
|
finalTrackerRotation = drawableSpinner.RotationTracker.Rotation;
|
||||||
@ -90,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
||||||
|
|
||||||
addSeekStep(2500);
|
addSeekStep(spinner_start_time + 2500);
|
||||||
AddAssert("disc rotation rewound",
|
AddAssert("disc rotation rewound",
|
||||||
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
||||||
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
||||||
@ -102,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
||||||
|
|
||||||
addSeekStep(5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddAssert("is disc rotation almost same",
|
AddAssert("is disc rotation almost same",
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance));
|
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance));
|
||||||
AddAssert("is symbol rotation almost same",
|
AddAssert("is symbol rotation almost same",
|
||||||
@ -140,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSpinnerNormalBonusRewinding()
|
public void TestSpinnerNormalBonusRewinding()
|
||||||
{
|
{
|
||||||
addSeekStep(1000);
|
addSeekStep(spinner_start_time + 1000);
|
||||||
|
|
||||||
AddAssert("player score matching expected bonus score", () =>
|
AddAssert("player score matching expected bonus score", () =>
|
||||||
{
|
{
|
||||||
@ -201,24 +204,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay
|
|
||||||
{
|
|
||||||
Frames = scoreReplay
|
|
||||||
.Frames
|
|
||||||
.Cast<OsuReplayFrame>()
|
|
||||||
.Select(replayFrame =>
|
|
||||||
{
|
|
||||||
var adjustedTime = replayFrame.Time * rate;
|
|
||||||
return new OsuReplayFrame(adjustedTime, replayFrame.Position, replayFrame.Actions.ToArray());
|
|
||||||
})
|
|
||||||
.Cast<ReplayFrame>()
|
|
||||||
.ToList()
|
|
||||||
};
|
|
||||||
|
|
||||||
private void addSeekStep(double time)
|
private void addSeekStep(double time)
|
||||||
{
|
{
|
||||||
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
||||||
|
|
||||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +229,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
new Spinner
|
new Spinner
|
||||||
{
|
{
|
||||||
Position = new Vector2(256, 192),
|
Position = new Vector2(256, 192),
|
||||||
EndTime = 6000,
|
StartTime = spinner_start_time,
|
||||||
|
Duration = spinner_duration
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
public double FlashlightRating { get; set; }
|
public double FlashlightRating { get; set; }
|
||||||
public double ApproachRate { get; set; }
|
public double ApproachRate { get; set; }
|
||||||
public double OverallDifficulty { get; set; }
|
public double OverallDifficulty { get; set; }
|
||||||
|
public double DrainRate { get; set; }
|
||||||
public int HitCircleCount { get; set; }
|
public int HitCircleCount { get; set; }
|
||||||
public int SpinnerCount { get; set; }
|
public int SpinnerCount { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
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(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
||||||
|
|
||||||
double preempt = (int)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;
|
||||||
|
|
||||||
int maxCombo = beatmap.HitObjects.Count;
|
int maxCombo = beatmap.HitObjects.Count;
|
||||||
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
|
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
|
||||||
@ -74,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
FlashlightRating = flashlightRating,
|
FlashlightRating = flashlightRating,
|
||||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||||
|
DrainRate = drainRate,
|
||||||
MaxCombo = maxCombo,
|
MaxCombo = maxCombo,
|
||||||
HitCircleCount = hitCirclesCount,
|
HitCircleCount = hitCirclesCount,
|
||||||
SpinnerCount = spinnerCount,
|
SpinnerCount = spinnerCount,
|
||||||
@ -100,8 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
HitWindows hitWindows = new OsuHitWindows();
|
HitWindows hitWindows = new OsuHitWindows();
|
||||||
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
||||||
|
|
||||||
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate;
|
||||||
hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
|
|
||||||
|
|
||||||
return new Skill[]
|
return new Skill[]
|
||||||
{
|
{
|
||||||
|
@ -40,9 +40,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
|
|
||||||
// Custom multipliers for NoFail and SpunOut.
|
|
||||||
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 = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||||
|
|
||||||
|
// Custom multipliers for NoFail and SpunOut.
|
||||||
if (mods.Any(m => m is OsuModNoFail))
|
if (mods.Any(m => m is OsuModNoFail))
|
||||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
|
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
|
||||||
|
|
||||||
@ -114,9 +114,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
double approachRateBonus = 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor;
|
double approachRateBonus = 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor;
|
||||||
|
|
||||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
if (mods.Any(m => m is OsuModBlinds))
|
||||||
if (mods.Any(h => h is OsuModHidden))
|
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * countMiss)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate);
|
||||||
|
else if (mods.Any(h => h is OsuModHidden))
|
||||||
|
{
|
||||||
|
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||||
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||||
|
}
|
||||||
|
|
||||||
aimValue *= approachRateBonus;
|
aimValue *= approachRateBonus;
|
||||||
|
|
||||||
@ -153,11 +157,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
speedValue *= 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor;
|
speedValue *= 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor;
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModHidden))
|
if (mods.Any(m => m is OsuModBlinds))
|
||||||
|
{
|
||||||
|
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||||
|
speedValue *= 1.12;
|
||||||
|
}
|
||||||
|
else if (mods.Any(m => m is OsuModHidden))
|
||||||
|
{
|
||||||
|
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||||
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||||
|
}
|
||||||
|
|
||||||
// Scale the speed value with accuracy and OD.
|
// Scale the speed value with accuracy and OD.
|
||||||
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
|
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (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.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||||
|
|
||||||
@ -189,8 +202,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
|
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
|
||||||
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
|
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModHidden))
|
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||||
|
if (mods.Any(m => m is OsuModBlinds))
|
||||||
|
accuracyValue *= 1.14;
|
||||||
|
else if (mods.Any(m => m is OsuModHidden))
|
||||||
accuracyValue *= 1.08;
|
accuracyValue *= 1.08;
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModFlashlight))
|
if (mods.Any(m => m is OsuModFlashlight))
|
||||||
accuracyValue *= 1.02;
|
accuracyValue *= 1.02;
|
||||||
|
|
||||||
|
@ -22,17 +22,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double SkillMultiplier => 26.25;
|
private double currentStrain = 1;
|
||||||
protected override double StrainDecayBase => 0.15;
|
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
private double skillMultiplier => 26.25;
|
||||||
|
private double strainDecayBase => 0.15;
|
||||||
|
|
||||||
|
private double strainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner)
|
if (current.BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||||
|
|
||||||
double result = 0;
|
double aimStrain = 0;
|
||||||
|
|
||||||
if (Previous.Count > 0)
|
if (Previous.Count > 0)
|
||||||
{
|
{
|
||||||
@ -46,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
||||||
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
||||||
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
||||||
result = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
aimStrain = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,11 +56,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
||||||
|
|
||||||
return Math.Max(
|
return Math.Max(
|
||||||
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
aimStrain + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
||||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||||
|
|
||||||
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
|
||||||
|
protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);
|
||||||
|
|
||||||
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
currentStrain *= strainDecay(current.DeltaTime);
|
||||||
|
currentStrain += strainValueOf(current) * skillMultiplier;
|
||||||
|
|
||||||
|
return currentStrain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double SkillMultiplier => 0.15;
|
private double skillMultiplier => 0.15;
|
||||||
protected override double StrainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
protected override double DecayWeight => 1.0;
|
protected override double DecayWeight => 1.0;
|
||||||
protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
|
protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
|
||||||
|
private double currentStrain = 1;
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
private double strainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner)
|
if (current.BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
@ -62,5 +63,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
return Math.Pow(smallDistNerf * result, 2.0);
|
return Math.Pow(smallDistNerf * result, 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
|
||||||
|
protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);
|
||||||
|
|
||||||
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
currentStrain *= strainDecay(current.DeltaTime);
|
||||||
|
currentStrain += strainValueOf(current) * skillMultiplier;
|
||||||
|
|
||||||
|
return currentStrain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Framework.Utils;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public abstract class OsuStrainSkill : StrainDecaySkill
|
public abstract class OsuStrainSkill : StrainSkill
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
||||||
|
@ -16,19 +16,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
public class Speed : OsuStrainSkill
|
public class Speed : OsuStrainSkill
|
||||||
{
|
{
|
||||||
private const double single_spacing_threshold = 125;
|
private const double single_spacing_threshold = 125;
|
||||||
|
private const double rhythm_multiplier = 0.75;
|
||||||
private const double angle_bonus_begin = 5 * Math.PI / 6;
|
private const int history_time_max = 5000; // 5 seconds of calculatingRhythmBonus max.
|
||||||
private const double pi_over_4 = Math.PI / 4;
|
|
||||||
private const double pi_over_2 = Math.PI / 2;
|
|
||||||
|
|
||||||
protected override double SkillMultiplier => 1400;
|
|
||||||
protected override double StrainDecayBase => 0.3;
|
|
||||||
protected override int ReducedSectionCount => 5;
|
|
||||||
protected override double DifficultyMultiplier => 1.04;
|
|
||||||
|
|
||||||
private const double min_speed_bonus = 75; // ~200BPM
|
private const double min_speed_bonus = 75; // ~200BPM
|
||||||
private const double speed_balancing_factor = 40;
|
private const double speed_balancing_factor = 40;
|
||||||
|
|
||||||
|
private double skillMultiplier => 1375;
|
||||||
|
private double strainDecayBase => 0.3;
|
||||||
|
|
||||||
|
private double currentStrain = 1;
|
||||||
|
private double currentRhythm = 1;
|
||||||
|
|
||||||
|
protected override int ReducedSectionCount => 5;
|
||||||
|
protected override double DifficultyMultiplier => 1.04;
|
||||||
|
protected override int HistoryLength => 32;
|
||||||
|
|
||||||
private readonly double greatWindow;
|
private readonly double greatWindow;
|
||||||
|
|
||||||
public Speed(Mod[] mods, double hitWindowGreat)
|
public Speed(Mod[] mods, double hitWindowGreat)
|
||||||
@ -37,52 +39,138 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
greatWindow = hitWindowGreat;
|
greatWindow = hitWindowGreat;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
/// <summary>
|
||||||
|
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
private double calculateRhythmBonus(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner)
|
if (current.BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
int previousIslandSize = 0;
|
||||||
var osuPrevious = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null;
|
|
||||||
|
|
||||||
double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
|
double rhythmComplexitySum = 0;
|
||||||
double strainTime = osuCurrent.StrainTime;
|
int islandSize = 1;
|
||||||
|
double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms
|
||||||
|
|
||||||
|
bool firstDeltaSwitch = false;
|
||||||
|
|
||||||
|
for (int i = Previous.Count - 2; i > 0; i--)
|
||||||
|
{
|
||||||
|
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1];
|
||||||
|
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i];
|
||||||
|
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1];
|
||||||
|
|
||||||
|
double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now
|
||||||
|
|
||||||
|
if (currHistoricalDecay != 0)
|
||||||
|
{
|
||||||
|
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
|
||||||
|
|
||||||
|
double currDelta = currObj.StrainTime;
|
||||||
|
double prevDelta = prevObj.StrainTime;
|
||||||
|
double lastDelta = lastObj.StrainTime;
|
||||||
|
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
|
||||||
|
|
||||||
|
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
|
||||||
|
|
||||||
|
windowPenalty = Math.Min(1, windowPenalty);
|
||||||
|
|
||||||
|
double effectiveRatio = windowPenalty * currRatio;
|
||||||
|
|
||||||
|
if (firstDeltaSwitch)
|
||||||
|
{
|
||||||
|
if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
|
||||||
|
{
|
||||||
|
if (islandSize < 7)
|
||||||
|
islandSize++; // island is still progressing, count size.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
||||||
|
effectiveRatio *= 0.125;
|
||||||
|
|
||||||
|
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
||||||
|
effectiveRatio *= 0.25;
|
||||||
|
|
||||||
|
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
||||||
|
effectiveRatio *= 0.25;
|
||||||
|
|
||||||
|
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
|
||||||
|
effectiveRatio *= 0.50;
|
||||||
|
|
||||||
|
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
|
||||||
|
effectiveRatio *= 0.125;
|
||||||
|
|
||||||
|
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
|
||||||
|
|
||||||
|
startRatio = effectiveRatio;
|
||||||
|
|
||||||
|
previousIslandSize = islandSize; // log the last island size.
|
||||||
|
|
||||||
|
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
|
||||||
|
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
|
||||||
|
|
||||||
|
islandSize = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
|
||||||
|
{
|
||||||
|
// Begin counting island until we change speed again.
|
||||||
|
firstDeltaSwitch = true;
|
||||||
|
startRatio = effectiveRatio;
|
||||||
|
islandSize = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||||
|
}
|
||||||
|
|
||||||
|
private double strainValueOf(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
if (current.BaseObject is Spinner)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// derive strainTime for calculation
|
||||||
|
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||||
|
var osuPrevObj = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null;
|
||||||
|
|
||||||
|
double strainTime = osuCurrObj.StrainTime;
|
||||||
double greatWindowFull = greatWindow * 2;
|
double greatWindowFull = greatWindow * 2;
|
||||||
double speedWindowRatio = strainTime / greatWindowFull;
|
double speedWindowRatio = strainTime / greatWindowFull;
|
||||||
|
|
||||||
// Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
|
// Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
|
||||||
if (osuPrevious != null && strainTime < greatWindowFull && osuPrevious.StrainTime > strainTime)
|
if (osuPrevObj != null && strainTime < greatWindowFull && osuPrevObj.StrainTime > strainTime)
|
||||||
strainTime = Interpolation.Lerp(osuPrevious.StrainTime, strainTime, speedWindowRatio);
|
strainTime = Interpolation.Lerp(osuPrevObj.StrainTime, strainTime, speedWindowRatio);
|
||||||
|
|
||||||
// Cap deltatime to the OD 300 hitwindow.
|
// Cap deltatime to the OD 300 hitwindow.
|
||||||
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
||||||
strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1);
|
strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1);
|
||||||
|
|
||||||
|
// derive speedBonus for calculation
|
||||||
double speedBonus = 1.0;
|
double speedBonus = 1.0;
|
||||||
|
|
||||||
if (strainTime < min_speed_bonus)
|
if (strainTime < min_speed_bonus)
|
||||||
speedBonus = 1 + Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
||||||
|
|
||||||
double angleBonus = 1.0;
|
double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance);
|
||||||
|
|
||||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin)
|
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
|
||||||
{
|
}
|
||||||
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57;
|
|
||||||
|
|
||||||
if (osuCurrent.Angle.Value < pi_over_2)
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
{
|
|
||||||
angleBonus = 1.28;
|
|
||||||
if (distance < 90 && osuCurrent.Angle.Value < pi_over_4)
|
|
||||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
|
|
||||||
else if (distance < 90)
|
|
||||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (1 + (speedBonus - 1) * 0.75)
|
protected override double CalculateInitialStrain(double time) => (currentStrain * currentRhythm) * strainDecay(time - Previous[0].StartTime);
|
||||||
* angleBonus
|
|
||||||
* (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5))
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
/ strainTime;
|
{
|
||||||
|
currentStrain *= strainDecay(current.DeltaTime);
|
||||||
|
currentStrain += strainValueOf(current) * skillMultiplier;
|
||||||
|
|
||||||
|
currentRhythm = calculateRhythmBonus(current);
|
||||||
|
|
||||||
|
return currentStrain * currentRhythm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
||||||
|
|
||||||
[TestCase(2.2867022617692685d, "diffcalc-test")]
|
[TestCase(2.2420075288523802d, "diffcalc-test")]
|
||||||
[TestCase(2.2867022617692685d, "diffcalc-test-strong")]
|
[TestCase(2.2420075288523802d, "diffcalc-test-strong")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
[TestCase(3.1704781712282624d, "diffcalc-test")]
|
[TestCase(3.134084469440479d, "diffcalc-test")]
|
||||||
[TestCase(3.1704781712282624d, "diffcalc-test-strong")]
|
[TestCase(3.134084469440479d, "diffcalc-test-strong")]
|
||||||
public void TestClockRateAdjusted(double expected, string name)
|
public void TestClockRateAdjusted(double expected, string name)
|
||||||
=> Test(expected, name, new TaikoModDoubleTime());
|
=> Test(expected, name, new TaikoModDoubleTime());
|
||||||
|
|
||||||
|
@ -47,10 +47,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!(original.Difficulty is TaikoMutliplierAppliedDifficulty))
|
if (!(original.Difficulty is TaikoMultiplierAppliedDifficulty))
|
||||||
{
|
{
|
||||||
// Rewrite the beatmap info to add the slider velocity multiplier
|
// Rewrite the beatmap info to add the slider velocity multiplier
|
||||||
original.Difficulty = new TaikoMutliplierAppliedDifficulty(original.Difficulty);
|
original.Difficulty = new TaikoMultiplierAppliedDifficulty(original.Difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
||||||
@ -191,15 +191,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
|
protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
|
||||||
|
|
||||||
private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty
|
private class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty
|
||||||
{
|
{
|
||||||
public TaikoMutliplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty)
|
public TaikoMultiplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty)
|
||||||
{
|
{
|
||||||
CopyFrom(difficulty);
|
CopyFrom(difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public TaikoMutliplierAppliedDifficulty()
|
public TaikoMultiplierAppliedDifficulty()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,14 +208,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
public override void CopyTo(BeatmapDifficulty other)
|
public override void CopyTo(BeatmapDifficulty other)
|
||||||
{
|
{
|
||||||
base.CopyTo(other);
|
base.CopyTo(other);
|
||||||
if (!(other is TaikoMutliplierAppliedDifficulty))
|
if (!(other is TaikoMultiplierAppliedDifficulty))
|
||||||
SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CopyFrom(IBeatmapDifficultyInfo other)
|
public override void CopyFrom(IBeatmapDifficultyInfo other)
|
||||||
{
|
{
|
||||||
base.CopyFrom(other);
|
base.CopyFrom(other);
|
||||||
if (!(other is TaikoMutliplierAppliedDifficulty))
|
if (!(other is TaikoMultiplierAppliedDifficulty))
|
||||||
SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,8 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
StaminaStrain = staminaRating,
|
StaminaStrain = staminaRating,
|
||||||
RhythmStrain = rhythmRating,
|
RhythmStrain = rhythmRating,
|
||||||
ColourStrain = colourRating,
|
ColourStrain = colourRating,
|
||||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
|
||||||
GreatHitWindow = (int)hitWindows.WindowFor(HitResult.Great) / clockRate,
|
|
||||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||||
Skills = skills
|
Skills = skills
|
||||||
};
|
};
|
||||||
|
@ -509,5 +509,17 @@ namespace osu.Game.Tests.Chat
|
|||||||
Assert.AreEqual(LinkAction.External, result.Action);
|
Assert.AreEqual(LinkAction.External, result.Action);
|
||||||
Assert.AreEqual("/relative", result.Argument);
|
Assert.AreEqual("/relative", result.Argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("https://dev.ppy.sh/home/changelog", "")]
|
||||||
|
[TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")]
|
||||||
|
public void TestChangelogLinks(string link, string expectedArg)
|
||||||
|
{
|
||||||
|
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
|
||||||
|
|
||||||
|
LinkDetails result = MessageFormatter.GetLinkDetails(link);
|
||||||
|
|
||||||
|
Assert.AreEqual(LinkAction.OpenChangelog, result.Action);
|
||||||
|
Assert.AreEqual(expectedArg, result.Argument);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
114
osu.Game.Tests/Database/FileStoreTests.cs
Normal file
114
osu.Game.Tests/Database/FileStoreTests.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Stores;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
public class FileStoreTests : RealmTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestImportFile()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var realm = realmFactory.Context;
|
||||||
|
var files = new RealmFileStore(realmFactory, storage);
|
||||||
|
|
||||||
|
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
|
||||||
|
|
||||||
|
realm.Write(() => files.Add(testData, realm));
|
||||||
|
|
||||||
|
Assert.True(files.Storage.Exists("0/05/054edec1d0211f624fed0cbca9d4f9400b0e491c43742af2c5b0abebf0c990d8"));
|
||||||
|
Assert.True(files.Storage.Exists(realm.All<RealmFile>().First().StoragePath));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestImportSameFileTwice()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var realm = realmFactory.Context;
|
||||||
|
var files = new RealmFileStore(realmFactory, storage);
|
||||||
|
|
||||||
|
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
|
||||||
|
|
||||||
|
realm.Write(() => files.Add(testData, realm));
|
||||||
|
realm.Write(() => files.Add(testData, realm));
|
||||||
|
|
||||||
|
Assert.AreEqual(1, realm.All<RealmFile>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDontPurgeReferenced()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var realm = realmFactory.Context;
|
||||||
|
var files = new RealmFileStore(realmFactory, storage);
|
||||||
|
|
||||||
|
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
|
||||||
|
|
||||||
|
var timer = new Stopwatch();
|
||||||
|
timer.Start();
|
||||||
|
|
||||||
|
realm.Write(() =>
|
||||||
|
{
|
||||||
|
// attach the file to an arbitrary beatmap
|
||||||
|
var beatmapSet = CreateBeatmapSet(CreateRuleset());
|
||||||
|
|
||||||
|
beatmapSet.Files.Add(new RealmNamedFileUsage(file, "arbitrary.resource"));
|
||||||
|
|
||||||
|
realm.Add(beatmapSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.Log($"Import complete at {timer.ElapsedMilliseconds}");
|
||||||
|
|
||||||
|
string path = file.StoragePath;
|
||||||
|
|
||||||
|
Assert.True(realm.All<RealmFile>().Any());
|
||||||
|
Assert.True(files.Storage.Exists(path));
|
||||||
|
|
||||||
|
files.Cleanup();
|
||||||
|
Logger.Log($"Cleanup complete at {timer.ElapsedMilliseconds}");
|
||||||
|
|
||||||
|
Assert.True(realm.All<RealmFile>().Any());
|
||||||
|
Assert.True(file.IsValid);
|
||||||
|
Assert.True(files.Storage.Exists(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPurgeUnreferenced()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var realm = realmFactory.Context;
|
||||||
|
var files = new RealmFileStore(realmFactory, storage);
|
||||||
|
|
||||||
|
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
|
||||||
|
|
||||||
|
string path = file.StoragePath;
|
||||||
|
|
||||||
|
Assert.True(realm.All<RealmFile>().Any());
|
||||||
|
Assert.True(files.Storage.Exists(path));
|
||||||
|
|
||||||
|
files.Cleanup();
|
||||||
|
|
||||||
|
Assert.False(realm.All<RealmFile>().Any());
|
||||||
|
Assert.False(file.IsValid);
|
||||||
|
Assert.False(files.Storage.Exists(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
// 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;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
213
osu.Game.Tests/Database/RealmLiveTests.cs
Normal file
213
osu.Game.Tests/Database/RealmLiveTests.cs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
public class RealmLiveTests : RealmTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestLiveCastability()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
RealmLive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
|
||||||
|
|
||||||
|
ILive<IBeatmapInfo> iBeatmap = beatmap;
|
||||||
|
|
||||||
|
Assert.AreEqual(0, iBeatmap.Value.Length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestValueAccessWithOpenContext()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
|
||||||
|
|
||||||
|
liveBeatmap = beatmap.ToLive();
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
|
||||||
|
Debug.Assert(liveBeatmap != null);
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
using (realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var resolved = liveBeatmap.Value;
|
||||||
|
|
||||||
|
Assert.IsTrue(resolved.Realm.IsClosed);
|
||||||
|
Assert.IsTrue(resolved.IsValid);
|
||||||
|
|
||||||
|
// can access properties without a crash.
|
||||||
|
Assert.IsFalse(resolved.Hidden);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScopedReadWithoutContext()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
|
||||||
|
|
||||||
|
liveBeatmap = beatmap.ToLive();
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
|
||||||
|
Debug.Assert(liveBeatmap != null);
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
liveBeatmap.PerformRead(beatmap =>
|
||||||
|
{
|
||||||
|
Assert.IsTrue(beatmap.IsValid);
|
||||||
|
Assert.IsFalse(beatmap.Hidden);
|
||||||
|
});
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScopedWriteWithoutContext()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
|
||||||
|
|
||||||
|
liveBeatmap = beatmap.ToLive();
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
|
||||||
|
Debug.Assert(liveBeatmap != null);
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; });
|
||||||
|
liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); });
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestValueAccessWithoutOpenContextFails()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
|
||||||
|
|
||||||
|
liveBeatmap = beatmap.ToLive();
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
|
||||||
|
Debug.Assert(liveBeatmap != null);
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
var unused = liveBeatmap.Value;
|
||||||
|
});
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLiveAssumptions()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
int changesTriggered = 0;
|
||||||
|
|
||||||
|
using (var updateThreadContext = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
updateThreadContext.All<RealmBeatmap>().SubscribeForNotifications(gotChange);
|
||||||
|
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var ruleset = CreateRuleset();
|
||||||
|
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
|
||||||
|
|
||||||
|
// add a second beatmap to ensure that a full refresh occurs below.
|
||||||
|
// not just a refresh from the resolved Live.
|
||||||
|
threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
|
||||||
|
|
||||||
|
liveBeatmap = beatmap.ToLive();
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
|
||||||
|
|
||||||
|
Debug.Assert(liveBeatmap != null);
|
||||||
|
|
||||||
|
// not yet seen by main context
|
||||||
|
Assert.AreEqual(0, updateThreadContext.All<RealmBeatmap>().Count());
|
||||||
|
Assert.AreEqual(0, changesTriggered);
|
||||||
|
|
||||||
|
var resolved = liveBeatmap.Value;
|
||||||
|
|
||||||
|
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
||||||
|
Assert.AreEqual(2, updateThreadContext.All<RealmBeatmap>().Count());
|
||||||
|
Assert.AreEqual(1, changesTriggered);
|
||||||
|
|
||||||
|
// even though the realm that this instance was resolved for was closed, it's still valid.
|
||||||
|
Assert.IsTrue(resolved.Realm.IsClosed);
|
||||||
|
Assert.IsTrue(resolved.IsValid);
|
||||||
|
|
||||||
|
// can access properties without a crash.
|
||||||
|
Assert.IsFalse(resolved.Hidden);
|
||||||
|
|
||||||
|
updateThreadContext.Write(r =>
|
||||||
|
{
|
||||||
|
// can use with the main context.
|
||||||
|
r.Remove(resolved);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void gotChange(IRealmCollection<RealmBeatmap> sender, ChangeSet changes, Exception error)
|
||||||
|
{
|
||||||
|
changesTriggered++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Nito.AsyncEx;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Models;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
@ -28,42 +29,109 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
protected void RunTestWithRealm(Action<RealmContextFactory, Storage> testAction, [CallerMemberName] string caller = "")
|
protected void RunTestWithRealm(Action<RealmContextFactory, Storage> testAction, [CallerMemberName] string caller = "")
|
||||||
{
|
{
|
||||||
AsyncContext.Run(() =>
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
|
||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
host.Run(new RealmTestGame(() =>
|
||||||
|
|
||||||
using (var realmFactory = new RealmContextFactory(testStorage, caller))
|
|
||||||
{
|
{
|
||||||
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
|
var testStorage = storage.GetStorageForDirectory(caller);
|
||||||
testAction(realmFactory, testStorage);
|
|
||||||
|
|
||||||
realmFactory.Dispose();
|
using (var realmFactory = new RealmContextFactory(testStorage, caller))
|
||||||
|
{
|
||||||
|
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
|
||||||
|
testAction(realmFactory, testStorage);
|
||||||
|
|
||||||
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
realmFactory.Dispose();
|
||||||
realmFactory.Compact();
|
|
||||||
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
|
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
||||||
}
|
realmFactory.Compact();
|
||||||
});
|
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void RunTestWithRealmAsync(Func<RealmContextFactory, Storage, Task> testAction, [CallerMemberName] string caller = "")
|
protected void RunTestWithRealmAsync(Func<RealmContextFactory, Storage, Task> testAction, [CallerMemberName] string caller = "")
|
||||||
{
|
{
|
||||||
AsyncContext.Run(async () =>
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
|
||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
host.Run(new RealmTestGame(async () =>
|
||||||
|
|
||||||
using (var realmFactory = new RealmContextFactory(testStorage, caller))
|
|
||||||
{
|
{
|
||||||
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
|
var testStorage = storage.GetStorageForDirectory(caller);
|
||||||
await testAction(realmFactory, testStorage);
|
|
||||||
|
|
||||||
realmFactory.Dispose();
|
using (var realmFactory = new RealmContextFactory(testStorage, caller))
|
||||||
|
{
|
||||||
|
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
|
||||||
|
await testAction(realmFactory, testStorage);
|
||||||
|
|
||||||
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
realmFactory.Dispose();
|
||||||
realmFactory.Compact();
|
|
||||||
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
|
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
||||||
|
realmFactory.Compact();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static RealmBeatmapSet CreateBeatmapSet(RealmRuleset ruleset)
|
||||||
|
{
|
||||||
|
RealmFile createRealmFile() => new RealmFile { Hash = Guid.NewGuid().ToString().ComputeSHA2Hash() };
|
||||||
|
|
||||||
|
var metadata = new RealmBeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = "My Love",
|
||||||
|
Artist = "Kuba Oms"
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapSet = new RealmBeatmapSet
|
||||||
|
{
|
||||||
|
Beatmaps =
|
||||||
|
{
|
||||||
|
new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Easy", },
|
||||||
|
new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Normal", },
|
||||||
|
new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Hard", },
|
||||||
|
new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Insane", }
|
||||||
|
},
|
||||||
|
Files =
|
||||||
|
{
|
||||||
|
new RealmNamedFileUsage(createRealmFile(), "test [easy].osu"),
|
||||||
|
new RealmNamedFileUsage(createRealmFile(), "test [normal].osu"),
|
||||||
|
new RealmNamedFileUsage(createRealmFile(), "test [hard].osu"),
|
||||||
|
new RealmNamedFileUsage(createRealmFile(), "test [insane].osu"),
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
beatmapSet.Files.Add(new RealmNamedFileUsage(createRealmFile(), $"hitsound{i}.mp3"));
|
||||||
|
|
||||||
|
foreach (var b in beatmapSet.Beatmaps)
|
||||||
|
b.BeatmapSet = beatmapSet;
|
||||||
|
|
||||||
|
return beatmapSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static RealmRuleset CreateRuleset() =>
|
||||||
|
new RealmRuleset(0, "osu!", "osu", true);
|
||||||
|
|
||||||
|
private class RealmTestGame : Framework.Game
|
||||||
|
{
|
||||||
|
public RealmTestGame(Func<Task> work)
|
||||||
|
{
|
||||||
|
// ReSharper disable once AsyncVoidLambda
|
||||||
|
Scheduler.Add(async () =>
|
||||||
|
{
|
||||||
|
await work().ConfigureAwait(true);
|
||||||
|
Exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmTestGame(Action work)
|
||||||
|
{
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
work();
|
||||||
|
Exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
|
private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
|
||||||
|
@ -168,14 +168,14 @@ namespace osu.Game.Tests.Online
|
|||||||
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
|
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapModelDownloader CreateBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider api, GameHost host)
|
protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager manager, IAPIProvider api, GameHost host)
|
||||||
{
|
{
|
||||||
return new TestBeatmapModelDownloader(modelManager, api, host);
|
return new TestBeatmapModelDownloader(manager, api, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
|
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
|
||||||
{
|
{
|
||||||
public TestBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost)
|
public TestBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost)
|
||||||
: base(modelManager, apiProvider, gameHost)
|
: base(modelManager, apiProvider, gameHost)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
@ -65,10 +64,9 @@ namespace osu.Game.Tests.Skins
|
|||||||
|
|
||||||
public new void TriggerSourceChanged() => base.TriggerSourceChanged();
|
public new void TriggerSourceChanged() => base.TriggerSourceChanged();
|
||||||
|
|
||||||
protected override void OnSourceChanged()
|
protected override void RefreshSources()
|
||||||
{
|
{
|
||||||
ResetSources();
|
SetSources(sources);
|
||||||
sources.ForEach(AddSource);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -18,16 +19,19 @@ namespace osu.Game.Tests.Visual.Audio
|
|||||||
{
|
{
|
||||||
public class TestSceneAudioFilter : OsuTestScene
|
public class TestSceneAudioFilter : OsuTestScene
|
||||||
{
|
{
|
||||||
private OsuSpriteText lowpassText;
|
private OsuSpriteText lowPassText;
|
||||||
private AudioFilter lowpassFilter;
|
private AudioFilter lowPassFilter;
|
||||||
|
|
||||||
private OsuSpriteText highpassText;
|
private OsuSpriteText highPassText;
|
||||||
private AudioFilter highpassFilter;
|
private AudioFilter highPassFilter;
|
||||||
|
|
||||||
private Track track;
|
private Track track;
|
||||||
|
|
||||||
private WaveformTestBeatmap beatmap;
|
private WaveformTestBeatmap beatmap;
|
||||||
|
|
||||||
|
private OsuSliderBar<int> lowPassSlider;
|
||||||
|
private OsuSliderBar<int> highPassSlider;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -38,53 +42,89 @@ namespace osu.Game.Tests.Visual.Audio
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
lowpassFilter = new AudioFilter(audio.TrackMixer),
|
lowPassFilter = new AudioFilter(audio.TrackMixer),
|
||||||
highpassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass),
|
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass),
|
||||||
lowpassText = new OsuSpriteText
|
lowPassText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(20),
|
||||||
Text = $"Low Pass: {lowpassFilter.Cutoff.Value}hz",
|
Text = $"Low Pass: {lowPassFilter.Cutoff}hz",
|
||||||
Font = new FontUsage(size: 40)
|
Font = new FontUsage(size: 40)
|
||||||
},
|
},
|
||||||
new OsuSliderBar<int>
|
lowPassSlider = new OsuSliderBar<int>
|
||||||
{
|
{
|
||||||
Width = 500,
|
Width = 500,
|
||||||
Height = 50,
|
Height = 50,
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(20),
|
||||||
Current = { BindTarget = lowpassFilter.Cutoff }
|
Current = new BindableInt
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = AudioFilter.MAX_LOWPASS_CUTOFF,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
highpassText = new OsuSpriteText
|
highPassText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(20),
|
||||||
Text = $"High Pass: {highpassFilter.Cutoff.Value}hz",
|
Text = $"High Pass: {highPassFilter.Cutoff}hz",
|
||||||
Font = new FontUsage(size: 40)
|
Font = new FontUsage(size: 40)
|
||||||
},
|
},
|
||||||
new OsuSliderBar<int>
|
highPassSlider = new OsuSliderBar<int>
|
||||||
{
|
{
|
||||||
Width = 500,
|
Width = 500,
|
||||||
Height = 50,
|
Height = 50,
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(20),
|
||||||
Current = { BindTarget = highpassFilter.Cutoff }
|
Current = new BindableInt
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = AudioFilter.MAX_LOWPASS_CUTOFF,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
lowpassFilter.Cutoff.ValueChanged += e => lowpassText.Text = $"Low Pass: {e.NewValue}hz";
|
|
||||||
highpassFilter.Cutoff.ValueChanged += e => highpassText.Text = $"High Pass: {e.NewValue}hz";
|
lowPassSlider.Current.ValueChanged += e =>
|
||||||
|
{
|
||||||
|
lowPassText.Text = $"Low Pass: {e.NewValue}hz";
|
||||||
|
lowPassFilter.Cutoff = e.NewValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
highPassSlider.Current.ValueChanged += e =>
|
||||||
|
{
|
||||||
|
highPassText.Text = $"High Pass: {e.NewValue}hz";
|
||||||
|
highPassFilter.Cutoff = e.NewValue;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Overrides of Drawable
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
highPassSlider.Current.Value = highPassFilter.Cutoff;
|
||||||
|
lowPassSlider.Current.Value = lowPassFilter.Cutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("Play Track", () => track.Start());
|
AddStep("Play Track", () => track.Start());
|
||||||
|
|
||||||
|
AddStep("Reset filters", () =>
|
||||||
|
{
|
||||||
|
lowPassFilter.Cutoff = AudioFilter.MAX_LOWPASS_CUTOFF;
|
||||||
|
highPassFilter.Cutoff = 0;
|
||||||
|
});
|
||||||
|
|
||||||
waitTrackPlay();
|
waitTrackPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLowPass()
|
public void TestLowPassSweep()
|
||||||
{
|
{
|
||||||
AddStep("Filter Sweep", () =>
|
AddStep("Filter Sweep", () =>
|
||||||
{
|
{
|
||||||
lowpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
|
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
|
||||||
.CutoffTo(0, 2000, Easing.OutCubic);
|
.CutoffTo(0, 2000, Easing.OutCubic);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,7 +132,7 @@ namespace osu.Game.Tests.Visual.Audio
|
|||||||
|
|
||||||
AddStep("Filter Sweep (reverse)", () =>
|
AddStep("Filter Sweep (reverse)", () =>
|
||||||
{
|
{
|
||||||
lowpassFilter.CutoffTo(0).Then()
|
lowPassFilter.CutoffTo(0).Then()
|
||||||
.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
|
.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -101,11 +141,11 @@ namespace osu.Game.Tests.Visual.Audio
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHighPass()
|
public void TestHighPassSweep()
|
||||||
{
|
{
|
||||||
AddStep("Filter Sweep", () =>
|
AddStep("Filter Sweep", () =>
|
||||||
{
|
{
|
||||||
highpassFilter.CutoffTo(0).Then()
|
highPassFilter.CutoffTo(0).Then()
|
||||||
.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
|
.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,7 +153,7 @@ namespace osu.Game.Tests.Visual.Audio
|
|||||||
|
|
||||||
AddStep("Filter Sweep (reverse)", () =>
|
AddStep("Filter Sweep (reverse)", () =>
|
||||||
{
|
{
|
||||||
highpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
|
highPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
|
||||||
.CutoffTo(0, 2000, Easing.OutCubic);
|
.CutoffTo(0, 2000, Easing.OutCubic);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("wait for editor load", () => editor != null);
|
AddUntilStep("wait for editor load", () => editor != null);
|
||||||
|
|
||||||
|
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
|
||||||
|
|
||||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||||
|
|
||||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
||||||
@ -41,11 +43,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
AddStep("Save and exit", () =>
|
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||||
{
|
|
||||||
InputManager.Keys(PlatformAction.Save);
|
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||||
InputManager.Key(Key.Escape);
|
|
||||||
});
|
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
||||||
|
|
||||||
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddUntilStep("Wait for editor load", () => editor != null);
|
AddUntilStep("Wait for editor load", () => editor != null);
|
||||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||||
|
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
checkFrameCount(0);
|
checkFrameCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRatePreservedWhenTimeNotProgressing()
|
||||||
|
{
|
||||||
|
AddStep("set manual clock rate", () => manualClock.Rate = 1);
|
||||||
|
seekManualTo(5000);
|
||||||
|
createStabilityContainer();
|
||||||
|
checkRate(1);
|
||||||
|
|
||||||
|
seekManualTo(10000);
|
||||||
|
checkRate(1);
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 3);
|
||||||
|
checkRate(1);
|
||||||
|
|
||||||
|
seekManualTo(5000);
|
||||||
|
checkRate(-1);
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 3);
|
||||||
|
checkRate(-1);
|
||||||
|
|
||||||
|
seekManualTo(10000);
|
||||||
|
checkRate(1);
|
||||||
|
}
|
||||||
|
|
||||||
private const int max_frames_catchup = 50;
|
private const int max_frames_catchup = 50;
|
||||||
|
|
||||||
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
|
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
|
||||||
@ -116,6 +140,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private void checkFrameCount(int frames) =>
|
private void checkFrameCount(int frames) =>
|
||||||
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
|
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
|
||||||
|
|
||||||
|
private void checkRate(double rate) =>
|
||||||
|
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
|
||||||
|
|
||||||
public class ClockConsumingChild : CompositeDrawable
|
public class ClockConsumingChild : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly OsuSpriteText text;
|
private readonly OsuSpriteText text;
|
||||||
|
@ -90,8 +90,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
CreateTest(() =>
|
CreateTest(() =>
|
||||||
{
|
{
|
||||||
AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
|
AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
|
||||||
AddStep("set storyboard duration to 1.3s", () => currentStoryboardDuration = 1300);
|
|
||||||
|
// Fail occurs at 164ms with the provided beatmap.
|
||||||
|
// Fail animation runs for 2.5s realtime but the gameplay time change is *variable* due to the frequency transform being applied, so we need a bit of lenience.
|
||||||
|
AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
31
osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
Normal file
31
osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
|
{
|
||||||
|
public class TestSceneStartupImport : OsuGameTestScene
|
||||||
|
{
|
||||||
|
private string importFilename;
|
||||||
|
|
||||||
|
protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { importFilename });
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("Prepare import beatmap", () => importFilename = TestResources.GetTestBeatmapForImport());
|
||||||
|
|
||||||
|
base.SetUpSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestImportCreatedNotification()
|
||||||
|
{
|
||||||
|
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ImportProgressNotification>().Count() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,12 +32,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
private TestResultsScreen resultsScreen;
|
private TestResultsScreen resultsScreen;
|
||||||
private int currentScoreId;
|
private int currentScoreId;
|
||||||
private bool requestComplete;
|
private bool requestComplete;
|
||||||
|
private int totalCount;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
currentScoreId = 0;
|
currentScoreId = 0;
|
||||||
requestComplete = false;
|
requestComplete = false;
|
||||||
|
totalCount = 0;
|
||||||
bindHandler();
|
bindHandler();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,7 +55,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
});
|
});
|
||||||
|
|
||||||
createResults(() => userScore);
|
createResults(() => userScore);
|
||||||
waitForDisplay();
|
|
||||||
|
|
||||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded);
|
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded);
|
||||||
}
|
}
|
||||||
@ -62,7 +63,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
public void TestShowNullUserScore()
|
public void TestShowNullUserScore()
|
||||||
{
|
{
|
||||||
createResults();
|
createResults();
|
||||||
waitForDisplay();
|
|
||||||
|
|
||||||
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
||||||
}
|
}
|
||||||
@ -79,7 +79,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
});
|
});
|
||||||
|
|
||||||
createResults(() => userScore);
|
createResults(() => userScore);
|
||||||
waitForDisplay();
|
|
||||||
|
|
||||||
AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1);
|
AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1);
|
||||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded);
|
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded);
|
||||||
@ -91,7 +90,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
AddStep("bind delayed handler", () => bindHandler(true));
|
AddStep("bind delayed handler", () => bindHandler(true));
|
||||||
|
|
||||||
createResults();
|
createResults();
|
||||||
waitForDisplay();
|
|
||||||
|
|
||||||
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
||||||
}
|
}
|
||||||
@ -100,7 +98,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
public void TestFetchWhenScrolledToTheRight()
|
public void TestFetchWhenScrolledToTheRight()
|
||||||
{
|
{
|
||||||
createResults();
|
createResults();
|
||||||
waitForDisplay();
|
|
||||||
|
|
||||||
AddStep("bind delayed handler", () => bindHandler(true));
|
AddStep("bind delayed handler", () => bindHandler(true));
|
||||||
|
|
||||||
@ -131,7 +128,6 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
});
|
});
|
||||||
|
|
||||||
createResults(() => userScore);
|
createResults(() => userScore);
|
||||||
waitForDisplay();
|
|
||||||
|
|
||||||
AddStep("bind delayed handler", () => bindHandler(true));
|
AddStep("bind delayed handler", () => bindHandler(true));
|
||||||
|
|
||||||
@ -161,13 +157,15 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => resultsScreen.ChildrenOfType<ScorePanelList>().FirstOrDefault()?.AllPanelsVisible == true);
|
waitForDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForDisplay()
|
private void waitForDisplay()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for request to complete", () => requestComplete);
|
AddUntilStep("wait for load to complete", () =>
|
||||||
AddUntilStep("wait for panels to be visible", () => resultsScreen.ChildrenOfType<ScorePanelList>().FirstOrDefault()?.AllPanelsVisible == true);
|
requestComplete
|
||||||
|
&& resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount
|
||||||
|
&& resultsScreen.ScorePanelList.AllPanelsVisible);
|
||||||
AddWaitStep("wait for display", 5);
|
AddWaitStep("wait for display", 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +201,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
triggerFail(s);
|
triggerFail(s);
|
||||||
else
|
else
|
||||||
triggerSuccess(s, createUserResponse(userScore));
|
triggerSuccess(s, createUserResponse(userScore));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IndexPlaylistScoresRequest i:
|
case IndexPlaylistScoresRequest i:
|
||||||
@ -248,6 +247,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
totalCount++;
|
||||||
|
|
||||||
for (int i = 1; i <= scores_per_result; i++)
|
for (int i = 1; i <= scores_per_result; i++)
|
||||||
{
|
{
|
||||||
multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore
|
multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore
|
||||||
@ -285,6 +286,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
},
|
},
|
||||||
Statistics = userScore.Statistics
|
Statistics = userScore.Statistics
|
||||||
});
|
});
|
||||||
|
|
||||||
|
totalCount += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCursor(multiplayerUserScore.ScoresAround.Lower);
|
addCursor(multiplayerUserScore.ScoresAround.Lower);
|
||||||
@ -325,6 +328,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
{ HitResult.Great, 300 }
|
{ HitResult.Great, 300 }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
totalCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCursor(result);
|
addCursor(result);
|
||||||
|
@ -53,7 +53,11 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Sample string", "Change something for a mod")]
|
[SettingSource("Sample string", "Change something for a mod")]
|
||||||
public Bindable<string> StringBindable { get; } = new Bindable<string>();
|
public Bindable<string> StringBindable { get; } = new Bindable<string>
|
||||||
|
{
|
||||||
|
Default = string.Empty,
|
||||||
|
Value = "Sample text"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TestEnum
|
private enum TestEnum
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Handlers.Tablet;
|
using osu.Framework.Input.Handlers.Tablet;
|
||||||
@ -21,6 +22,9 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
private TestTabletHandler tabletHandler;
|
private TestTabletHandler tabletHandler;
|
||||||
private TabletSettings settings;
|
private TabletSettings settings;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
|
@ -142,6 +142,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
AddStep("store selected beatmap", () => selected = Beatmap.Value);
|
||||||
|
|
||||||
|
AddUntilStep("wait for beatmaps to load", () => songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>().Any());
|
||||||
|
|
||||||
AddStep("select next and enter", () =>
|
AddStep("select next and enter", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
|
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
|
||||||
@ -599,10 +601,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
FilterableDifficultyIcon difficultyIcon = null;
|
FilterableDifficultyIcon difficultyIcon = null;
|
||||||
AddStep("Find an icon", () =>
|
AddUntilStep("Find an icon", () =>
|
||||||
{
|
{
|
||||||
difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
return (difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
||||||
.First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex());
|
.FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex())) != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Click on a difficulty", () =>
|
AddStep("Click on a difficulty", () =>
|
||||||
@ -765,10 +767,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
FilterableGroupedDifficultyIcon groupIcon = null;
|
FilterableGroupedDifficultyIcon groupIcon = null;
|
||||||
AddStep("Find group icon for different ruleset", () =>
|
AddUntilStep("Find group icon for different ruleset", () =>
|
||||||
{
|
{
|
||||||
groupIcon = set.ChildrenOfType<FilterableGroupedDifficultyIcon>()
|
return (groupIcon = set.ChildrenOfType<FilterableGroupedDifficultyIcon>()
|
||||||
.First(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3);
|
.FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3)) != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
|
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
|
||||||
|
@ -163,7 +163,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
||||||
|
|
||||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
|
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +170,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public void TestDeleteViaDatabase()
|
public void TestDeleteViaDatabase()
|
||||||
{
|
{
|
||||||
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
||||||
|
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
||||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID));
|
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod));
|
AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod));
|
||||||
AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod));
|
AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod));
|
||||||
|
|
||||||
var mutlipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() };
|
var multipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() };
|
||||||
AddStep(@"Add multiple Mods", () => changeMods(mutlipleIncrementMods));
|
AddStep(@"Add multiple Mods", () => changeMods(multipleIncrementMods));
|
||||||
AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(mutlipleIncrementMods));
|
AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleIncrementMods));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneRoundedButton : OsuTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
RoundedButton button = null;
|
||||||
|
|
||||||
|
AddStep("create button", () => Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.DarkGray
|
||||||
|
},
|
||||||
|
button = new RoundedButton
|
||||||
|
{
|
||||||
|
Width = 400,
|
||||||
|
Text = "Test button",
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Action = () => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddToggleStep("toggle disabled", disabled => button.Action = disabled ? (Action)null : () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
|
|
||||||
public DateTextBox()
|
public DateTextBox()
|
||||||
{
|
{
|
||||||
base.Current = new Bindable<string>();
|
base.Current = new Bindable<string>(string.Empty);
|
||||||
|
|
||||||
((OsuTextBox)Control).OnCommit += (sender, newText) =>
|
((OsuTextBox)Control).OnCommit += (sender, newText) =>
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Tournament.Models
|
|||||||
{
|
{
|
||||||
public List<SeedingBeatmap> Beatmaps = new List<SeedingBeatmap>();
|
public List<SeedingBeatmap> Beatmaps = new List<SeedingBeatmap>();
|
||||||
|
|
||||||
public Bindable<string> Mod = new Bindable<string>();
|
public Bindable<string> Mod = new Bindable<string>(string.Empty);
|
||||||
|
|
||||||
public Bindable<int> Seed = new BindableInt
|
public Bindable<int> Seed = new BindableInt
|
||||||
{
|
{
|
||||||
|
@ -14,8 +14,8 @@ namespace osu.Game.Tournament.Models
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class TournamentRound
|
public class TournamentRound
|
||||||
{
|
{
|
||||||
public readonly Bindable<string> Name = new Bindable<string>();
|
public readonly Bindable<string> Name = new Bindable<string>(string.Empty);
|
||||||
public readonly Bindable<string> Description = new Bindable<string>();
|
public readonly Bindable<string> Description = new Bindable<string>(string.Empty);
|
||||||
|
|
||||||
public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 };
|
public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 };
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
|
|
||||||
private readonly Bindable<int?> beatmapId = new Bindable<int?>();
|
private readonly Bindable<int?> beatmapId = new Bindable<int?>();
|
||||||
|
|
||||||
private readonly Bindable<string> mods = new Bindable<string>();
|
private readonly Bindable<string> mods = new Bindable<string>(string.Empty);
|
||||||
|
|
||||||
private readonly Container drawableContainer;
|
private readonly Container drawableContainer;
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
|
|
||||||
private readonly Bindable<int?> beatmapId = new Bindable<int?>();
|
private readonly Bindable<int?> beatmapId = new Bindable<int?>();
|
||||||
|
|
||||||
private readonly Bindable<string> score = new Bindable<string>();
|
private readonly Bindable<string> score = new Bindable<string>(string.Empty);
|
||||||
|
|
||||||
private readonly Container drawableContainer;
|
private readonly Container drawableContainer;
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
score2Text.X = Math.Max(5 + score2Text.DrawWidth / 2, score2Bar.DrawWidth);
|
score2Text.X = Math.Max(5 + score2Text.DrawWidth / 2, score2Bar.DrawWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MatchScoreCounter : ScoreCounter
|
private class MatchScoreCounter : CommaSeparatedScoreCounter
|
||||||
{
|
{
|
||||||
private OsuSpriteText displayedSpriteText;
|
private OsuSpriteText displayedSpriteText;
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using ManagedBass.Fx;
|
using ManagedBass.Fx;
|
||||||
using osu.Framework.Audio.Mixing;
|
using osu.Framework.Audio.Mixing;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Audio.Effects
|
namespace osu.Game.Audio.Effects
|
||||||
@ -21,10 +20,25 @@ namespace osu.Game.Audio.Effects
|
|||||||
private readonly BQFParameters filter;
|
private readonly BQFParameters filter;
|
||||||
private readonly BQFType type;
|
private readonly BQFType type;
|
||||||
|
|
||||||
|
private bool isAttached;
|
||||||
|
|
||||||
|
private int cutoff;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current cutoff of this filter.
|
/// The cutoff frequency of this filter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BindableNumber<int> Cutoff { get; }
|
public int Cutoff
|
||||||
|
{
|
||||||
|
get => cutoff;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == cutoff)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cutoff = value;
|
||||||
|
updateFilter(cutoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A Component that implements a BASS FX BiQuad Filter Effect.
|
/// A Component that implements a BASS FX BiQuad Filter Effect.
|
||||||
@ -36,102 +50,96 @@ namespace osu.Game.Audio.Effects
|
|||||||
this.mixer = mixer;
|
this.mixer = mixer;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
int initialCutoff;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case BQFType.HighPass:
|
|
||||||
initialCutoff = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BQFType.LowPass:
|
|
||||||
initialCutoff = MAX_LOWPASS_CUTOFF;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
initialCutoff = 500; // A default that should ensure audio remains audible for other filters.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cutoff = new BindableNumber<int>(initialCutoff)
|
|
||||||
{
|
|
||||||
MinValue = 1,
|
|
||||||
MaxValue = MAX_LOWPASS_CUTOFF
|
|
||||||
};
|
|
||||||
|
|
||||||
filter = new BQFParameters
|
filter = new BQFParameters
|
||||||
{
|
{
|
||||||
lFilter = type,
|
lFilter = type,
|
||||||
fCenter = initialCutoff,
|
|
||||||
fBandwidth = 0,
|
fBandwidth = 0,
|
||||||
fQ = 0.7f // This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0)
|
// This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0)
|
||||||
|
fQ = 0.7f
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't start attached if this is low-pass or high-pass filter (as they have special auto-attach/detach logic)
|
Cutoff = getInitialCutoff(type);
|
||||||
if (type != BQFType.LowPass && type != BQFType.HighPass)
|
|
||||||
attachFilter();
|
|
||||||
|
|
||||||
Cutoff.ValueChanged += updateFilter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attachFilter()
|
private int getInitialCutoff(BQFType type)
|
||||||
{
|
{
|
||||||
Debug.Assert(!mixer.Effects.Contains(filter));
|
switch (type)
|
||||||
mixer.Effects.Add(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void detachFilter()
|
|
||||||
{
|
|
||||||
Debug.Assert(mixer.Effects.Contains(filter));
|
|
||||||
mixer.Effects.Remove(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFilter(ValueChangedEvent<int> cutoff)
|
|
||||||
{
|
|
||||||
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
|
|
||||||
if (type == BQFType.LowPass)
|
|
||||||
{
|
{
|
||||||
if (cutoff.NewValue >= MAX_LOWPASS_CUTOFF)
|
case BQFType.HighPass:
|
||||||
{
|
return 1;
|
||||||
detachFilter();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cutoff.OldValue >= MAX_LOWPASS_CUTOFF && cutoff.NewValue < MAX_LOWPASS_CUTOFF)
|
case BQFType.LowPass:
|
||||||
attachFilter();
|
return MAX_LOWPASS_CUTOFF;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 500; // A default that should ensure audio remains audible for other filters.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFilter(int newValue)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case BQFType.LowPass:
|
||||||
|
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
|
||||||
|
if (newValue >= MAX_LOWPASS_CUTOFF)
|
||||||
|
{
|
||||||
|
ensureDetached();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
|
||||||
|
case BQFType.HighPass:
|
||||||
|
if (newValue <= 1)
|
||||||
|
{
|
||||||
|
ensureDetached();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
|
ensureAttached();
|
||||||
if (type == BQFType.HighPass)
|
|
||||||
{
|
|
||||||
if (cutoff.NewValue <= 1)
|
|
||||||
{
|
|
||||||
detachFilter();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cutoff.OldValue <= 1 && cutoff.NewValue > 1)
|
|
||||||
attachFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterIndex = mixer.Effects.IndexOf(filter);
|
var filterIndex = mixer.Effects.IndexOf(filter);
|
||||||
|
|
||||||
if (filterIndex < 0) return;
|
if (filterIndex < 0) return;
|
||||||
|
|
||||||
if (mixer.Effects[filterIndex] is BQFParameters existingFilter)
|
if (mixer.Effects[filterIndex] is BQFParameters existingFilter)
|
||||||
{
|
{
|
||||||
existingFilter.fCenter = cutoff.NewValue;
|
existingFilter.fCenter = newValue;
|
||||||
|
|
||||||
// required to update effect with new parameters.
|
// required to update effect with new parameters.
|
||||||
mixer.Effects[filterIndex] = existingFilter;
|
mixer.Effects[filterIndex] = existingFilter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureAttached()
|
||||||
|
{
|
||||||
|
if (isAttached)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug.Assert(!mixer.Effects.Contains(filter));
|
||||||
|
mixer.Effects.Add(filter);
|
||||||
|
isAttached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureDetached()
|
||||||
|
{
|
||||||
|
if (!isAttached)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug.Assert(mixer.Effects.Contains(filter));
|
||||||
|
mixer.Effects.Remove(filter);
|
||||||
|
isAttached = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
ensureDetached();
|
||||||
if (mixer.Effects.Contains(filter))
|
|
||||||
detachFilter();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +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.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Transforms;
|
using osu.Framework.Graphics.Transforms;
|
||||||
|
|
||||||
@ -12,7 +11,7 @@ namespace osu.Game.Audio.Effects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The filter cutoff.
|
/// The filter cutoff.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BindableNumber<int> Cutoff { get; }
|
int Cutoff { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FilterableAudioComponentExtensions
|
public static class FilterableAudioComponentExtensions
|
||||||
@ -40,7 +39,7 @@ namespace osu.Game.Audio.Effects
|
|||||||
public static TransformSequence<T> CutoffTo<T, TEasing>(this T component, int newCutoff, double duration, TEasing easing)
|
public static TransformSequence<T> CutoffTo<T, TEasing>(this T component, int newCutoff, double duration, TEasing easing)
|
||||||
where T : class, ITransformableFilter, IDrawable
|
where T : class, ITransformableFilter, IDrawable
|
||||||
where TEasing : IEasingFunction
|
where TEasing : IEasingFunction
|
||||||
=> component.TransformBindableTo(component.Cutoff, newCutoff, duration, easing);
|
=> component.TransformTo(nameof(component.Cutoff), newCutoff, duration, easing);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Smoothly adjusts filter cutoff over time.
|
/// Smoothly adjusts filter cutoff over time.
|
||||||
@ -49,6 +48,6 @@ namespace osu.Game.Audio.Effects
|
|||||||
public static TransformSequence<T> CutoffTo<T, TEasing>(this TransformSequence<T> sequence, int newCutoff, double duration, TEasing easing)
|
public static TransformSequence<T> CutoffTo<T, TEasing>(this TransformSequence<T> sequence, int newCutoff, double duration, TEasing easing)
|
||||||
where T : class, ITransformableFilter, IDrawable
|
where T : class, ITransformableFilter, IDrawable
|
||||||
where TEasing : IEasingFunction
|
where TEasing : IEasingFunction
|
||||||
=> sequence.Append(o => o.TransformBindableTo(o.Cutoff, newCutoff, duration, easing));
|
=> sequence.Append(o => o.TransformTo(nameof(o.Cutoff), newCutoff, duration, easing));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,13 @@ namespace osu.Game.Beatmaps
|
|||||||
public IBeatmap Convert(CancellationToken cancellationToken = default)
|
public IBeatmap Convert(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// We always operate on a clone of the original beatmap, to not modify it game-wide
|
// We always operate on a clone of the original beatmap, to not modify it game-wide
|
||||||
return ConvertBeatmap(Beatmap.Clone(), cancellationToken);
|
var original = Beatmap.Clone();
|
||||||
|
|
||||||
|
// Shallow clone isn't enough to ensure we don't mutate beatmap info unexpectedly.
|
||||||
|
// Can potentially be removed after `Beatmap.Difficulty` doesn't save back to `Beatmap.BeatmapInfo`.
|
||||||
|
original.BeatmapInfo = original.BeatmapInfo.Clone();
|
||||||
|
|
||||||
|
return ConvertBeatmap(original, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider api, GameHost host)
|
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider api, GameHost host)
|
||||||
{
|
{
|
||||||
return new BeatmapModelDownloader(modelManager, api, host);
|
return new BeatmapModelDownloader(modelManager, api, host);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
||||||
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
|
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
|
||||||
|
|
||||||
public BeatmapModelDownloader(BeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
|
public BeatmapModelDownloader(IBeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
|
||||||
: base(beatmapModelManager, api, host)
|
: base(beatmapModelManager, api, host)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Configuration
|
|||||||
[Description("Never repeat")]
|
[Description("Never repeat")]
|
||||||
RandomPermutation,
|
RandomPermutation,
|
||||||
|
|
||||||
[Description("Random")]
|
[Description("True Random")]
|
||||||
Random
|
Random
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="paths">One or more archive locations on disk.</param>
|
/// <param name="paths">One or more archive locations on disk.</param>
|
||||||
public Task Import(params string[] paths)
|
public Task Import(params string[] paths)
|
||||||
{
|
{
|
||||||
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
|
var notification = new ImportProgressNotification();
|
||||||
|
|
||||||
PostNotification?.Invoke(notification);
|
PostNotification?.Invoke(notification);
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public Task Import(params ImportTask[] tasks)
|
public Task Import(params ImportTask[] tasks)
|
||||||
{
|
{
|
||||||
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
|
var notification = new ImportProgressNotification();
|
||||||
|
|
||||||
PostNotification?.Invoke(notification);
|
PostNotification?.Invoke(notification);
|
||||||
|
|
||||||
|
20
osu.Game/Database/IHasRealmFiles.cs
Normal file
20
osu.Game/Database/IHasRealmFiles.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Models;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A model that contains a list of files it is responsible for.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasRealmFiles
|
||||||
|
{
|
||||||
|
IList<RealmNamedFileUsage> Files { get; }
|
||||||
|
|
||||||
|
string Hash { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ using osu.Game.Overlays.Notifications;
|
|||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class which handles importing of asociated models to the game store.
|
/// A class which handles importing of associated models to the game store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
public interface IModelImporter<TModel> : IPostNotifications
|
public interface IModelImporter<TModel> : IPostNotifications
|
||||||
|
19
osu.Game/Database/INamedFile.cs
Normal file
19
osu.Game/Database/INamedFile.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Models;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a join model which gives a filename and scope to a <see cref="File"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface INamedFile
|
||||||
|
{
|
||||||
|
string Filename { get; set; }
|
||||||
|
|
||||||
|
RealmFile File { get; set; }
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game/Database/ImportProgressNotification.cs
Normal file
15
osu.Game/Database/ImportProgressNotification.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class ImportProgressNotification : ProgressNotification
|
||||||
|
{
|
||||||
|
public ImportProgressNotification()
|
||||||
|
{
|
||||||
|
State = ProgressNotificationState.Active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -135,9 +135,8 @@ namespace osu.Game.Database
|
|||||||
if (IsDisposed)
|
if (IsDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
|
||||||
// TODO: this can be added for safety once we figure how to bypass in test
|
if (!ThreadSafety.IsUpdateThread)
|
||||||
// if (!ThreadSafety.IsUpdateThread)
|
throw new InvalidOperationException($"{nameof(BlockAllOperations)} must be called from the update thread.");
|
||||||
// throw new InvalidOperationException($"{nameof(BlockAllOperations)} must be called from the update thread.");
|
|
||||||
|
|
||||||
Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
|
Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
|
||||||
|
|
||||||
|
111
osu.Game/Database/RealmLive.cs
Normal file
111
osu.Game/Database/RealmLive.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// 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.Threading;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a method of working with realm objects over longer application lifetimes.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The underlying object type.</typeparam>
|
||||||
|
public class RealmLive<T> : ILive<T> where T : RealmObject, IHasGuidPrimaryKey
|
||||||
|
{
|
||||||
|
public Guid ID { get; }
|
||||||
|
|
||||||
|
private readonly SynchronizationContext? fetchedContext;
|
||||||
|
private readonly int fetchedThreadId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The original live data used to create this instance.
|
||||||
|
/// </summary>
|
||||||
|
private readonly T data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a new instance of live realm data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The realm data.</param>
|
||||||
|
public RealmLive(T data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
|
fetchedContext = SynchronizationContext.Current;
|
||||||
|
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||||
|
|
||||||
|
ID = data.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a read operation on this live object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="perform">The action to perform.</param>
|
||||||
|
public void PerformRead(Action<T> perform)
|
||||||
|
{
|
||||||
|
if (originalDataValid)
|
||||||
|
{
|
||||||
|
perform(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var realm = Realm.GetInstance(data.Realm.Config))
|
||||||
|
perform(realm.Find<T>(ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a read operation on this live object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="perform">The action to perform.</param>
|
||||||
|
public TReturn PerformRead<TReturn>(Func<T, TReturn> perform)
|
||||||
|
{
|
||||||
|
if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn)))
|
||||||
|
throw new InvalidOperationException($"Realm live objects should not exit the scope of {nameof(PerformRead)}.");
|
||||||
|
|
||||||
|
if (originalDataValid)
|
||||||
|
return perform(data);
|
||||||
|
|
||||||
|
using (var realm = Realm.GetInstance(data.Realm.Config))
|
||||||
|
return perform(realm.Find<T>(ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a write operation on this live object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="perform">The action to perform.</param>
|
||||||
|
public void PerformWrite(Action<T> perform) =>
|
||||||
|
PerformRead(t =>
|
||||||
|
{
|
||||||
|
var transaction = t.Realm.BeginWrite();
|
||||||
|
perform(t);
|
||||||
|
transaction.Commit();
|
||||||
|
});
|
||||||
|
|
||||||
|
public T Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (originalDataValid)
|
||||||
|
return data;
|
||||||
|
|
||||||
|
T retrieved;
|
||||||
|
|
||||||
|
using (var realm = Realm.GetInstance(data.Realm.Config))
|
||||||
|
retrieved = realm.Find<T>(ID);
|
||||||
|
|
||||||
|
if (!retrieved.IsValid)
|
||||||
|
throw new InvalidOperationException("Attempted to access value without an open context");
|
||||||
|
|
||||||
|
return retrieved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool originalDataValid => isCorrectThread && data.IsValid;
|
||||||
|
|
||||||
|
// this matches realm's internal thread validation (see https://github.com/realm/realm-dotnet/blob/903b4d0b304f887e37e2d905384fb572a6496e70/Realm/Realm/Native/SynchronizationContextScheduler.cs#L72)
|
||||||
|
private bool isCorrectThread
|
||||||
|
=> (fetchedContext != null && SynchronizationContext.Current == fetchedContext) || fetchedThreadId == Thread.CurrentThread.ManagedThreadId;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using Realms;
|
using Realms;
|
||||||
@ -47,5 +48,17 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
return mapper.Map<T>(item);
|
return mapper.Map<T>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<RealmLive<T>> ToLive<T>(this IEnumerable<T> realmList)
|
||||||
|
where T : RealmObject, IHasGuidPrimaryKey
|
||||||
|
{
|
||||||
|
return realmList.Select(l => new RealmLive<T>(l)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RealmLive<T> ToLive<T>(this T realmObject)
|
||||||
|
where T : RealmObject, IHasGuidPrimaryKey
|
||||||
|
{
|
||||||
|
return new RealmLive<T>(realmObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +225,16 @@ namespace osu.Game.Graphics
|
|||||||
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
||||||
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to <see cref="OverlayColourProvider.Pink"/>'s <see cref="OverlayColourProvider.Colour3"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to <see cref="OverlayColourProvider.Blue"/>'s <see cref="OverlayColourProvider.Colour3"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>.
|
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
public abstract class CommaSeparatedScoreCounter : RollingCounter<double>
|
||||||
|
{
|
||||||
|
protected override double RollingDuration => 1000;
|
||||||
|
protected override Easing RollingEasing => Easing.Out;
|
||||||
|
|
||||||
|
protected override double GetProportionalDuration(double currentValue, double newValue) =>
|
||||||
|
currentValue > newValue ? currentValue - newValue : newValue - currentValue;
|
||||||
|
|
||||||
|
protected override LocalisableString FormatCount(double count) => ((long)count).ToLocalisableString(@"N0");
|
||||||
|
|
||||||
|
protected override OsuSpriteText CreateSpriteText()
|
||||||
|
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
|
||||||
|
}
|
||||||
|
}
|
@ -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 osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -13,43 +14,30 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override double RollingDuration => 1000;
|
protected override double RollingDuration => 1000;
|
||||||
protected override Easing RollingEasing => Easing.Out;
|
protected override Easing RollingEasing => Easing.Out;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether comma separators should be displayed.
|
|
||||||
/// </summary>
|
|
||||||
public bool UseCommaSeparator { get; }
|
|
||||||
|
|
||||||
public Bindable<int> RequiredDisplayDigits { get; } = new Bindable<int>();
|
public Bindable<int> RequiredDisplayDigits { get; } = new Bindable<int>();
|
||||||
|
|
||||||
|
private string formatString;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays score.
|
/// Displays score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="leading">How many leading zeroes the counter will have.</param>
|
/// <param name="leading">How many leading zeroes the counter will have.</param>
|
||||||
/// <param name="useCommaSeparator">Whether comma separators should be displayed.</param>
|
protected ScoreCounter(int leading = 0)
|
||||||
protected ScoreCounter(int leading = 0, bool useCommaSeparator = false)
|
|
||||||
{
|
{
|
||||||
UseCommaSeparator = useCommaSeparator;
|
|
||||||
|
|
||||||
RequiredDisplayDigits.Value = leading;
|
RequiredDisplayDigits.Value = leading;
|
||||||
RequiredDisplayDigits.BindValueChanged(_ => UpdateDisplay());
|
RequiredDisplayDigits.BindValueChanged(displayDigitsChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetProportionalDuration(double currentValue, double newValue)
|
private void displayDigitsChanged(ValueChangedEvent<int> _)
|
||||||
{
|
{
|
||||||
return currentValue > newValue ? currentValue - newValue : newValue - currentValue;
|
formatString = new string('0', RequiredDisplayDigits.Value);
|
||||||
|
UpdateDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(double count)
|
protected override double GetProportionalDuration(double currentValue, double newValue) =>
|
||||||
{
|
currentValue > newValue ? currentValue - newValue : newValue - currentValue;
|
||||||
string format = new string('0', RequiredDisplayDigits.Value);
|
|
||||||
|
|
||||||
if (UseCommaSeparator)
|
protected override LocalisableString FormatCount(double count) => ((long)count).ToLocalisableString(formatString);
|
||||||
{
|
|
||||||
for (int i = format.Length - 3; i > 0; i -= 3)
|
|
||||||
format = format.Insert(i, @",");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((long)count).ToString(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText()
|
protected override OsuSpriteText CreateSpriteText()
|
||||||
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
|
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));
|
||||||
|
49
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
Normal file
49
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public class RoundedButton : OsuButton, IFilterable
|
||||||
|
{
|
||||||
|
public override float Height
|
||||||
|
{
|
||||||
|
get => base.Height;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Height = value;
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
updateCornerRadius();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
BackgroundColour = colours.Blue3;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateCornerRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCornerRadius() => Content.CornerRadius = DrawHeight / 2;
|
||||||
|
|
||||||
|
public virtual IEnumerable<string> FilterTerms => new[] { Text.ToString() };
|
||||||
|
|
||||||
|
public bool MatchingFilter
|
||||||
|
{
|
||||||
|
set => this.FadeTo(value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FilteringActive { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
|
public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Output device"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Master"
|
/// "Master"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -14,11 +14,36 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
|
public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Beatmap"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BeatmapHeader => new TranslatableString(getKey(@"beatmap_header"), @"Beatmap");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "General"
|
/// "General"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
|
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Audio"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AudioHeader => new TranslatableString(getKey(@"audio"), @"Audio");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "HUD"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HUDHeader => new TranslatableString(getKey(@"h_u_d"), @"HUD");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Input"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString InputHeader => new TranslatableString(getKey(@"input"), @"Input");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Background"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BackgroundHeader => new TranslatableString(getKey(@"background"), @"Background");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Background dim"
|
/// "Background dim"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -104,6 +104,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
|
public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Screenshots"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Screenshots => new TranslatableString(getKey(@"screenshots"), @"Screenshots");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Screenshot format"
|
/// "Screenshot format"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
19
osu.Game/Localisation/RulesetSettingsStrings.cs
Normal file
19
osu.Game/Localisation/RulesetSettingsStrings.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class RulesetSettingsStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.RulesetSettings";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Rulesets"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
|
public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Current skin"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CurrentSkin => new TranslatableString(getKey(@"current_skin"), @"Current skin");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Skin layout editor"
|
/// "Skin layout editor"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
117
osu.Game/Models/RealmBeatmap.cs
Normal file
117
osu.Game/Models/RealmBeatmap.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A single beatmap difficulty.
|
||||||
|
/// </summary>
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
[Serializable]
|
||||||
|
[MapTo("Beatmap")]
|
||||||
|
public class RealmBeatmap : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo
|
||||||
|
{
|
||||||
|
[PrimaryKey]
|
||||||
|
public Guid ID { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string DifficultyName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public RealmRuleset Ruleset { get; set; } = null!;
|
||||||
|
|
||||||
|
public RealmBeatmapDifficulty Difficulty { get; set; } = null!;
|
||||||
|
|
||||||
|
public RealmBeatmapMetadata Metadata { get; set; } = null!;
|
||||||
|
|
||||||
|
public RealmBeatmapSet? BeatmapSet { get; set; }
|
||||||
|
|
||||||
|
public BeatmapSetOnlineStatus Status
|
||||||
|
{
|
||||||
|
get => (BeatmapSetOnlineStatus)StatusInt;
|
||||||
|
set => StatusInt = (int)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MapTo(nameof(Status))]
|
||||||
|
public int StatusInt { get; set; }
|
||||||
|
|
||||||
|
public int? OnlineID { get; set; }
|
||||||
|
|
||||||
|
public double Length { get; set; }
|
||||||
|
|
||||||
|
public double BPM { get; set; }
|
||||||
|
|
||||||
|
public string Hash { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public double StarRating { get; set; }
|
||||||
|
|
||||||
|
public string MD5Hash { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Hidden { get; set; }
|
||||||
|
|
||||||
|
public RealmBeatmap(RealmRuleset ruleset, RealmBeatmapDifficulty difficulty, RealmBeatmapMetadata metadata)
|
||||||
|
{
|
||||||
|
Ruleset = ruleset;
|
||||||
|
Difficulty = difficulty;
|
||||||
|
Metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
private RealmBeatmap()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Properties we may not want persisted (but also maybe no harm?)
|
||||||
|
|
||||||
|
public double AudioLeadIn { get; set; }
|
||||||
|
|
||||||
|
public float StackLeniency { get; set; } = 0.7f;
|
||||||
|
|
||||||
|
public bool SpecialStyle { get; set; }
|
||||||
|
|
||||||
|
public bool LetterboxInBreaks { get; set; }
|
||||||
|
|
||||||
|
public bool WidescreenStoryboard { get; set; }
|
||||||
|
|
||||||
|
public bool EpilepsyWarning { get; set; }
|
||||||
|
|
||||||
|
public bool SamplesMatchPlaybackRate { get; set; }
|
||||||
|
|
||||||
|
public double DistanceSpacing { get; set; }
|
||||||
|
|
||||||
|
public int BeatDivisor { get; set; }
|
||||||
|
|
||||||
|
public int GridSize { get; set; }
|
||||||
|
|
||||||
|
public double TimelineZoom { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public bool AudioEquals(RealmBeatmap? other) => other != null
|
||||||
|
&& BeatmapSet != null
|
||||||
|
&& other.BeatmapSet != null
|
||||||
|
&& BeatmapSet.Hash == other.BeatmapSet.Hash
|
||||||
|
&& Metadata.AudioFile == other.Metadata.AudioFile;
|
||||||
|
|
||||||
|
public bool BackgroundEquals(RealmBeatmap? other) => other != null
|
||||||
|
&& BeatmapSet != null
|
||||||
|
&& other.BeatmapSet != null
|
||||||
|
&& BeatmapSet.Hash == other.BeatmapSet.Hash
|
||||||
|
&& Metadata.BackgroundFile == other.Metadata.BackgroundFile;
|
||||||
|
|
||||||
|
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
|
||||||
|
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
||||||
|
IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
|
||||||
|
IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => Difficulty;
|
||||||
|
}
|
||||||
|
}
|
45
osu.Game/Models/RealmBeatmapDifficulty.cs
Normal file
45
osu.Game/Models/RealmBeatmapDifficulty.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
[MapTo("BeatmapDifficulty")]
|
||||||
|
public class RealmBeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo
|
||||||
|
{
|
||||||
|
public float DrainRate { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY;
|
||||||
|
public float CircleSize { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY;
|
||||||
|
public float OverallDifficulty { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY;
|
||||||
|
public float ApproachRate { get; set; } = IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY;
|
||||||
|
|
||||||
|
public double SliderMultiplier { get; set; } = 1;
|
||||||
|
public double SliderTickRate { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a shallow-clone of this <see cref="RealmBeatmapDifficulty"/>.
|
||||||
|
/// </summary>
|
||||||
|
public RealmBeatmapDifficulty Clone()
|
||||||
|
{
|
||||||
|
var diff = new RealmBeatmapDifficulty();
|
||||||
|
CopyTo(diff);
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(RealmBeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
difficulty.ApproachRate = ApproachRate;
|
||||||
|
difficulty.DrainRate = DrainRate;
|
||||||
|
difficulty.CircleSize = CircleSize;
|
||||||
|
difficulty.OverallDifficulty = OverallDifficulty;
|
||||||
|
|
||||||
|
difficulty.SliderMultiplier = SliderMultiplier;
|
||||||
|
difficulty.SliderTickRate = SliderTickRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
osu.Game/Models/RealmBeatmapMetadata.cs
Normal file
45
osu.Game/Models/RealmBeatmapMetadata.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
[Serializable]
|
||||||
|
[MapTo("BeatmapMetadata")]
|
||||||
|
public class RealmBeatmapMetadata : RealmObject, IBeatmapMetadataInfo
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonProperty("title_unicode")]
|
||||||
|
public string TitleUnicode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Artist { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonProperty("artist_unicode")]
|
||||||
|
public string ArtistUnicode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Author { get; set; } = string.Empty; // eventually should be linked to a persisted User.
|
||||||
|
|
||||||
|
public string Source { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonProperty(@"tags")]
|
||||||
|
public string Tags { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time in milliseconds to begin playing the track for preview purposes.
|
||||||
|
/// If -1, the track should begin playing at 40% of its length.
|
||||||
|
/// </summary>
|
||||||
|
public int PreviewTime { get; set; }
|
||||||
|
|
||||||
|
public string AudioFile { get; set; } = string.Empty;
|
||||||
|
public string BackgroundFile { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
78
osu.Game/Models/RealmBeatmapSet.cs
Normal file
78
osu.Game/Models/RealmBeatmapSet.cs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
[MapTo("BeatmapSet")]
|
||||||
|
public class RealmBeatmapSet : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable<RealmBeatmapSet>, IBeatmapSetInfo
|
||||||
|
{
|
||||||
|
[PrimaryKey]
|
||||||
|
public Guid ID { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public int? OnlineID { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset DateAdded { get; set; }
|
||||||
|
|
||||||
|
public IBeatmapMetadataInfo? Metadata => Beatmaps.FirstOrDefault()?.Metadata;
|
||||||
|
|
||||||
|
public IList<RealmBeatmap> Beatmaps { get; } = null!;
|
||||||
|
|
||||||
|
public IList<RealmNamedFileUsage> Files { get; } = null!;
|
||||||
|
|
||||||
|
public bool DeletePending { get; set; }
|
||||||
|
|
||||||
|
public string Hash { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether deleting this beatmap set should be prohibited (due to it being a system requirement to be present).
|
||||||
|
/// </summary>
|
||||||
|
public bool Protected { get; set; }
|
||||||
|
|
||||||
|
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarRating);
|
||||||
|
|
||||||
|
public double MaxLength => Beatmaps.Max(b => b.Length);
|
||||||
|
|
||||||
|
public double MaxBPM => Beatmaps.Max(b => b.BPM);
|
||||||
|
|
||||||
|
/// <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.StoragePath;
|
||||||
|
|
||||||
|
public override string ToString() => Metadata?.ToString() ?? base.ToString();
|
||||||
|
|
||||||
|
public bool Equals(RealmBeatmapSet? other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (IsManaged && other.IsManaged)
|
||||||
|
return ID == other.ID;
|
||||||
|
|
||||||
|
if (OnlineID.HasValue && other.OnlineID.HasValue)
|
||||||
|
return OnlineID == other.OnlineID;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
|
||||||
|
return Hash == other.Hash;
|
||||||
|
|
||||||
|
return ReferenceEquals(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
|
||||||
|
|
||||||
|
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;
|
||||||
|
}
|
||||||
|
}
|
22
osu.Game/Models/RealmFile.cs
Normal file
22
osu.Game/Models/RealmFile.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
[MapTo("File")]
|
||||||
|
public class RealmFile : RealmObject, IFileInfo
|
||||||
|
{
|
||||||
|
[PrimaryKey]
|
||||||
|
public string Hash { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash);
|
||||||
|
}
|
||||||
|
}
|
34
osu.Game/Models/RealmNamedFileUsage.cs
Normal file
34
osu.Game/Models/RealmNamedFileUsage.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
public class RealmNamedFileUsage : EmbeddedObject, INamedFile, INamedFileUsage
|
||||||
|
{
|
||||||
|
public RealmFile File { get; set; } = null!;
|
||||||
|
|
||||||
|
public string Filename { get; set; } = null!;
|
||||||
|
|
||||||
|
public RealmNamedFileUsage(RealmFile file, string filename)
|
||||||
|
{
|
||||||
|
File = file;
|
||||||
|
Filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
private RealmNamedFileUsage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IFileInfo INamedFileUsage.File => File;
|
||||||
|
}
|
||||||
|
}
|
63
osu.Game/Models/RealmRuleset.cs
Normal file
63
osu.Game/Models/RealmRuleset.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Models
|
||||||
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
|
[MapTo("Ruleset")]
|
||||||
|
public class RealmRuleset : RealmObject, IEquatable<RealmRuleset>, IRulesetInfo
|
||||||
|
{
|
||||||
|
[PrimaryKey]
|
||||||
|
public string ShortName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int? OnlineID { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string InstantiationInfo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public RealmRuleset(string shortName, string name, string instantiationInfo, int? onlineID = null)
|
||||||
|
{
|
||||||
|
ShortName = shortName;
|
||||||
|
Name = name;
|
||||||
|
InstantiationInfo = instantiationInfo;
|
||||||
|
OnlineID = onlineID;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
private RealmRuleset()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmRuleset(int? onlineID, string name, string shortName, bool available)
|
||||||
|
{
|
||||||
|
OnlineID = onlineID;
|
||||||
|
Name = name;
|
||||||
|
ShortName = shortName;
|
||||||
|
Available = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Available { get; set; }
|
||||||
|
|
||||||
|
public bool Equals(RealmRuleset? other) => other != null && OnlineID == other.OnlineID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
|
||||||
|
public RealmRuleset Clone() => new RealmRuleset
|
||||||
|
{
|
||||||
|
OnlineID = OnlineID,
|
||||||
|
Name = Name,
|
||||||
|
ShortName = ShortName,
|
||||||
|
InstantiationInfo = InstantiationInfo,
|
||||||
|
Available = Available
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -177,6 +177,24 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
case "wiki":
|
case "wiki":
|
||||||
return new LinkDetails(LinkAction.OpenWiki, string.Join('/', args.Skip(3)));
|
return new LinkDetails(LinkAction.OpenWiki, string.Join('/', args.Skip(3)));
|
||||||
|
|
||||||
|
case "home":
|
||||||
|
if (mainArg != "changelog")
|
||||||
|
// handle link other than changelog as external for now
|
||||||
|
return new LinkDetails(LinkAction.External, url);
|
||||||
|
|
||||||
|
switch (args.Length)
|
||||||
|
{
|
||||||
|
case 4:
|
||||||
|
// https://osu.ppy.sh/home/changelog
|
||||||
|
return new LinkDetails(LinkAction.OpenChangelog, string.Empty);
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
// https://osu.ppy.sh/home/changelog/lazer/2021.1006
|
||||||
|
return new LinkDetails(LinkAction.OpenChangelog, $"{args[4]}/{args[5]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,6 +342,7 @@ namespace osu.Game.Online.Chat
|
|||||||
SearchBeatmapSet,
|
SearchBeatmapSet,
|
||||||
OpenWiki,
|
OpenWiki,
|
||||||
Custom,
|
Custom,
|
||||||
|
OpenChangelog,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Link : IComparable<Link>
|
public class Link : IComparable<Link>
|
||||||
|
@ -90,6 +90,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
private WikiOverlay wikiOverlay;
|
private WikiOverlay wikiOverlay;
|
||||||
|
|
||||||
|
private ChangelogOverlay changelogOverlay;
|
||||||
|
|
||||||
private SkinEditorOverlay skinEditor;
|
private SkinEditorOverlay skinEditor;
|
||||||
|
|
||||||
private Container overlayContent;
|
private Container overlayContent;
|
||||||
@ -209,13 +211,6 @@ namespace osu.Game
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (args?.Length > 0)
|
|
||||||
{
|
|
||||||
var paths = args.Where(a => !a.StartsWith('-')).ToArray();
|
|
||||||
if (paths.Length > 0)
|
|
||||||
Task.Run(() => Import(paths));
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies.CacheAs(this);
|
dependencies.CacheAs(this);
|
||||||
|
|
||||||
dependencies.Cache(SentryLogger);
|
dependencies.Cache(SentryLogger);
|
||||||
@ -336,6 +331,17 @@ namespace osu.Game
|
|||||||
ShowWiki(link.Argument);
|
ShowWiki(link.Argument);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case LinkAction.OpenChangelog:
|
||||||
|
if (string.IsNullOrEmpty(link.Argument))
|
||||||
|
ShowChangelogListing();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var changelogArgs = link.Argument.Split("/");
|
||||||
|
ShowChangelogBuild(changelogArgs[0], changelogArgs[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action.");
|
throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action.");
|
||||||
}
|
}
|
||||||
@ -401,6 +407,18 @@ namespace osu.Game
|
|||||||
/// <param name="path">The wiki page to show</param>
|
/// <param name="path">The wiki page to show</param>
|
||||||
public void ShowWiki(string path) => waitForReady(() => wikiOverlay, _ => wikiOverlay.ShowPage(path));
|
public void ShowWiki(string path) => waitForReady(() => wikiOverlay, _ => wikiOverlay.ShowPage(path));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show changelog listing overlay
|
||||||
|
/// </summary>
|
||||||
|
public void ShowChangelogListing() => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowListing());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show changelog's build as an overlay
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="updateStream">The update stream name</param>
|
||||||
|
/// <param name="version">The build version of the update stream</param>
|
||||||
|
public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Present a beatmap at song select immediately.
|
/// Present a beatmap at song select immediately.
|
||||||
/// The user should have already requested this interactively.
|
/// The user should have already requested this interactively.
|
||||||
@ -769,7 +787,7 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
||||||
loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true);
|
loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true);
|
||||||
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(changelogOverlay = new ChangelogOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
|
||||||
@ -842,6 +860,19 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
|
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup.
|
||||||
|
handleStartupImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStartupImport()
|
||||||
|
{
|
||||||
|
if (args?.Length > 0)
|
||||||
|
{
|
||||||
|
var paths = args.Where(a => !a.StartsWith('-')).ToArray();
|
||||||
|
if (paths.Length > 0)
|
||||||
|
Task.Run(() => Import(paths));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
|
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -410,11 +411,28 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
|
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
|
||||||
|
|
||||||
using (realmFactory.BlockAllOperations())
|
IDisposable realmBlocker = null;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
contextFactory.FlushConnections();
|
ManualResetEventSlim readyToRun = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
realmBlocker = realmFactory.BlockAllOperations();
|
||||||
|
contextFactory.FlushConnections();
|
||||||
|
|
||||||
|
readyToRun.Set();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
readyToRun.Wait();
|
||||||
|
|
||||||
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
realmBlocker?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Log(@"Migration complete!");
|
Logger.Log(@"Migration complete!");
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Overlays.OSD
|
|||||||
private Sample sampleChange;
|
private Sample sampleChange;
|
||||||
|
|
||||||
public TrackedSettingToast(SettingDescription description)
|
public TrackedSettingToast(SettingDescription description)
|
||||||
: base(description.Name, description.Value, description.Shortcut)
|
: base(description.Name.ToString(), description.Value.ToString(), description.Shortcut.ToString())
|
||||||
{
|
{
|
||||||
FillFlowContainer<OptionLight> optionLights;
|
FillFlowContainer<OptionLight> optionLights;
|
||||||
|
|
||||||
|
@ -14,10 +14,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
BackgroundColour = colours.Pink;
|
BackgroundColour = colours.Pink3;
|
||||||
|
|
||||||
Triangles.ColourDark = colours.PinkDark;
|
|
||||||
Triangles.ColourLight = colours.PinkLight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
{
|
{
|
||||||
dropdown = new AudioDeviceSettingsDropdown
|
dropdown = new AudioDeviceSettingsDropdown
|
||||||
{
|
{
|
||||||
|
LabelText = AudioSettingsStrings.OutputDevice,
|
||||||
Keywords = new[] { "speaker", "headphone", "output" }
|
Keywords = new[] { "speaker", "headphone", "output" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||||
|
{
|
||||||
|
public class AudioSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.PositionalHitsounds,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||||
|
{
|
||||||
|
public class BackgroundSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => GameplaySettingsStrings.BackgroundHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsSlider<double>
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.BackgroundDim,
|
||||||
|
Current = config.GetBindable<double>(OsuSetting.DimLevel),
|
||||||
|
KeyboardStep = 0.01f,
|
||||||
|
DisplayAsPercentage = true
|
||||||
|
},
|
||||||
|
new SettingsSlider<double>
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.BackgroundBlur,
|
||||||
|
Current = config.GetBindable<double>(OsuSetting.BlurLevel),
|
||||||
|
KeyboardStep = 0.01f,
|
||||||
|
DisplayAsPercentage = true
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.LightenDuringBreaks,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||||
|
{
|
||||||
|
public class BeatmapSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => GameplaySettingsStrings.BeatmapHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.BeatmapSkins,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.BeatmapColours,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.BeatmapColours)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.BeatmapHitsounds,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GraphicsSettingsStrings.StoryboardVideo,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +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.
|
||||||
|
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -20,77 +19,18 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SettingsSlider<double>
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.BackgroundDim,
|
|
||||||
Current = config.GetBindable<double>(OsuSetting.DimLevel),
|
|
||||||
KeyboardStep = 0.01f,
|
|
||||||
DisplayAsPercentage = true
|
|
||||||
},
|
|
||||||
new SettingsSlider<double>
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.BackgroundBlur,
|
|
||||||
Current = config.GetBindable<double>(OsuSetting.BlurLevel),
|
|
||||||
KeyboardStep = 0.01f,
|
|
||||||
DisplayAsPercentage = true
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.LightenDuringBreaks,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
|
|
||||||
},
|
|
||||||
new SettingsEnumDropdown<HUDVisibilityMode>
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.HUDVisibilityMode,
|
|
||||||
Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
|
|
||||||
Keywords = new[] { "hp", "bar" }
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.PositionalHitsounds,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
|
|
||||||
},
|
|
||||||
new SettingsEnumDropdown<ScoringMode>
|
new SettingsEnumDropdown<ScoringMode>
|
||||||
{
|
{
|
||||||
LabelText = GameplaySettingsStrings.ScoreDisplayMode,
|
LabelText = GameplaySettingsStrings.ScoreDisplayMode,
|
||||||
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
|
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
|
||||||
Keywords = new[] { "scoring" }
|
Keywords = new[] { "scoring" }
|
||||||
},
|
},
|
||||||
};
|
new SettingsCheckbox
|
||||||
|
|
||||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
|
||||||
{
|
|
||||||
Add(new SettingsCheckbox
|
|
||||||
{
|
{
|
||||||
LabelText = GameplaySettingsStrings.DisableWinKey,
|
LabelText = GraphicsSettingsStrings.HitLighting,
|
||||||
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
|
Current = config.GetBindable<bool>(OsuSetting.HitLighting)
|
||||||
});
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
Normal file
45
osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||||
|
{
|
||||||
|
public class HUDSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => GameplaySettingsStrings.HUDHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsEnumDropdown<HUDVisibilityMode>
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.HUDVisibilityMode,
|
||||||
|
Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
|
||||||
|
Keywords = new[] { "hp", "bar" }
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||||
|
{
|
||||||
|
public class InputSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override LocalisableString Header => GameplaySettingsStrings.InputHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsSlider<float, SizeSlider>
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.GameplayCursorSize,
|
||||||
|
Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
|
||||||
|
KeyboardStep = 0.01f
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.AutoCursorSize,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||||
|
{
|
||||||
|
Add(new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.DisableWinKey,
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Overlays.Settings.Sections.Gameplay;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Gameplay;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections
|
namespace osu.Game.Overlays.Settings.Sections
|
||||||
{
|
{
|
||||||
@ -20,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon
|
public override Drawable CreateIcon() => new SpriteIcon
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Regular.Circle
|
Icon = FontAwesome.Regular.DotCircle
|
||||||
};
|
};
|
||||||
|
|
||||||
public GameplaySection()
|
public GameplaySection()
|
||||||
@ -28,27 +23,13 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new GeneralSettings(),
|
new GeneralSettings(),
|
||||||
|
new AudioSettings(),
|
||||||
|
new BeatmapSettings(),
|
||||||
|
new BackgroundSettings(),
|
||||||
|
new HUDSettings(),
|
||||||
|
new InputSettings(),
|
||||||
new ModsSettings(),
|
new ModsSettings(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(RulesetStore rulesets)
|
|
||||||
{
|
|
||||||
foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SettingsSubsection section = ruleset.CreateSettings();
|
|
||||||
|
|
||||||
if (section != null)
|
|
||||||
Add(section);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error(e, "Failed to load ruleset settings");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,25 +9,15 @@ using osu.Game.Localisation;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||||
{
|
{
|
||||||
public class DetailSettings : SettingsSubsection
|
public class ScreenshotSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
protected override LocalisableString Header => GraphicsSettingsStrings.DetailSettingsHeader;
|
protected override LocalisableString Header => GraphicsSettingsStrings.Screenshots;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GraphicsSettingsStrings.StoryboardVideo,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = GraphicsSettingsStrings.HitLighting,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.HitLighting)
|
|
||||||
},
|
|
||||||
new SettingsEnumDropdown<ScreenshotFormat>
|
new SettingsEnumDropdown<ScreenshotFormat>
|
||||||
{
|
{
|
||||||
LabelText = GraphicsSettingsStrings.ScreenshotFormat,
|
LabelText = GraphicsSettingsStrings.ScreenshotFormat,
|
@ -22,9 +22,9 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new RendererSettings(),
|
|
||||||
new LayoutSettings(),
|
new LayoutSettings(),
|
||||||
new DetailSettings(),
|
new RendererSettings(),
|
||||||
|
new ScreenshotSettings(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
private RealmContextFactory realmFactory { get; set; }
|
private RealmContextFactory realmFactory { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Radius = 2,
|
Radius = 2,
|
||||||
Colour = colours.YellowDark.Opacity(0),
|
Colour = colourProvider.Highlight1.Opacity(0),
|
||||||
Type = EdgeEffectType.Shadow,
|
Type = EdgeEffectType.Shadow,
|
||||||
Hollow = true,
|
Hollow = true,
|
||||||
},
|
},
|
||||||
@ -110,13 +110,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black,
|
Colour = colourProvider.Background5,
|
||||||
Alpha = 0.6f,
|
|
||||||
},
|
},
|
||||||
text = new OsuSpriteText
|
text = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = action.GetLocalisableDescription(),
|
Text = action.GetLocalisableDescription(),
|
||||||
Margin = new MarginPadding(padding),
|
Margin = new MarginPadding(1.5f * padding),
|
||||||
},
|
},
|
||||||
buttons = new FillFlowContainer<KeyButton>
|
buttons = new FillFlowContainer<KeyButton>
|
||||||
{
|
{
|
||||||
@ -405,7 +404,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
private readonly Box box;
|
private readonly Box box;
|
||||||
public readonly OsuSpriteText Text;
|
public readonly OsuSpriteText Text;
|
||||||
|
|
||||||
private Color4 hoverColour;
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; }
|
||||||
|
|
||||||
private bool isBinding;
|
private bool isBinding;
|
||||||
|
|
||||||
@ -448,7 +448,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
box = new Box
|
box = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black
|
|
||||||
},
|
},
|
||||||
Text = new OsuSpriteText
|
Text = new OsuSpriteText
|
||||||
{
|
{
|
||||||
@ -463,9 +462,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load()
|
||||||
{
|
{
|
||||||
hoverColour = colours.YellowDark;
|
updateHoverState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
@ -484,12 +483,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
{
|
{
|
||||||
if (isBinding)
|
if (isBinding)
|
||||||
{
|
{
|
||||||
box.FadeColour(Color4.White, transition_time, Easing.OutQuint);
|
box.FadeColour(colourProvider.Light2, transition_time, Easing.OutQuint);
|
||||||
Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint);
|
Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
box.FadeColour(IsHovered ? hoverColour : Color4.Black, transition_time, Easing.OutQuint);
|
box.FadeColour(IsHovered ? colourProvider.Light4 : colourProvider.Background6, transition_time, Easing.OutQuint);
|
||||||
Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint);
|
Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -27,8 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
{
|
{
|
||||||
this.variant = variant;
|
this.variant = variant;
|
||||||
|
|
||||||
FlowContent.Spacing = new Vector2(0, 1);
|
FlowContent.Spacing = new Vector2(0, 3);
|
||||||
FlowContent.Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -60,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ResetButton : DangerousTriangleButton
|
public class ResetButton : DangerousSettingsButton
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -8,16 +9,24 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Handlers.Tablet;
|
using osu.Framework.Input.Handlers.Tablet;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Input
|
namespace osu.Game.Overlays.Settings.Sections.Input
|
||||||
{
|
{
|
||||||
internal class RotationPresetButtons : FillFlowContainer
|
internal class RotationPresetButtons : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
public new MarginPadding Padding
|
||||||
|
{
|
||||||
|
get => base.Padding;
|
||||||
|
set => base.Padding = value;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly ITabletHandler tabletHandler;
|
private readonly ITabletHandler tabletHandler;
|
||||||
|
|
||||||
private Bindable<float> rotation;
|
private Bindable<float> rotation;
|
||||||
|
private readonly RotationButton[] rotationPresets = new RotationButton[preset_count];
|
||||||
|
|
||||||
|
private const int preset_count = 4;
|
||||||
private const int height = 50;
|
private const int height = 50;
|
||||||
|
|
||||||
public RotationPresetButtons(ITabletHandler tabletHandler)
|
public RotationPresetButtons(ITabletHandler tabletHandler)
|
||||||
@ -27,18 +36,39 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = height;
|
Height = height;
|
||||||
|
|
||||||
for (int i = 0; i < 360; i += 90)
|
IEnumerable<Dimension> createColumns(int count)
|
||||||
{
|
{
|
||||||
var presetRotation = i;
|
for (int i = 0; i < count; ++i)
|
||||||
|
|
||||||
Add(new RotationButton(i)
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
if (i > 0)
|
||||||
Height = height,
|
yield return new Dimension(GridSizeMode.Absolute, 10);
|
||||||
Width = 0.25f,
|
|
||||||
Text = $@"{presetRotation}º",
|
yield return new Dimension();
|
||||||
Action = () => tabletHandler.Rotation.Value = presetRotation,
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
GridContainer grid;
|
||||||
|
|
||||||
|
InternalChild = grid = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = createColumns(preset_count).ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.Content = new[] { new Drawable[preset_count * 2 - 1] };
|
||||||
|
|
||||||
|
for (int i = 0; i < preset_count; i++)
|
||||||
|
{
|
||||||
|
var rotationValue = i * 90;
|
||||||
|
|
||||||
|
var rotationPreset = new RotationButton(rotationValue)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 1,
|
||||||
|
Text = $@"{rotationValue}º",
|
||||||
|
Action = () => tabletHandler.Rotation.Value = rotationValue,
|
||||||
|
};
|
||||||
|
grid.Content[0][2 * i] = rotationPresets[i] = rotationPreset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,16 +79,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
rotation = tabletHandler.Rotation.GetBoundCopy();
|
rotation = tabletHandler.Rotation.GetBoundCopy();
|
||||||
rotation.BindValueChanged(val =>
|
rotation.BindValueChanged(val =>
|
||||||
{
|
{
|
||||||
foreach (var b in Children.OfType<RotationButton>())
|
foreach (var b in rotationPresets)
|
||||||
b.IsSelected = b.Preset == val.NewValue;
|
b.IsSelected = b.Preset == val.NewValue;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RotationButton : TriangleButton
|
public class RotationButton : RoundedButton
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; }
|
||||||
|
|
||||||
public readonly int Preset;
|
public readonly int Preset;
|
||||||
|
|
||||||
public RotationButton(int preset)
|
public RotationButton(int preset)
|
||||||
@ -91,18 +124,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
|
|
||||||
private void updateColour()
|
private void updateColour()
|
||||||
{
|
{
|
||||||
if (isSelected)
|
BackgroundColour = isSelected ? colours.Blue3 : colourProvider.Background3;
|
||||||
{
|
|
||||||
BackgroundColour = colours.BlueDark;
|
|
||||||
Triangles.ColourDark = colours.BlueDarker;
|
|
||||||
Triangles.ColourLight = colours.Blue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BackgroundColour = colours.Gray4;
|
|
||||||
Triangles.ColourDark = colours.Gray5;
|
|
||||||
Triangles.ColourLight = colours.Gray6;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
LabelText = TabletSettingsStrings.Rotation,
|
LabelText = TabletSettingsStrings.Rotation,
|
||||||
Current = rotation
|
Current = rotation
|
||||||
},
|
},
|
||||||
new RotationPresetButtons(tabletHandler),
|
new RotationPresetButtons(tabletHandler)
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = SettingsPanel.CONTENT_MARGINS
|
||||||
|
}
|
||||||
|
},
|
||||||
new SettingsSlider<float>
|
new SettingsSlider<float>
|
||||||
{
|
{
|
||||||
TransferValueOnCommit = true,
|
TransferValueOnCommit = true,
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -21,15 +20,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
{
|
{
|
||||||
protected override LocalisableString Header => "General";
|
protected override LocalisableString Header => "General";
|
||||||
|
|
||||||
private TriangleButton importBeatmapsButton;
|
private SettingsButton importBeatmapsButton;
|
||||||
private TriangleButton importScoresButton;
|
private SettingsButton importScoresButton;
|
||||||
private TriangleButton importSkinsButton;
|
private SettingsButton importSkinsButton;
|
||||||
private TriangleButton importCollectionsButton;
|
private SettingsButton importCollectionsButton;
|
||||||
private TriangleButton deleteBeatmapsButton;
|
private SettingsButton deleteBeatmapsButton;
|
||||||
private TriangleButton deleteScoresButton;
|
private SettingsButton deleteScoresButton;
|
||||||
private TriangleButton deleteSkinsButton;
|
private SettingsButton deleteSkinsButton;
|
||||||
private TriangleButton restoreButton;
|
private SettingsButton restoreButton;
|
||||||
private TriangleButton undeleteButton;
|
private SettingsButton undeleteButton;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay)
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections
|
namespace osu.Game.Overlays.Settings.Sections
|
||||||
{
|
{
|
||||||
@ -21,7 +20,6 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
|
|
||||||
public MaintenanceSection()
|
public MaintenanceSection()
|
||||||
{
|
{
|
||||||
FlowContent.Spacing = new Vector2(0, 5);
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new GeneralSettings()
|
new GeneralSettings()
|
||||||
|
44
osu.Game/Overlays/Settings/Sections/RulesetSection.cs
Normal file
44
osu.Game/Overlays/Settings/Sections/RulesetSection.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections
|
||||||
|
{
|
||||||
|
public class RulesetSection : SettingsSection
|
||||||
|
{
|
||||||
|
public override LocalisableString Header => RulesetSettingsStrings.Rulesets;
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.Chess
|
||||||
|
};
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(RulesetStore rulesets)
|
||||||
|
{
|
||||||
|
foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SettingsSubsection section = ruleset.CreateSettings();
|
||||||
|
|
||||||
|
if (section != null)
|
||||||
|
Add(section);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, "Failed to load ruleset settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Skinning.Editor;
|
using osu.Game.Skinning.Editor;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections
|
namespace osu.Game.Overlays.Settings.Sections
|
||||||
{
|
{
|
||||||
@ -63,43 +62,18 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor)
|
private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor)
|
||||||
{
|
{
|
||||||
FlowContent.Spacing = new Vector2(0, 5);
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
skinDropdown = new SkinSettingsDropdown(),
|
skinDropdown = new SkinSettingsDropdown
|
||||||
|
{
|
||||||
|
LabelText = SkinSettingsStrings.CurrentSkin
|
||||||
|
},
|
||||||
new SettingsButton
|
new SettingsButton
|
||||||
{
|
{
|
||||||
Text = SkinSettingsStrings.SkinLayoutEditor,
|
Text = SkinSettingsStrings.SkinLayoutEditor,
|
||||||
Action = () => skinEditor?.Toggle(),
|
Action = () => skinEditor?.Toggle(),
|
||||||
},
|
},
|
||||||
new ExportSkinButton(),
|
new ExportSkinButton(),
|
||||||
new SettingsSlider<float, SizeSlider>
|
|
||||||
{
|
|
||||||
LabelText = SkinSettingsStrings.GameplayCursorSize,
|
|
||||||
Current = config.GetBindable<float>(OsuSetting.GameplayCursorSize),
|
|
||||||
KeyboardStep = 0.01f
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = SkinSettingsStrings.AutoCursorSize,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.AutoCursorSize)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = SkinSettingsStrings.BeatmapSkins,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = SkinSettingsStrings.BeatmapColours,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.BeatmapColours)
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = SkinSettingsStrings.BeatmapHitsounds,
|
|
||||||
Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds)
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
managerUpdated = skins.ItemUpdated.GetBoundCopy();
|
managerUpdated = skins.ItemUpdated.GetBoundCopy();
|
||||||
|
@ -6,11 +6,11 @@ using System.Linq;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class SettingsButton : TriangleButton, IHasTooltip
|
public class SettingsButton : RoundedButton, IHasTooltip
|
||||||
{
|
{
|
||||||
public SettingsButton()
|
public SettingsButton()
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = heading,
|
Text = heading,
|
||||||
Font = OsuFont.GetFont(size: 40),
|
Font = OsuFont.TorusAlternate.With(size: 40),
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
{
|
{
|
||||||
Left = SettingsPanel.CONTENT_MARGINS,
|
Left = SettingsPanel.CONTENT_MARGINS,
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Colour = colours.Pink,
|
Colour = colourProvider.Content2,
|
||||||
Text = subheading,
|
Text = subheading,
|
||||||
Font = OsuFont.GetFont(size: 18),
|
Font = OsuFont.GetFont(size: 18),
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
|
@ -12,7 +12,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK.Graphics;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
@ -31,9 +31,10 @@ namespace osu.Game.Overlays.Settings
|
|||||||
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
|
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
|
||||||
public virtual IEnumerable<string> FilterTerms => new[] { Header.ToString() };
|
public virtual IEnumerable<string> FilterTerms => new[] { Header.ToString() };
|
||||||
|
|
||||||
private const int header_size = 26;
|
public const int ITEM_SPACING = 14;
|
||||||
private const int margin = 20;
|
|
||||||
private const int border_size = 2;
|
private const int header_size = 24;
|
||||||
|
private const int border_size = 4;
|
||||||
|
|
||||||
public bool MatchingFilter
|
public bool MatchingFilter
|
||||||
{
|
{
|
||||||
@ -54,8 +55,9 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = header_size
|
Top = 36
|
||||||
},
|
},
|
||||||
|
Spacing = new Vector2(0, ITEM_SPACING),
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -63,14 +65,14 @@ namespace osu.Game.Overlays.Settings
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Name = "separator",
|
Name = "separator",
|
||||||
Colour = new Color4(0, 0, 0, 255),
|
Colour = colourProvider.Background6,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = border_size,
|
Height = border_size,
|
||||||
},
|
},
|
||||||
@ -78,8 +80,8 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = margin + border_size,
|
Top = 28,
|
||||||
Bottom = margin + 10,
|
Bottom = 40,
|
||||||
},
|
},
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -87,13 +89,11 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
header = new OsuSpriteText
|
header = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: header_size),
|
Font = OsuFont.TorusAlternate.With(size: header_size),
|
||||||
Text = Header,
|
Text = Header,
|
||||||
Colour = colours.Yellow,
|
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
{
|
{
|
||||||
Left = SettingsPanel.CONTENT_MARGINS,
|
Horizontal = SettingsPanel.CONTENT_MARGINS
|
||||||
Right = SettingsPanel.CONTENT_MARGINS
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FlowContent
|
FlowContent
|
||||||
|
@ -46,13 +46,17 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
FlowContent = new FillFlowContainer
|
FlowContent = new FillFlowContainer
|
||||||
{
|
{
|
||||||
|
Margin = new MarginPadding { Top = SettingsSection.ITEM_SPACING },
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 8),
|
Spacing = new Vector2(0, SettingsSection.ITEM_SPACING),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const int header_height = 43;
|
||||||
|
private const int header_font_size = 20;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -60,9 +64,9 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = Header.ToString().ToUpper(), // TODO: Add localisation support after https://github.com/ppy/osu-framework/pull/4603 is merged.
|
Text = Header,
|
||||||
Margin = new MarginPadding { Vertical = 30, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS },
|
Margin = new MarginPadding { Vertical = (header_height - header_font_size) * 0.5f, Horizontal = SettingsPanel.CONTENT_MARGINS },
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(size: header_font_size),
|
||||||
},
|
},
|
||||||
FlowContent
|
FlowContent
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
@ -13,5 +15,17 @@ namespace osu.Game.Overlays.Settings
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
CommitOnFocusLost = true
|
CommitOnFocusLost = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public override Bindable<string> Current
|
||||||
|
{
|
||||||
|
get => base.Current;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Default == null)
|
||||||
|
throw new InvalidOperationException($"Bindable settings of type {nameof(Bindable<string>)} should have a non-null default value.");
|
||||||
|
|
||||||
|
base.Current = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -15,22 +16,23 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class Sidebar : Container<SidebarButton>, IStateful<ExpandedState>
|
public class Sidebar : Container<SidebarIconButton>, IStateful<ExpandedState>
|
||||||
{
|
{
|
||||||
private readonly FillFlowContainer<SidebarButton> content;
|
private readonly Box background;
|
||||||
public const float DEFAULT_WIDTH = Toolbar.Toolbar.HEIGHT * 1.4f;
|
private readonly FillFlowContainer<SidebarIconButton> content;
|
||||||
|
public const float DEFAULT_WIDTH = 70;
|
||||||
public const int EXPANDED_WIDTH = 200;
|
public const int EXPANDED_WIDTH = 200;
|
||||||
|
|
||||||
public event Action<ExpandedState> StateChanged;
|
public event Action<ExpandedState> StateChanged;
|
||||||
|
|
||||||
protected override Container<SidebarButton> Content => content;
|
protected override Container<SidebarIconButton> Content => content;
|
||||||
|
|
||||||
public Sidebar()
|
public Sidebar()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
background = new Box
|
||||||
{
|
{
|
||||||
Colour = OsuColour.Gray(0.02f),
|
Colour = OsuColour.Gray(0.02f),
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -39,7 +41,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
content = new FillFlowContainer<SidebarButton>
|
content = new FillFlowContainer<SidebarIconButton>
|
||||||
{
|
{
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -52,6 +54,12 @@ namespace osu.Game.Overlays.Settings
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
background.Colour = colourProvider.Background5;
|
||||||
|
}
|
||||||
|
|
||||||
private ScheduledDelegate expandEvent;
|
private ScheduledDelegate expandEvent;
|
||||||
private ExpandedState state;
|
private ExpandedState state;
|
||||||
|
|
||||||
@ -80,8 +88,6 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
public SidebarScrollContainer()
|
public SidebarScrollContainer()
|
||||||
{
|
{
|
||||||
Content.Anchor = Anchor.CentreLeft;
|
|
||||||
Content.Origin = Anchor.CentreLeft;
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
ScrollbarVisible = false;
|
ScrollbarVisible = false;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user