mirror of
https://github.com/ppy/osu.git
synced 2025-02-21 03:43:21 +08:00
Merge branch 'master' into localise-toasts
This commit is contained in:
commit
c97cfdd978
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +1,2 @@
|
|||||||
|
github: ppy
|
||||||
custom: https://osu.ppy.sh/home/support
|
custom: https://osu.ppy.sh/home/support
|
||||||
|
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)
|
||||||
|
2
.github/workflows/report-nunit.yml
vendored
2
.github/workflows/report-nunit.yml
vendored
@ -30,3 +30,5 @@ jobs:
|
|||||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||||
path: "*.trx"
|
path: "*.trx"
|
||||||
reporter: dotnet-trx
|
reporter: dotnet-trx
|
||||||
|
list-suites: 'failed'
|
||||||
|
list-tests: 'failed'
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1015.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1011.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1014.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. -->
|
||||||
|
@ -42,9 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
|
|
||||||
|
|
||||||
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
|
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
||||||
|
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -101,27 +102,27 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
public override float GetBeatSnapDistanceAt(HitObject referenceObject)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float DurationToDistance(double referenceTime, double duration)
|
public override float DurationToDistance(HitObject referenceObject, double duration)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double DistanceToDuration(double referenceTime, float distance)
|
public override double DistanceToDuration(HitObject referenceObject, float distance)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -388,7 +388,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||||
}
|
}
|
||||||
|
|
||||||
AddStep("load player", () =>
|
AddStep("load player", () =>
|
||||||
|
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
Debug.Assert(distanceData != null);
|
Debug.Assert(distanceData != null);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
|
||||||
|
|
||||||
double beatLength;
|
double beatLength;
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||||
else
|
else
|
||||||
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
|
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
||||||
|
|
||||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||||
StartTime = (int)Math.Round(hitObject.StartTime);
|
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||||
|
@ -13,6 +13,7 @@ SliderTickRate:1
|
|||||||
|
|
||||||
[TimingPoints]
|
[TimingPoints]
|
||||||
0,500,4,1,0,100,1,0
|
0,500,4,1,0,100,1,0
|
||||||
|
10000,-150,4,1,0,100,1,0
|
||||||
|
|
||||||
[HitObjects]
|
[HitObjects]
|
||||||
51,192,500,128,0,1500:1:0:0:0:
|
51,192,500,128,0,1500:1:0:0:0:
|
||||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
// For non-mania beatmap, speed changes should only happen through timing points
|
// For non-mania beatmap, speed changes should only happen through timing points
|
||||||
if (!isForCurrentRuleset)
|
if (!isForCurrentRuleset)
|
||||||
p.DifficultyPoint = new DifficultyControlPoint();
|
p.EffectPoint = new EffectControlPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
BarLines.ForEach(Playfield.Add);
|
BarLines.ForEach(Playfield.Add);
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -179,15 +180,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length;
|
||||||
|
|
||||||
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
|
public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
|
||||||
|
|
||||||
public double DistanceToDuration(double referenceTime, float distance) => distance;
|
public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
|
||||||
|
|
||||||
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0;
|
||||||
|
|
||||||
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
|
public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
{
|
{
|
||||||
new Spinner
|
new Spinner
|
||||||
{
|
{
|
||||||
Duration = 2000,
|
Duration = 6000,
|
||||||
Position = OsuPlayfield.BASE_SIZE / 2
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||||
gameplayState.Beatmap.Difficulty.CircleSize = val;
|
gameplayState.Beatmap.Difficulty.CircleSize = val;
|
||||||
Scheduler.AddOnce(() => loadContent(false));
|
Scheduler.AddOnce(loadContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("test cursor container", () => loadContent(false));
|
AddStep("test cursor container", () => loadContent(false));
|
||||||
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize);
|
AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize);
|
||||||
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
||||||
|
|
||||||
AddStep("load content", () => loadContent());
|
AddStep("load content", loadContent);
|
||||||
|
|
||||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
||||||
|
|
||||||
@ -98,7 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
|
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null)
|
private void loadContent() => loadContent(false);
|
||||||
|
|
||||||
|
private void loadContent(bool automated, Func<SkinProvidingContainer> skinProvider = null)
|
||||||
{
|
{
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
{
|
{
|
||||||
|
@ -407,8 +407,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
|
||||||
|
|
||||||
SelectedMods.Value = new[] { new OsuModClassic() };
|
SelectedMods.Value = new[] { new OsuModClassic() };
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
@ -439,6 +437,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public TestSlider()
|
public TestSlider()
|
||||||
{
|
{
|
||||||
|
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
||||||
|
|
||||||
DefaultsApplied += _ =>
|
DefaultsApplied += _ =>
|
||||||
{
|
{
|
||||||
HeadCircle.HitWindows = new TestHitWindows();
|
HeadCircle.HitWindows = new TestHitWindows();
|
||||||
|
@ -13,6 +13,7 @@ using osuTK.Graphics;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -328,10 +329,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
|
cpi.Add(0, new DifficultyControlPoint { SliderVelocity = speedMultiplier });
|
||||||
|
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
slider.ApplyDefaults(cpi, new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = circleSize,
|
||||||
|
SliderTickRate = 3
|
||||||
|
});
|
||||||
|
|
||||||
var drawable = CreateDrawableSlider(slider);
|
var drawable = CreateDrawableSlider(slider);
|
||||||
|
|
||||||
|
@ -348,6 +348,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
StartTime = time_slider_start,
|
StartTime = time_slider_start,
|
||||||
Position = new Vector2(0, 0),
|
Position = new Vector2(0, 0),
|
||||||
|
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f },
|
||||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
@ -362,8 +363,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
p.OnLoadComplete += _ =>
|
p.OnLoadComplete += _ =>
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -369,8 +369,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
p.OnLoadComplete += _ =>
|
p.OnLoadComplete += _ =>
|
||||||
@ -399,6 +397,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public TestSlider()
|
public TestSlider()
|
||||||
{
|
{
|
||||||
|
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
||||||
|
|
||||||
DefaultsApplied += _ =>
|
DefaultsApplied += _ =>
|
||||||
{
|
{
|
||||||
HeadCircle.HitWindows = new TestHitWindows();
|
HeadCircle.HitWindows = new TestHitWindows();
|
||||||
|
@ -11,6 +11,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||||
{
|
{
|
||||||
@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
||||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
|
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
case IHasDuration endTimeData:
|
case IHasDuration endTimeData:
|
||||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
public double OverallDifficulty { get; set; }
|
public double OverallDifficulty { get; set; }
|
||||||
public double DrainRate { get; set; }
|
public double DrainRate { get; set; }
|
||||||
public int HitCircleCount { get; set; }
|
public int HitCircleCount { get; set; }
|
||||||
|
public int SliderCount { get; set; }
|
||||||
public int SpinnerCount { get; set; }
|
public int SpinnerCount { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||||
|
|
||||||
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
||||||
|
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
||||||
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
|
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
|
||||||
|
|
||||||
return new OsuDifficultyAttributes
|
return new OsuDifficultyAttributes
|
||||||
@ -78,6 +79,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
DrainRate = drainRate,
|
DrainRate = drainRate,
|
||||||
MaxCombo = maxCombo,
|
MaxCombo = maxCombo,
|
||||||
HitCircleCount = hitCirclesCount,
|
HitCircleCount = hitCirclesCount,
|
||||||
|
SliderCount = sliderCount,
|
||||||
SpinnerCount = spinnerCount,
|
SpinnerCount = spinnerCount,
|
||||||
Skills = skills
|
Skills = skills
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
|
private int effectiveMissCount;
|
||||||
|
|
||||||
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||||
: base(ruleset, attributes, score)
|
: base(ruleset, attributes, score)
|
||||||
{
|
{
|
||||||
@ -39,19 +41,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
|
effectiveMissCount = calculateEffectiveMissCount();
|
||||||
|
|
||||||
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.
|
// 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 * effectiveMissCount);
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModSpunOut))
|
if (mods.Any(m => m is OsuModSpunOut))
|
||||||
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
|
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModRelax))
|
if (mods.Any(h => h is OsuModRelax))
|
||||||
{
|
{
|
||||||
countMiss += countOk + countMeh;
|
effectiveMissCount += countOk + countMeh;
|
||||||
multiplier *= 0.6;
|
multiplier *= 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +100,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
aimValue *= lengthBonus;
|
aimValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (countMiss > 0)
|
if (effectiveMissCount > 0)
|
||||||
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
|
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
|
||||||
|
|
||||||
// Combo scaling.
|
// Combo scaling.
|
||||||
if (Attributes.MaxCombo > 0)
|
if (Attributes.MaxCombo > 0)
|
||||||
@ -115,7 +118,7 @@ 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;
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModBlinds))
|
if (mods.Any(m => m is OsuModBlinds))
|
||||||
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * countMiss)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate);
|
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate);
|
||||||
else if (mods.Any(h => h is OsuModHidden))
|
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.
|
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||||
@ -142,8 +145,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
speedValue *= lengthBonus;
|
speedValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (countMiss > 0)
|
if (effectiveMissCount > 0)
|
||||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
|
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||||
|
|
||||||
// Combo scaling.
|
// Combo scaling.
|
||||||
if (Attributes.MaxCombo > 0)
|
if (Attributes.MaxCombo > 0)
|
||||||
@ -231,8 +234,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
flashlightValue *= 1.3;
|
flashlightValue *= 1.3;
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (countMiss > 0)
|
if (effectiveMissCount > 0)
|
||||||
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
|
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||||
|
|
||||||
// Combo scaling.
|
// Combo scaling.
|
||||||
if (Attributes.MaxCombo > 0)
|
if (Attributes.MaxCombo > 0)
|
||||||
@ -250,6 +253,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
return flashlightValue;
|
return flashlightValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int calculateEffectiveMissCount()
|
||||||
|
{
|
||||||
|
// guess the number of misses + slider breaks from combo
|
||||||
|
double comboBasedMissCount = 0.0;
|
||||||
|
|
||||||
|
if (Attributes.SliderCount > 0)
|
||||||
|
{
|
||||||
|
double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount;
|
||||||
|
if (scoreMaxCombo < fullComboThreshold)
|
||||||
|
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're clamping misscount because since its derived from combo it can be higher than total hits and that breaks some calculations
|
||||||
|
comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits);
|
||||||
|
|
||||||
|
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
|
||||||
|
}
|
||||||
|
|
||||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
|
|
||||||
private void setDistances()
|
private void setDistances()
|
||||||
{
|
{
|
||||||
|
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
|
||||||
|
if (BaseObject is Spinner || lastObject is Spinner)
|
||||||
|
return;
|
||||||
|
|
||||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
|
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
|
||||||
|
|
||||||
@ -71,11 +75,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
|
|
||||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||||
|
|
||||||
// Don't need to jump to reach spinners
|
|
||||||
if (!(BaseObject is Spinner))
|
|
||||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||||
|
|
||||||
if (lastLastObject != null)
|
if (lastLastObject != null && !(lastLastObject is Spinner))
|
||||||
{
|
{
|
||||||
Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject);
|
Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject);
|
||||||
|
|
||||||
|
@ -8,12 +8,14 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -67,6 +69,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; }
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
@ -75,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
case SliderPlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
|
||||||
|
var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
|
||||||
|
|
||||||
|
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -212,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void updateSlider()
|
private void updateSlider()
|
||||||
{
|
{
|
||||||
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
|
|
||||||
bodyPiece.UpdateFrom(HitObject);
|
bodyPiece.UpdateFrom(HitObject);
|
||||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||||
|
@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void updatePath()
|
private void updatePath()
|
||||||
{
|
{
|
||||||
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
editorBeatmap?.Update(HitObject);
|
editorBeatmap?.Update(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||||
{
|
{
|
||||||
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
|
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
|
||||||
: base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
|
: base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
}
|
}
|
||||||
|
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// 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 osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
|
||||||
|
/// </summary>
|
||||||
|
private const float min_alpha = 0.0002f;
|
||||||
|
|
||||||
|
private const float transition_duration = 100;
|
||||||
|
|
||||||
|
public override string Name => "No Scope";
|
||||||
|
public override string Acronym => "NS";
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
|
||||||
|
public override string Description => "Where's the cursor?";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
private BindableNumber<int> currentCombo;
|
||||||
|
|
||||||
|
private float targetAlpha;
|
||||||
|
|
||||||
|
[SettingSource(
|
||||||
|
"Hidden at combo",
|
||||||
|
"The combo count at which the cursor becomes completely hidden",
|
||||||
|
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
||||||
|
)]
|
||||||
|
public BindableInt HiddenComboCount { get; } = new BindableInt
|
||||||
|
{
|
||||||
|
Default = 10,
|
||||||
|
Value = 10,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||||
|
|
||||||
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
|
{
|
||||||
|
if (HiddenComboCount.Value == 0) return;
|
||||||
|
|
||||||
|
currentCombo = scoreProcessor.Combo.GetBoundCopy();
|
||||||
|
currentCombo.BindValueChanged(combo =>
|
||||||
|
{
|
||||||
|
targetAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HiddenComboSlider : OsuSliderBar<int>
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
|
||||||
|
}
|
||||||
|
}
|
@ -140,9 +140,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
|
|
||||||
|
|
||||||
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
|
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
||||||
|
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||||
@ -175,7 +174,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
Position = Position,
|
Position = Position,
|
||||||
StackHeight = StackHeight,
|
StackHeight = StackHeight,
|
||||||
SampleControlPoint = SampleControlPoint,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModBarrelRoll(),
|
new OsuModBarrelRoll(),
|
||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
|
new OsuModNoScope(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint;
|
||||||
|
|
||||||
double beatLength;
|
double beatLength;
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||||
else
|
else
|
||||||
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
|
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
||||||
|
|
||||||
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
|
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
|
||||||
|
|
||||||
|
@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
|
|
||||||
|
|
||||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
|
double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
|
|
||||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||||
|
@ -192,15 +192,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
||||||
Assert.AreEqual(0, difficultyPoint.Time);
|
Assert.AreEqual(0, difficultyPoint.Time);
|
||||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||||
|
|
||||||
difficultyPoint = controlPoints.DifficultyPointAt(48428);
|
difficultyPoint = controlPoints.DifficultyPointAt(48428);
|
||||||
Assert.AreEqual(0, difficultyPoint.Time);
|
Assert.AreEqual(0, difficultyPoint.Time);
|
||||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||||
|
|
||||||
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
||||||
Assert.AreEqual(116999, difficultyPoint.Time);
|
Assert.AreEqual(116999, difficultyPoint.Time);
|
||||||
Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1);
|
Assert.AreEqual(0.75, difficultyPoint.SliderVelocity, 0.1);
|
||||||
|
|
||||||
var soundPoint = controlPoints.SamplePointAt(0);
|
var soundPoint = controlPoints.SamplePointAt(0);
|
||||||
Assert.AreEqual(956, soundPoint.Time);
|
Assert.AreEqual(956, soundPoint.Time);
|
||||||
@ -227,7 +227,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.IsTrue(effectPoint.KiaiMode);
|
Assert.IsTrue(effectPoint.KiaiMode);
|
||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||||
|
|
||||||
effectPoint = controlPoints.EffectPointAt(119637);
|
effectPoint = controlPoints.EffectPointAt(116637);
|
||||||
Assert.AreEqual(95901, effectPoint.Time);
|
Assert.AreEqual(95901, effectPoint.Time);
|
||||||
Assert.IsFalse(effectPoint.KiaiMode);
|
Assert.IsFalse(effectPoint.KiaiMode);
|
||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||||
@ -249,10 +249,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3));
|
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3));
|
||||||
Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3));
|
Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3));
|
||||||
|
|
||||||
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
|
||||||
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(1500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
|
||||||
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(2500).SliderVelocity, Is.EqualTo(0.75).Within(0.1));
|
||||||
Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(3500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
|
||||||
|
|
||||||
Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True);
|
Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True);
|
||||||
Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True);
|
Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True);
|
||||||
@ -279,10 +279,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
|
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
{
|
{
|
||||||
var controlPoints = decoder.Decode(stream).ControlPointInfo;
|
var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
|
||||||
|
|
||||||
Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(0).SliderVelocity, Is.EqualTo(0.5).Within(0.1));
|
||||||
Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1).Within(0.1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,12 +394,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu"))
|
using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
{
|
{
|
||||||
var controlPointInfo = decoder.Decode(stream).ControlPointInfo;
|
var controlPointInfo = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
|
||||||
|
|
||||||
Assert.That(controlPointInfo.DifficultyPointAt(5).SpeedMultiplier, Is.EqualTo(1));
|
Assert.That(controlPointInfo.DifficultyPointAt(5).SliderVelocity, Is.EqualTo(1));
|
||||||
Assert.That(controlPointInfo.DifficultyPointAt(1000).SpeedMultiplier, Is.EqualTo(10));
|
Assert.That(controlPointInfo.DifficultyPointAt(1000).SliderVelocity, Is.EqualTo(10));
|
||||||
Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d));
|
Assert.That(controlPointInfo.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1.8518518518518519d));
|
||||||
Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5));
|
Assert.That(controlPointInfo.DifficultyPointAt(3000).SliderVelocity, Is.EqualTo(0.5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +46,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
sort(decoded.beatmap);
|
sort(decoded.beatmap);
|
||||||
sort(decodedAfterEncode.beatmap);
|
sort(decodedAfterEncode.beatmap);
|
||||||
|
|
||||||
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
|
compareBeatmaps(decoded, decodedAfterEncode);
|
||||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCaseSource(nameof(allBeatmaps))]
|
[TestCaseSource(nameof(allBeatmaps))]
|
||||||
@ -62,8 +61,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
sort(decoded.beatmap);
|
sort(decoded.beatmap);
|
||||||
sort(decodedAfterEncode.beatmap);
|
sort(decodedAfterEncode.beatmap);
|
||||||
|
|
||||||
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
|
compareBeatmaps(decoded, decodedAfterEncode);
|
||||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCaseSource(nameof(allBeatmaps))]
|
[TestCaseSource(nameof(allBeatmaps))]
|
||||||
@ -77,12 +75,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
|
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
|
||||||
|
|
||||||
// in this process, we may lose some detail in the control points section.
|
compareBeatmaps(decoded, decodedAfterEncode);
|
||||||
// let's focus on only the hitobjects.
|
|
||||||
var originalHitObjects = decoded.beatmap.HitObjects.Serialize();
|
|
||||||
var newHitObjects = decodedAfterEncode.beatmap.HitObjects.Serialize();
|
|
||||||
|
|
||||||
Assert.That(newHitObjects, Is.EqualTo(originalHitObjects));
|
|
||||||
|
|
||||||
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
|
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
|
||||||
{
|
{
|
||||||
@ -97,7 +90,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
// completely ignore "legacy" types, which have been moved to HitObjects.
|
// completely ignore "legacy" types, which have been moved to HitObjects.
|
||||||
// even though these would mostly be ignored by the Add call, they will still be available in groups,
|
// even though these would mostly be ignored by the Add call, they will still be available in groups,
|
||||||
// which isn't what we want to be testing here.
|
// which isn't what we want to be testing here.
|
||||||
if (point is SampleControlPoint)
|
if (point is SampleControlPoint || point is DifficultyControlPoint)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
newControlPoints.Add(point.Time, point.DeepClone());
|
newControlPoints.Add(point.Time, point.DeepClone());
|
||||||
@ -107,6 +100,19 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void compareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual)
|
||||||
|
{
|
||||||
|
// Check all control points that are still considered to be at a global level.
|
||||||
|
Assert.That(expected.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.TimingPoints.Serialize()));
|
||||||
|
Assert.That(expected.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.EffectPoints.Serialize()));
|
||||||
|
|
||||||
|
// Check all hitobjects.
|
||||||
|
Assert.That(expected.beatmap.HitObjects.Serialize(), Is.EqualTo(actual.beatmap.HitObjects.Serialize()));
|
||||||
|
|
||||||
|
// Check skin.
|
||||||
|
Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
|
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
|
||||||
{
|
{
|
||||||
@ -156,7 +162,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name)
|
private (IBeatmap beatmap, TestLegacySkin skin) decodeFromLegacy(Stream stream, string name)
|
||||||
{
|
{
|
||||||
using (var reader = new LineBufferedReader(stream))
|
using (var reader = new LineBufferedReader(stream))
|
||||||
{
|
{
|
||||||
@ -174,7 +180,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
|
private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin skin) fullBeatmap)
|
||||||
{
|
{
|
||||||
var (beatmap, beatmapSkin) = fullBeatmap;
|
var (beatmap, beatmapSkin) = fullBeatmap;
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
|
@ -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,7 +29,9 @@ 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))
|
||||||
|
{
|
||||||
|
host.Run(new RealmTestGame(() =>
|
||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
var testStorage = storage.GetStorageForDirectory(caller);
|
||||||
|
|
||||||
@ -43,12 +46,15 @@ namespace osu.Game.Tests.Database
|
|||||||
realmFactory.Compact();
|
realmFactory.Compact();
|
||||||
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
|
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))
|
||||||
|
{
|
||||||
|
host.Run(new RealmTestGame(async () =>
|
||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
var testStorage = storage.GetStorageForDirectory(caller);
|
||||||
|
|
||||||
@ -61,11 +67,73 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
|
||||||
realmFactory.Compact();
|
realmFactory.Compact();
|
||||||
Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
|
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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.Game.Models;
|
||||||
|
using osu.Game.Stores;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
public class RulesetStoreTests : RealmTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestCreateStore()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
|
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
var rulesets2 = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
|
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||||
|
|
||||||
|
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
||||||
|
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRetrievedRulesetsAreDetached()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
|
||||||
|
Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
|
||||||
|
Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
Normal file
104
osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using FileInfo = osu.Game.IO.FileInfo;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckAudioInVideoTest
|
||||||
|
{
|
||||||
|
private CheckAudioInVideo check;
|
||||||
|
private IBeatmap beatmap;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckAudioInVideo();
|
||||||
|
beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Files = new List<BeatmapSetFileInfo>(new[]
|
||||||
|
{
|
||||||
|
new BeatmapSetFileInfo
|
||||||
|
{
|
||||||
|
Filename = "abc123.mp4",
|
||||||
|
FileInfo = new FileInfo { Hash = "abcdef" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRegularVideoFile()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Videos/test-video.mp4"))
|
||||||
|
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVideoFileWithAudio()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Videos/test-video-with-audio.mp4"))
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateHasAudioTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVideoFileWithTrackButNoAudio()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Videos/test-video-with-track-but-no-audio.mp4"))
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateHasAudioTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissingFile()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(null)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateMissingFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(Stream resourceStream)
|
||||||
|
{
|
||||||
|
var storyboard = new Storyboard();
|
||||||
|
var layer = storyboard.GetLayer("Video");
|
||||||
|
layer.Add(new StoryboardVideo("abc123.mp4", 0));
|
||||||
|
|
||||||
|
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||||
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||||
|
mockWorkingBeatmap.As<IWorkingBeatmap>().SetupGet(w => w.Storyboard).Returns(storyboard);
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
Normal file
128
osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using ManagedBass;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK.Audio;
|
||||||
|
using FileInfo = osu.Game.IO.FileInfo;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTooShortAudioFilesTest
|
||||||
|
{
|
||||||
|
private CheckTooShortAudioFiles check;
|
||||||
|
private IBeatmap beatmap;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTooShortAudioFiles();
|
||||||
|
beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Files = new List<BeatmapSetFileInfo>(new[]
|
||||||
|
{
|
||||||
|
new BeatmapSetFileInfo
|
||||||
|
{
|
||||||
|
Filename = "abc123.wav",
|
||||||
|
FileInfo = new FileInfo { Hash = "abcdef" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0 = No output device. This still allows decoding.
|
||||||
|
if (!Bass.Init(0) && Bass.LastError != Errors.Already)
|
||||||
|
throw new AudioException("Could not initialize Bass.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifferentExtension()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||||
|
beatmap.BeatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo
|
||||||
|
{
|
||||||
|
Filename = "abc123.jpg",
|
||||||
|
FileInfo = new FileInfo { Hash = "abcdef" }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should fail to load, but not produce an error due to the extension not being expected to load.
|
||||||
|
Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRegularAudioFile()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.mp3"))
|
||||||
|
{
|
||||||
|
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBlankAudioFile()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Samples/blank.wav"))
|
||||||
|
{
|
||||||
|
// This is a 0 ms duration audio file, commonly used to silence sliderslides/ticks, and so should be fine.
|
||||||
|
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortAudioFile()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3"))
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateTooShort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissingAudioFile()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
|
||||||
|
{
|
||||||
|
Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCorruptAudioFile()
|
||||||
|
{
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateBadFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false)
|
||||||
|
{
|
||||||
|
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||||
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs
Normal file
86
osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using FileInfo = osu.Game.IO.FileInfo;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckZeroByteFilesTest
|
||||||
|
{
|
||||||
|
private CheckZeroByteFiles check;
|
||||||
|
private IBeatmap beatmap;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckZeroByteFiles();
|
||||||
|
beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Files = new List<BeatmapSetFileInfo>(new[]
|
||||||
|
{
|
||||||
|
new BeatmapSetFileInfo
|
||||||
|
{
|
||||||
|
Filename = "abc123.jpg",
|
||||||
|
FileInfo = new FileInfo { Hash = "abcdef" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNonZeroBytes()
|
||||||
|
{
|
||||||
|
Assert.IsEmpty(check.Run(getContext(byteLength: 44)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestZeroBytes()
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(byteLength: 0)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckZeroByteFiles.IssueTemplateZeroBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissing()
|
||||||
|
{
|
||||||
|
Assert.IsEmpty(check.Run(getContextMissing()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(long byteLength)
|
||||||
|
{
|
||||||
|
var mockStream = new Mock<Stream>();
|
||||||
|
mockStream.Setup(s => s.Length).Returns(byteLength);
|
||||||
|
|
||||||
|
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
|
||||||
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(mockStream.Object);
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContextMissing()
|
||||||
|
{
|
||||||
|
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
|
||||||
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns((Stream)null);
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
@ -55,8 +56,6 @@ namespace osu.Game.Tests.Editing
|
|||||||
|
|
||||||
composer.EditorBeatmap.Difficulty.SliderMultiplier = 1;
|
composer.EditorBeatmap.Difficulty.SliderMultiplier = 1;
|
||||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
|
|
||||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
|
|
||||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,13 +72,13 @@ namespace osu.Game.Tests.Editing
|
|||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
public void TestSpeedMultiplier(float multiplier)
|
public void TestSpeedMultiplier(float multiplier)
|
||||||
{
|
{
|
||||||
AddStep($"set multiplier = {multiplier}", () =>
|
assertSnapDistance(100 * multiplier, new HitObject
|
||||||
{
|
{
|
||||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
DifficultyControlPoint = new DifficultyControlPoint
|
||||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier });
|
{
|
||||||
|
SliderVelocity = multiplier
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
assertSnapDistance(100 * multiplier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
@ -197,20 +196,20 @@ namespace osu.Game.Tests.Editing
|
|||||||
assertSnappedDistance(400, 400);
|
assertSnappedDistance(400, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSnapDistance(float expectedDistance)
|
private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
|
||||||
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance);
|
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()) == expectedDistance);
|
||||||
|
|
||||||
private void assertDurationToDistance(double duration, float expectedDistance)
|
private void assertDurationToDistance(double duration, float expectedDistance)
|
||||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(0, duration) == expectedDistance);
|
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
|
||||||
|
|
||||||
private void assertDistanceToDuration(float distance, double expectedDuration)
|
private void assertDistanceToDuration(float distance, double expectedDuration)
|
||||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(0, distance) == expectedDuration);
|
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(new HitObject(), distance) == expectedDuration);
|
||||||
|
|
||||||
private void assertSnappedDuration(float distance, double expectedDuration)
|
private void assertSnappedDuration(float distance, double expectedDuration)
|
||||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(0, distance) == expectedDuration);
|
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(new HitObject(), distance) == expectedDuration);
|
||||||
|
|
||||||
private void assertSnappedDistance(float distance, float expectedDistance)
|
private void assertSnappedDistance(float distance, float expectedDistance)
|
||||||
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(0, distance) == expectedDistance);
|
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(new HitObject(), distance) == expectedDistance);
|
||||||
|
|
||||||
private class TestHitObjectComposer : OsuHitObjectComposer
|
private class TestHitObjectComposer : OsuHitObjectComposer
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Tests
|
|||||||
protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false)
|
protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false)
|
||||||
{
|
{
|
||||||
var osu = new TestOsuGameBase(withBeatmap);
|
var osu = new TestOsuGameBase(withBeatmap);
|
||||||
Task.Run(() => host.Run(osu))
|
Task.Factory.StartNew(() => host.Run(osu), TaskCreationOptions.LongRunning)
|
||||||
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
||||||
|
|
||||||
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAddRedundantDifficulty()
|
public void TestAddRedundantDifficulty()
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
|
|
||||||
cpi.Add(0, new DifficultyControlPoint()); // is redundant
|
cpi.Add(0, new DifficultyControlPoint()); // is redundant
|
||||||
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
|
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||||
|
|
||||||
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
|
cpi.Add(1000, new DifficultyControlPoint { SliderVelocity = 2 }); // is not redundant
|
||||||
|
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
||||||
@ -159,7 +159,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAddControlPointToGroup()
|
public void TestAddControlPointToGroup()
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
|
|
||||||
var group = cpi.GroupAt(1000, true);
|
var group = cpi.GroupAt(1000, true);
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
@ -174,23 +174,23 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAddDuplicateControlPointToGroup()
|
public void TestAddDuplicateControlPointToGroup()
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
|
|
||||||
var group = cpi.GroupAt(1000, true);
|
var group = cpi.GroupAt(1000, true);
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
group.Add(new DifficultyControlPoint());
|
group.Add(new DifficultyControlPoint());
|
||||||
group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 });
|
group.Add(new DifficultyControlPoint { SliderVelocity = 2 });
|
||||||
|
|
||||||
Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
|
Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
|
||||||
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
||||||
Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2));
|
Assert.That(cpi.DifficultyPoints.First().SliderVelocity, Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRemoveControlPointFromGroup()
|
public void TestRemoveControlPointFromGroup()
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
|
|
||||||
var group = cpi.GroupAt(1000, true);
|
var group = cpi.GroupAt(1000, true);
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
@ -208,14 +208,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestOrdering()
|
public void TestOrdering()
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
|
|
||||||
cpi.Add(0, new TimingControlPoint());
|
cpi.Add(0, new TimingControlPoint());
|
||||||
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||||
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||||
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
||||||
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
|
cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
|
||||||
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
|
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
|
||||||
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
||||||
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
@ -230,14 +230,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestClear()
|
public void TestClear()
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new LegacyControlPointInfo();
|
||||||
|
|
||||||
cpi.Add(0, new TimingControlPoint());
|
cpi.Add(0, new TimingControlPoint());
|
||||||
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||||
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||||
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
||||||
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
|
cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
|
||||||
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
|
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
|
||||||
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
||||||
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Tests/Resources/Samples/blank.wav
Normal file
BIN
osu.Game.Tests/Resources/Samples/blank.wav
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Samples/corrupt.wav
Normal file
BIN
osu.Game.Tests/Resources/Samples/corrupt.wav
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
osu.Game.Tests/Resources/Samples/test-sample-cut.mp3
Normal file
BIN
osu.Game.Tests/Resources/Samples/test-sample-cut.mp3
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4
Normal file
BIN
osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
osu.Game.Tests/Resources/Videos/test-video.mp4
Normal file
BIN
osu.Game.Tests/Resources/Videos/test-video.mp4
Normal file
Binary file not shown.
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -123,5 +163,11 @@ namespace osu.Game.Tests.Visual.Audio
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void waitTrackPlay() => AddWaitStep("Let track play", 10);
|
private void waitTrackPlay() => AddWaitStep("Let track play", 10);
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
track?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public new float DistanceSpacing => base.DistanceSpacing;
|
public new float DistanceSpacing => base.DistanceSpacing;
|
||||||
|
|
||||||
public TestDistanceSnapGrid(double? endTime = null)
|
public TestDistanceSnapGrid(double? endTime = null)
|
||||||
: base(grid_position, 0, endTime)
|
: base(new HitObject(), grid_position, 0, endTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,15 +159,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10;
|
||||||
|
|
||||||
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
|
public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
|
||||||
|
|
||||||
public double DistanceToDuration(double referenceTime, float distance) => distance;
|
public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
|
||||||
|
|
||||||
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0;
|
||||||
|
|
||||||
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
|
public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -30,22 +31,36 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
PushAndConfirm(() => new EditorLoader());
|
PushAndConfirm(() => new EditorLoader());
|
||||||
|
|
||||||
AddUntilStep("wait for editor load", () => editor != null);
|
AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
|
||||||
|
|
||||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
|
||||||
|
// We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
|
||||||
|
|
||||||
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
|
||||||
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
|
||||||
|
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
|
||||||
|
AddStep("Set artist and title", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||||
|
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||||
|
});
|
||||||
|
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty");
|
||||||
|
|
||||||
|
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||||
|
|
||||||
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||||
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", () =>
|
checkMutations();
|
||||||
{
|
|
||||||
InputManager.Keys(PlatformAction.Save);
|
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||||
InputManager.Key(Key.Escape);
|
|
||||||
});
|
checkMutations();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
@ -56,7 +71,16 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
AddStep("Enter editor", () => InputManager.Key(Key.Number5));
|
||||||
|
|
||||||
AddUntilStep("Wait for editor load", () => editor != null);
|
AddUntilStep("Wait for editor load", () => editor != null);
|
||||||
|
|
||||||
|
checkMutations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMutations()
|
||||||
|
{
|
||||||
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);
|
||||||
|
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||||
|
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class TestSceneAllRulesetPlayers : RateAdjustedBeatmapTestScene
|
public abstract class TestSceneAllRulesetPlayers : RateAdjustedBeatmapTestScene
|
||||||
{
|
{
|
||||||
protected Player Player;
|
protected Player Player { get; private set; }
|
||||||
|
|
||||||
|
protected OsuConfigManager Config { get; private set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(RulesetStore rulesets)
|
private void load(RulesetStore rulesets)
|
||||||
{
|
{
|
||||||
OsuConfigManager manager;
|
Dependencies.Cache(Config = new OsuConfigManager(LocalStorage));
|
||||||
Dependencies.Cache(manager = new OsuConfigManager(LocalStorage));
|
Config.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
|
||||||
manager.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -17,6 +19,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
return new FailPlayer();
|
return new FailPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuWithoutRedTint()
|
||||||
|
{
|
||||||
|
AddStep("Disable red tint", () => Config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
|
||||||
|
TestOsu();
|
||||||
|
AddStep("Enable red tint", () => Config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||||
|
@ -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;
|
||||||
|
@ -291,7 +291,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType<EpilepsyWarning>().Any() == warning);
|
AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => (getWarning() != null) == warning);
|
||||||
|
|
||||||
if (warning)
|
if (warning)
|
||||||
{
|
{
|
||||||
@ -335,12 +335,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
AddUntilStep("wait for epilepsy warning", () => loader.ChildrenOfType<EpilepsyWarning>().Single().Alpha > 0);
|
AddUntilStep("wait for epilepsy warning", () => getWarning().Alpha > 0);
|
||||||
|
AddUntilStep("warning is shown", () => getWarning().State.Value == Visibility.Visible);
|
||||||
|
|
||||||
AddStep("exit early", () => loader.Exit());
|
AddStep("exit early", () => loader.Exit());
|
||||||
|
|
||||||
|
AddUntilStep("warning is hidden", () => getWarning().State.Value == Visibility.Hidden);
|
||||||
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault();
|
||||||
|
|
||||||
private class TestPlayerLoader : PlayerLoader
|
private class TestPlayerLoader : PlayerLoader
|
||||||
{
|
{
|
||||||
public new VisualSettings VisualSettings => base.VisualSettings;
|
public new VisualSettings VisualSettings => base.VisualSettings;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -10,10 +11,13 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Online.Solo;
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
@ -32,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override bool HasCustomSteps => true;
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new NonImportingPlayer(false);
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset();
|
||||||
|
|
||||||
@ -86,6 +90,46 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
|
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSubmissionForDifferentRuleset()
|
||||||
|
{
|
||||||
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
|
createPlayerTest(createRuleset: () => new TaikoRuleset());
|
||||||
|
|
||||||
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
|
addFakeHit();
|
||||||
|
|
||||||
|
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||||
|
|
||||||
|
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
|
||||||
|
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
|
||||||
|
AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new TaikoRuleset().RulesetInfo.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSubmissionForConvertedBeatmap()
|
||||||
|
{
|
||||||
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
|
createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
|
addFakeHit();
|
||||||
|
|
||||||
|
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||||
|
|
||||||
|
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
|
||||||
|
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
|
||||||
|
AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new ManiaRuleset().RulesetInfo.ID);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNoSubmissionOnExitWithNoToken()
|
public void TestNoSubmissionOnExitWithNoToken()
|
||||||
{
|
{
|
||||||
@ -183,12 +227,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(null)]
|
||||||
public void TestNoSubmissionOnCustomRuleset()
|
[TestCase(10)]
|
||||||
|
public void TestNoSubmissionOnCustomRuleset(int? rulesetId)
|
||||||
{
|
{
|
||||||
prepareTokenResponse(true);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = 10 } });
|
createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = rulesetId } });
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -242,5 +287,33 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class NonImportingPlayer : TestPlayer
|
||||||
|
{
|
||||||
|
public NonImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
|
||||||
|
: base(allowPause, showResults, pauseOnFocusLost)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task ImportScore(Score score)
|
||||||
|
{
|
||||||
|
// It was discovered that Score members could sometimes be half-populated.
|
||||||
|
// In particular, the RulesetID property could be set to 0 even on non-osu! maps.
|
||||||
|
// We want to test that the state of that property is consistent in this test.
|
||||||
|
// EF makes this impossible.
|
||||||
|
//
|
||||||
|
// First off, because of the EF navigational property-explicit foreign key field duality,
|
||||||
|
// it can happen that - for example - the Ruleset navigational property is correctly initialised to mania,
|
||||||
|
// but the RulesetID foreign key property is not initialised and remains 0.
|
||||||
|
// EF silently bypasses this by prioritising the Ruleset navigational property over the RulesetID foreign key one.
|
||||||
|
//
|
||||||
|
// Additionally, adding an entity to an EF DbSet CAUSES SIDE EFFECTS with regard to the foreign key property.
|
||||||
|
// In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context,
|
||||||
|
// RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3.
|
||||||
|
//
|
||||||
|
// For the above reasons, importing is disabled in this test.
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,9 +93,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
||||||
{
|
{
|
||||||
new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } },
|
new MultiplierControlPoint(time_range) { EffectPoint = { ScrollSpeed = 1.25 } },
|
||||||
new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } },
|
new MultiplierControlPoint(1.5 * time_range) { EffectPoint = { ScrollSpeed = 1 } },
|
||||||
new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } }
|
new MultiplierControlPoint(2 * time_range) { EffectPoint = { ScrollSpeed = 1.5 } }
|
||||||
};
|
};
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
51
osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
Normal file
51
osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Tests.Visual.Spectator;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneSpectatorHost : PlayerTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Cached(typeof(SpectatorClient))]
|
||||||
|
private TestSpectatorClient spectatorClient { get; } = new TestSpectatorClient();
|
||||||
|
|
||||||
|
private DummyAPIAccess dummyAPIAccess => (DummyAPIAccess)API;
|
||||||
|
private const int dummy_user_id = 42;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("set dummy user", () => dummyAPIAccess.LocalUser.Value = new User
|
||||||
|
{
|
||||||
|
Id = dummy_user_id,
|
||||||
|
Username = "DummyUser"
|
||||||
|
});
|
||||||
|
AddStep("add test spectator client", () => Add(spectatorClient));
|
||||||
|
AddStep("add watching user", () => spectatorClient.WatchUser(dummy_user_id));
|
||||||
|
base.SetUpSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClientSendsCorrectRuleset()
|
||||||
|
{
|
||||||
|
AddUntilStep("spectator client sending frames", () => spectatorClient.PlayingUserStates.ContainsKey(dummy_user_id));
|
||||||
|
AddAssert("spectator client sent correct ruleset", () => spectatorClient.PlayingUserStates[dummy_user_id].RulesetID == Ruleset.Value.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void TearDownSteps()
|
||||||
|
{
|
||||||
|
base.TearDownSteps();
|
||||||
|
AddStep("stop watching user", () => spectatorClient.StopWatchingUser(dummy_user_id));
|
||||||
|
AddStep("remove test spectator client", () => Remove(spectatorClient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -564,11 +564,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddRepeatStep("click spectate button", () =>
|
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("click ready button", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
InputManager.MoveMouseTo(readyButton);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
}, 2);
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
|
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
||||||
|
|
||||||
@ -582,6 +589,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
|
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
|
||||||
|
|
||||||
private void createRoom(Func<Room> room)
|
private void createRoom(Func<Room> room)
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
@ -275,6 +275,68 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var state = i;
|
var state = i;
|
||||||
AddStep($"set state: {state}", () => Client.ChangeUserState(0, state));
|
AddStep($"set state: {state}", () => Client.ChangeUserState(0, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddStep("set state: downloading", () => Client.ChangeUserBeatmapAvailability(0, BeatmapAvailability.Downloading(0)));
|
||||||
|
|
||||||
|
AddStep("set state: locally available", () => Client.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModOverlap()
|
||||||
|
{
|
||||||
|
AddStep("add dummy mods", () =>
|
||||||
|
{
|
||||||
|
Client.ChangeUserMods(new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModNoFail(),
|
||||||
|
new OsuModDoubleTime()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add user with mods", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
Username = "Baka",
|
||||||
|
RulesetsStatistics = new Dictionary<string, UserStatistics>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Ruleset.Value.ShortName,
|
||||||
|
new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
});
|
||||||
|
Client.ChangeUserMods(0, new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModHardRock(),
|
||||||
|
new OsuModDoubleTime()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 0 ready", () => Client.ChangeState(MultiplayerUserState.Ready));
|
||||||
|
|
||||||
|
AddStep("set 1 spectate", () => Client.ChangeUserState(0, MultiplayerUserState.Spectating));
|
||||||
|
|
||||||
|
// Have to set back to idle due to status priority.
|
||||||
|
AddStep("set 0 no map, 1 ready", () =>
|
||||||
|
{
|
||||||
|
Client.ChangeState(MultiplayerUserState.Idle);
|
||||||
|
Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded());
|
||||||
|
Client.ChangeUserState(0, MultiplayerUserState.Ready);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set 0 downloading", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
|
||||||
|
|
||||||
|
AddStep("set 0 spectate", () => Client.ChangeUserState(0, MultiplayerUserState.Spectating));
|
||||||
|
|
||||||
|
AddStep("make both default", () =>
|
||||||
|
{
|
||||||
|
Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable());
|
||||||
|
Client.ChangeUserState(0, MultiplayerUserState.Idle);
|
||||||
|
Client.ChangeState(MultiplayerUserState.Idle);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNewParticipantsList()
|
private void createNewParticipantsList()
|
||||||
|
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.Overlays.Notifications;
|
||||||
|
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<ProgressCompletionNotification>().Count() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||||
AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
||||||
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
||||||
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<FillFlowContainer>().OfType<IHasText>().First().Text == collectionName)));
|
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName)));
|
||||||
|
|
||||||
private IconButton getAddOrRemoveButton(int index)
|
private IconButton getAddOrRemoveButton(int index)
|
||||||
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
|
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
|
||||||
|
@ -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);
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
// 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 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.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -19,28 +23,62 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
AddStep("create component", () =>
|
AddStep("create component", () =>
|
||||||
{
|
{
|
||||||
LabelledSliderBar<double> component;
|
FillFlowContainer flow;
|
||||||
|
|
||||||
Child = new Container
|
Child = flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Width = 500,
|
Width = 500,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Child = component = new LabelledSliderBar<double>
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new LabelledSliderBar<double>
|
||||||
{
|
{
|
||||||
Current = new BindableDouble(5)
|
Current = new BindableDouble(5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Precision = 1,
|
Precision = 1,
|
||||||
}
|
},
|
||||||
}
|
Label = "a sample component",
|
||||||
|
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
component.Label = "a sample component";
|
foreach (var colour in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||||
component.Description = hasDescription ? "this text describes the component" : string.Empty;
|
{
|
||||||
|
flow.Add(new OverlayColourContainer(colour)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Current = new BindableDouble(5)
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
Label = "a sample component",
|
||||||
|
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverlayColourContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider;
|
||||||
|
|
||||||
|
public OverlayColourContainer(OverlayColourScheme scheme)
|
||||||
|
{
|
||||||
|
colourProvider = new OverlayColourProvider(scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 : () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneSettingsCheckbox : OsuTestScene
|
||||||
|
{
|
||||||
|
[TestCase]
|
||||||
|
public void TestCheckbox()
|
||||||
|
{
|
||||||
|
AddStep("create component", () =>
|
||||||
|
{
|
||||||
|
FillFlowContainer flow;
|
||||||
|
|
||||||
|
Child = flow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 500,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var colour1 in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||||
|
{
|
||||||
|
flow.Add(new OverlayColourContainer(colour1)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverlayColourContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider;
|
||||||
|
|
||||||
|
public OverlayColourContainer(OverlayColourScheme scheme)
|
||||||
|
{
|
||||||
|
colourProvider = new OverlayColourProvider(scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase tournament = null)
|
public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase tournament = null)
|
||||||
{
|
{
|
||||||
tournament ??= new TournamentGameBase();
|
tournament ??= new TournamentGameBase();
|
||||||
Task.Run(() => host.Run(tournament))
|
Task.Factory.StartNew(() => host.Run(tournament), TaskCreationOptions.LongRunning)
|
||||||
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
|
||||||
WaitForOrAssert(() => tournament.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
WaitForOrAssert(() => tournament.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||||
return tournament;
|
return tournament;
|
||||||
|
@ -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);
|
{
|
||||||
|
case BQFType.HighPass:
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case BQFType.LowPass:
|
||||||
|
return MAX_LOWPASS_CUTOFF;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 500; // A default that should ensure audio remains audible for other filters.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void detachFilter()
|
private void updateFilter(int newValue)
|
||||||
{
|
{
|
||||||
Debug.Assert(mixer.Effects.Contains(filter));
|
switch (type)
|
||||||
mixer.Effects.Remove(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFilter(ValueChangedEvent<int> cutoff)
|
|
||||||
{
|
{
|
||||||
|
case BQFType.LowPass:
|
||||||
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
|
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
|
||||||
if (type == BQFType.LowPass)
|
if (newValue >= MAX_LOWPASS_CUTOFF)
|
||||||
{
|
{
|
||||||
if (cutoff.NewValue >= MAX_LOWPASS_CUTOFF)
|
ensureDetached();
|
||||||
{
|
|
||||||
detachFilter();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cutoff.OldValue >= MAX_LOWPASS_CUTOFF && cutoff.NewValue < MAX_LOWPASS_CUTOFF)
|
break;
|
||||||
attachFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
|
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
|
||||||
if (type == BQFType.HighPass)
|
case BQFType.HighPass:
|
||||||
|
if (newValue <= 1)
|
||||||
{
|
{
|
||||||
if (cutoff.NewValue <= 1)
|
ensureDetached();
|
||||||
{
|
|
||||||
detachFilter();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cutoff.OldValue <= 1 && cutoff.NewValue > 1)
|
break;
|
||||||
attachFilter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureAttached();
|
||||||
|
|
||||||
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>
|
||||||
|
@ -178,7 +178,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
#region Implementation of IHasOnlineID
|
#region Implementation of IHasOnlineID
|
||||||
|
|
||||||
public int? OnlineID => OnlineBeatmapID;
|
public int OnlineID => OnlineBeatmapID ?? -1;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
@ -176,11 +176,6 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when the user requests to view the resulting import.
|
|
||||||
/// </summary>
|
|
||||||
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PresentImport { set => beatmapModelManager.PostImport = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a beatmap difficulty.
|
/// Delete a beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -338,5 +333,14 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IPostImports<out BeatmapSetInfo>
|
||||||
|
|
||||||
|
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PostImport
|
||||||
|
{
|
||||||
|
set => beatmapModelManager.PostImport = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,13 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
var setInfo = beatmapInfo.BeatmapSet;
|
var setInfo = beatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
|
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`.
|
||||||
|
// This should hopefully be temporary, assuming said clone is eventually removed.
|
||||||
|
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
|
||||||
|
|
||||||
|
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
|
||||||
|
beatmapContent.BeatmapInfo = beatmapInfo;
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using (var stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
@ -202,7 +209,6 @@ namespace osu.Game.Beatmaps
|
|||||||
using (ContextFactory.GetForWrite())
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
|
beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
|
||||||
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
|
|
||||||
|
|
||||||
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
|
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
#region Implementation of IHasOnlineID
|
#region Implementation of IHasOnlineID
|
||||||
|
|
||||||
public int? OnlineID => OnlineBeatmapSetID;
|
public int OnlineID => OnlineBeatmapSetID ?? -1;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -15,11 +15,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// The time at which the control point takes effect.
|
/// The time at which the control point takes effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double Time => controlPointGroup?.Time ?? 0;
|
public double Time { get; set; }
|
||||||
|
|
||||||
private ControlPointGroup controlPointGroup;
|
public void AttachGroup(ControlPointGroup pointGroup) => Time = pointGroup.Time;
|
||||||
|
|
||||||
public void AttachGroup(ControlPointGroup pointGroup) => controlPointGroup = pointGroup;
|
|
||||||
|
|
||||||
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
||||||
|
|
||||||
@ -46,6 +44,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
|
|
||||||
public virtual void CopyFrom(ControlPoint other)
|
public virtual void CopyFrom(ControlPoint other)
|
||||||
{
|
{
|
||||||
|
Time = other.Time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
|
|
||||||
private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
|
private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All difficulty points.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty]
|
|
||||||
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
|
|
||||||
|
|
||||||
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All effect points.
|
/// All effect points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -55,14 +47,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds the difficulty control point that is active at <paramref name="time"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="time">The time to find the difficulty control point at.</param>
|
|
||||||
/// <returns>The difficulty control point.</returns>
|
|
||||||
[NotNull]
|
|
||||||
public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the effect control point that is active at <paramref name="time"/>.
|
/// Finds the effect control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -100,7 +84,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
{
|
{
|
||||||
groups.Clear();
|
groups.Clear();
|
||||||
timingPoints.Clear();
|
timingPoints.Clear();
|
||||||
difficultyPoints.Clear();
|
|
||||||
effectPoints.Clear();
|
effectPoints.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,10 +260,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
case EffectControlPoint _:
|
case EffectControlPoint _:
|
||||||
existing = EffectPointAt(time);
|
existing = EffectPointAt(time);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DifficultyControlPoint _:
|
|
||||||
existing = DifficultyPointAt(time);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPoint?.IsRedundant(existing) == true;
|
return newPoint?.IsRedundant(existing) == true;
|
||||||
@ -298,9 +277,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
effectPoints.Add(typed);
|
effectPoints.Add(typed);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DifficultyControlPoint typed:
|
default:
|
||||||
difficultyPoints.Add(typed);
|
throw new ArgumentException($"A control point of unexpected type {controlPoint.GetType()} was added to this {nameof(ControlPointInfo)}");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,10 +293,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
case EffectControlPoint typed:
|
case EffectControlPoint typed:
|
||||||
effectPoints.Remove(typed);
|
effectPoints.Remove(typed);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DifficultyControlPoint typed:
|
|
||||||
difficultyPoints.Remove(typed);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,17 +7,20 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps.ControlPoints
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
{
|
{
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
|
||||||
|
/// </remarks>
|
||||||
public class DifficultyControlPoint : ControlPoint
|
public class DifficultyControlPoint : ControlPoint
|
||||||
{
|
{
|
||||||
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
|
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
|
||||||
{
|
{
|
||||||
SpeedMultiplierBindable = { Disabled = true },
|
SliderVelocityBindable = { Disabled = true },
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The speed multiplier at this control point.
|
/// The slider velocity at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1)
|
public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
Default = 1,
|
Default = 1,
|
||||||
@ -28,21 +31,21 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The speed multiplier at this control point.
|
/// The slider velocity at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double SpeedMultiplier
|
public double SliderVelocity
|
||||||
{
|
{
|
||||||
get => SpeedMultiplierBindable.Value;
|
get => SliderVelocityBindable.Value;
|
||||||
set => SpeedMultiplierBindable.Value = value;
|
set => SliderVelocityBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsRedundant(ControlPoint existing)
|
public override bool IsRedundant(ControlPoint existing)
|
||||||
=> existing is DifficultyControlPoint existingDifficulty
|
=> existing is DifficultyControlPoint existingDifficulty
|
||||||
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
&& SliderVelocity == existingDifficulty.SliderVelocity;
|
||||||
|
|
||||||
public override void CopyFrom(ControlPoint other)
|
public override void CopyFrom(ControlPoint other)
|
||||||
{
|
{
|
||||||
SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier;
|
SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity;
|
||||||
|
|
||||||
base.CopyFrom(other);
|
base.CopyFrom(other);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
||||||
{
|
{
|
||||||
KiaiModeBindable = { Disabled = true },
|
KiaiModeBindable = { Disabled = true },
|
||||||
OmitFirstBarLineBindable = { Disabled = true }
|
OmitFirstBarLineBindable = { Disabled = true },
|
||||||
|
ScrollSpeedBindable = { Disabled = true }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -20,6 +21,26 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
|
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative scroll speed at this control point.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 0.01,
|
||||||
|
MaxValue = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative scroll speed.
|
||||||
|
/// </summary>
|
||||||
|
public double ScrollSpeed
|
||||||
|
{
|
||||||
|
get => ScrollSpeedBindable.Value;
|
||||||
|
set => ScrollSpeedBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -49,12 +70,14 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
=> !OmitFirstBarLine
|
=> !OmitFirstBarLine
|
||||||
&& existing is EffectControlPoint existingEffect
|
&& existing is EffectControlPoint existingEffect
|
||||||
&& KiaiMode == existingEffect.KiaiMode
|
&& KiaiMode == existingEffect.KiaiMode
|
||||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine
|
||||||
|
&& ScrollSpeed == existingEffect.ScrollSpeed;
|
||||||
|
|
||||||
public override void CopyFrom(ControlPoint other)
|
public override void CopyFrom(ControlPoint other)
|
||||||
{
|
{
|
||||||
KiaiMode = ((EffectControlPoint)other).KiaiMode;
|
KiaiMode = ((EffectControlPoint)other).KiaiMode;
|
||||||
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
|
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
|
||||||
|
ScrollSpeed = ((EffectControlPoint)other).ScrollSpeed;
|
||||||
|
|
||||||
base.CopyFrom(other);
|
base.CopyFrom(other);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps.ControlPoints
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
{
|
{
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
|
||||||
|
/// </remarks>
|
||||||
public class SampleControlPoint : ControlPoint
|
public class SampleControlPoint : ControlPoint
|
||||||
{
|
{
|
||||||
public const string DEFAULT_BANK = "normal";
|
public const string DEFAULT_BANK = "normal";
|
||||||
|
@ -384,14 +384,21 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
addControlPoint(time, new LegacyDifficultyControlPoint(beatLength)
|
addControlPoint(time, new LegacyDifficultyControlPoint(beatLength)
|
||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
{
|
{
|
||||||
SpeedMultiplier = speedMultiplier,
|
SliderVelocity = speedMultiplier,
|
||||||
}, timingChange);
|
}, timingChange);
|
||||||
|
|
||||||
addControlPoint(time, new EffectControlPoint
|
var effectPoint = new EffectControlPoint
|
||||||
{
|
{
|
||||||
KiaiMode = kiaiMode,
|
KiaiMode = kiaiMode,
|
||||||
OmitFirstBarLine = omitFirstBarSignature,
|
OmitFirstBarLine = omitFirstBarSignature,
|
||||||
}, timingChange);
|
};
|
||||||
|
|
||||||
|
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
|
||||||
|
// scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments.
|
||||||
|
if (!isOsuRuleset)
|
||||||
|
effectPoint.ScrollSpeed = speedMultiplier;
|
||||||
|
|
||||||
|
addControlPoint(time, effectPoint, timingChange);
|
||||||
|
|
||||||
addControlPoint(time, new LegacySampleControlPoint
|
addControlPoint(time, new LegacySampleControlPoint
|
||||||
{
|
{
|
||||||
|
@ -170,33 +170,30 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
writer.WriteLine("[TimingPoints]");
|
|
||||||
|
|
||||||
if (!(beatmap.ControlPointInfo is LegacyControlPointInfo))
|
|
||||||
{
|
|
||||||
var legacyControlPoints = new LegacyControlPointInfo();
|
var legacyControlPoints = new LegacyControlPointInfo();
|
||||||
|
|
||||||
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
|
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
|
||||||
legacyControlPoints.Add(point.Time, point.DeepClone());
|
legacyControlPoints.Add(point.Time, point.DeepClone());
|
||||||
|
|
||||||
beatmap.ControlPointInfo = legacyControlPoints;
|
writer.WriteLine("[TimingPoints]");
|
||||||
|
|
||||||
SampleControlPoint lastRelevantSamplePoint = null;
|
SampleControlPoint lastRelevantSamplePoint = null;
|
||||||
|
DifficultyControlPoint lastRelevantDifficultyPoint = null;
|
||||||
|
|
||||||
// iterate over hitobjects and pull out all required sample changes
|
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
|
||||||
foreach (var h in beatmap.HitObjects)
|
|
||||||
|
// iterate over hitobjects and pull out all required sample and difficulty changes
|
||||||
|
extractDifficultyControlPoints(beatmap.HitObjects);
|
||||||
|
extractSampleControlPoints(beatmap.HitObjects);
|
||||||
|
|
||||||
|
// handle scroll speed, which is stored as "slider velocity" in legacy formats.
|
||||||
|
// this is relevant for scrolling ruleset beatmaps.
|
||||||
|
if (!isOsuRuleset)
|
||||||
{
|
{
|
||||||
var hSamplePoint = h.SampleControlPoint;
|
foreach (var point in legacyControlPoints.EffectPoints)
|
||||||
|
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
||||||
if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint))
|
|
||||||
{
|
|
||||||
legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint);
|
|
||||||
lastRelevantSamplePoint = hSamplePoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var group in beatmap.ControlPointInfo.Groups)
|
foreach (var group in legacyControlPoints.Groups)
|
||||||
{
|
{
|
||||||
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||||
|
|
||||||
@ -209,16 +206,16 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output any remaining effects as secondary non-timing control point.
|
// Output any remaining effects as secondary non-timing control point.
|
||||||
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time);
|
||||||
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
||||||
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},"));
|
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},"));
|
||||||
outputControlPointAt(group.Time, false);
|
outputControlPointAt(group.Time, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void outputControlPointAt(double time, bool isTimingPoint)
|
void outputControlPointAt(double time, bool isTimingPoint)
|
||||||
{
|
{
|
||||||
var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time);
|
var samplePoint = legacyControlPoints.SamplePointAt(time);
|
||||||
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
var effectPoint = legacyControlPoints.EffectPointAt(time);
|
||||||
|
|
||||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
||||||
@ -230,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (effectPoint.OmitFirstBarLine)
|
if (effectPoint.OmitFirstBarLine)
|
||||||
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
||||||
|
|
||||||
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},"));
|
writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},"));
|
||||||
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
||||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
||||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
||||||
@ -238,6 +235,55 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
||||||
writer.WriteLine();
|
writer.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerable<DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
if (!isOsuRuleset)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
foreach (var hitObject in hitObjects)
|
||||||
|
{
|
||||||
|
yield return hitObject.DifficultyControlPoint;
|
||||||
|
|
||||||
|
foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects))
|
||||||
|
yield return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void extractDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
foreach (var hDifficultyPoint in collectDifficultyControlPoints(hitObjects).OrderBy(dp => dp.Time))
|
||||||
|
{
|
||||||
|
if (!hDifficultyPoint.IsRedundant(lastRelevantDifficultyPoint))
|
||||||
|
{
|
||||||
|
legacyControlPoints.Add(hDifficultyPoint.Time, hDifficultyPoint);
|
||||||
|
lastRelevantDifficultyPoint = hDifficultyPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<SampleControlPoint> collectSampleControlPoints(IEnumerable<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in hitObjects)
|
||||||
|
{
|
||||||
|
yield return hitObject.SampleControlPoint;
|
||||||
|
|
||||||
|
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
|
||||||
|
yield return nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void extractSampleControlPoints(IEnumerable<HitObject> hitObject)
|
||||||
|
{
|
||||||
|
foreach (var hSamplePoint in collectSampleControlPoints(hitObject).OrderBy(sp => sp.Time))
|
||||||
|
{
|
||||||
|
if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint))
|
||||||
|
{
|
||||||
|
legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint);
|
||||||
|
lastRelevantSamplePoint = hSamplePoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleColours(TextWriter writer)
|
private void handleColours(TextWriter writer)
|
||||||
|
@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
public LegacyDifficultyControlPoint()
|
public LegacyDifficultyControlPoint()
|
||||||
{
|
{
|
||||||
SpeedMultiplierBindable.Precision = double.Epsilon;
|
SliderVelocityBindable.Precision = double.Epsilon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CopyFrom(ControlPoint other)
|
public override void CopyFrom(ControlPoint other)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Lists;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Legacy
|
namespace osu.Game.Beatmaps.Legacy
|
||||||
@ -14,9 +15,9 @@ namespace osu.Game.Beatmaps.Legacy
|
|||||||
/// All sound points.
|
/// All sound points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public IBindableList<SampleControlPoint> SamplePoints => samplePoints;
|
public IReadOnlyList<SampleControlPoint> SamplePoints => samplePoints;
|
||||||
|
|
||||||
private readonly BindableList<SampleControlPoint> samplePoints = new BindableList<SampleControlPoint>();
|
private readonly SortedList<SampleControlPoint> samplePoints = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the sound control point that is active at <paramref name="time"/>.
|
/// Finds the sound control point that is active at <paramref name="time"/>.
|
||||||
@ -26,35 +27,76 @@ namespace osu.Game.Beatmaps.Legacy
|
|||||||
[NotNull]
|
[NotNull]
|
||||||
public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT);
|
public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All difficulty points.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
|
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
|
||||||
|
|
||||||
|
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the difficulty control point that is active at <paramref name="time"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The time to find the difficulty control point at.</param>
|
||||||
|
/// <returns>The difficulty control point.</returns>
|
||||||
|
[NotNull]
|
||||||
|
public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
|
||||||
|
|
||||||
public override void Clear()
|
public override void Clear()
|
||||||
{
|
{
|
||||||
base.Clear();
|
base.Clear();
|
||||||
samplePoints.Clear();
|
samplePoints.Clear();
|
||||||
|
difficultyPoints.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint)
|
protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint)
|
||||||
{
|
{
|
||||||
if (newPoint is SampleControlPoint)
|
switch (newPoint)
|
||||||
{
|
{
|
||||||
|
case SampleControlPoint _:
|
||||||
|
// intentionally don't use SamplePointAt (we always need to consider the first sample point).
|
||||||
var existing = BinarySearch(SamplePoints, time);
|
var existing = BinarySearch(SamplePoints, time);
|
||||||
return newPoint.IsRedundant(existing);
|
return newPoint.IsRedundant(existing);
|
||||||
}
|
|
||||||
|
|
||||||
|
case DifficultyControlPoint _:
|
||||||
|
return newPoint.IsRedundant(DifficultyPointAt(time));
|
||||||
|
|
||||||
|
default:
|
||||||
return base.CheckAlreadyExisting(time, newPoint);
|
return base.CheckAlreadyExisting(time, newPoint);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void GroupItemAdded(ControlPoint controlPoint)
|
protected override void GroupItemAdded(ControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
if (controlPoint is SampleControlPoint typed)
|
switch (controlPoint)
|
||||||
|
{
|
||||||
|
case SampleControlPoint typed:
|
||||||
samplePoints.Add(typed);
|
samplePoints.Add(typed);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case DifficultyControlPoint typed:
|
||||||
|
difficultyPoints.Add(typed);
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
base.GroupItemAdded(controlPoint);
|
base.GroupItemAdded(controlPoint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void GroupItemRemoved(ControlPoint controlPoint)
|
protected override void GroupItemRemoved(ControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
if (controlPoint is SampleControlPoint typed)
|
switch (controlPoint)
|
||||||
|
{
|
||||||
|
case SampleControlPoint typed:
|
||||||
samplePoints.Remove(typed);
|
samplePoints.Remove(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DifficultyControlPoint typed:
|
||||||
|
difficultyPoints.Remove(typed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
base.GroupItemRemoved(controlPoint);
|
base.GroupItemRemoved(controlPoint);
|
||||||
}
|
}
|
||||||
|
@ -188,6 +188,8 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
|
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CancelAsyncLoad()
|
public void CancelAsyncLoad()
|
||||||
|
{
|
||||||
|
lock (beatmapFetchLock)
|
||||||
{
|
{
|
||||||
loadCancellation?.Cancel();
|
loadCancellation?.Cancel();
|
||||||
loadCancellation = new CancellationTokenSource();
|
loadCancellation = new CancellationTokenSource();
|
||||||
@ -195,6 +197,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapLoadTask?.IsCompleted != true)
|
if (beatmapLoadTask?.IsCompleted != true)
|
||||||
beatmapLoadTask = null;
|
beatmapLoadTask = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
|
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
@ -205,7 +208,13 @@ namespace osu.Game.Beatmaps
|
|||||||
return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10));
|
return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<IBeatmap> loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
|
private readonly object beatmapFetchLock = new object();
|
||||||
|
|
||||||
|
private Task<IBeatmap> loadBeatmapAsync()
|
||||||
|
{
|
||||||
|
lock (beatmapFetchLock)
|
||||||
|
{
|
||||||
|
return beatmapLoadTask ??= Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
// Todo: Handle cancellation during beatmap parsing
|
// Todo: Handle cancellation during beatmap parsing
|
||||||
var b = GetBeatmap() ?? new Beatmap();
|
var b = GetBeatmap() ?? new Beatmap();
|
||||||
@ -218,6 +227,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
return b;
|
return b;
|
||||||
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => BeatmapInfo.ToString();
|
public override string ToString() => BeatmapInfo.ToString();
|
||||||
|
|
||||||
|
@ -66,10 +66,14 @@ namespace osu.Game.Beatmaps
|
|||||||
lock (workingCache)
|
lock (workingCache)
|
||||||
{
|
{
|
||||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
||||||
|
|
||||||
if (working != null)
|
if (working != null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Invalidating working beatmap cache for {info}");
|
||||||
workingCache.Remove(working);
|
workingCache.Remove(working);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -86,6 +90,7 @@ namespace osu.Game.Beatmaps
|
|||||||
lock (workingCache)
|
lock (workingCache)
|
||||||
{
|
{
|
||||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
||||||
|
|
||||||
if (working != null)
|
if (working != null)
|
||||||
return working;
|
return working;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace osu.Game.Configuration
|
|||||||
[Description("Never repeat")]
|
[Description("Never repeat")]
|
||||||
RandomPermutation,
|
RandomPermutation,
|
||||||
|
|
||||||
[Description("Random")]
|
[Description("True Random")]
|
||||||
Random
|
Random
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>, IPostImports<TModel>
|
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
|
||||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||||
where TFileModel : class, INamedFileInfo, new()
|
where TFileModel : class, INamedFileInfo, new()
|
||||||
{
|
{
|
||||||
@ -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);
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ namespace osu.Game.Database
|
|||||||
public interface IHasOnlineID
|
public interface IHasOnlineID
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The server-side ID representing this instance, if one exists.
|
/// The server-side ID representing this instance, if one exists. -1 denotes a missing ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int? OnlineID { get; }
|
int OnlineID { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,10 +10,10 @@ 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, IPostImports<TModel>
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user