mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:33:30 +08:00
Merge branch 'master' into catch-combo-counter
This commit is contained in:
commit
0761aab42f
@ -5,6 +5,6 @@
|
||||
"version": "3.1.100"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "2.0.52"
|
||||
"Microsoft.Build.Traversal": "2.1.1"
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.812.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.819.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.911.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps;
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Timeout(10000)]
|
||||
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
|
||||
@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
Name = @"Fruit Count",
|
||||
Content = fruits.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Juice Stream Count",
|
||||
Content = juiceStreams.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Banana Shower Count",
|
||||
Content = bananaShowers.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -21,13 +21,11 @@ using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using System;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class CatchRuleset : Ruleset, ILegacyRuleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double ApproachRate;
|
||||
public int MaxCombo;
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,13 @@ using osu.Game.Tests.Beatmaps;
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Timeout(10000)]
|
||||
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
|
||||
[TestCase("basic")]
|
||||
[TestCase("zero-length-slider")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
@ -41,14 +40,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Note Count",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = notes.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hold Note Count",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = holdnotes.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ using osu.Game.Rulesets.Mania.Objects;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -167,8 +166,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
var positionData = original as IHasPosition;
|
||||
|
||||
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
|
||||
for (int i = 0; i <= generator.SpanCount; i++)
|
||||
{
|
||||
double time = original.StartTime + generator.SegmentDuration * i;
|
||||
|
||||
recordNote(time, positionData?.Position ?? Vector2.Zero);
|
||||
computeDensity(time);
|
||||
}
|
||||
|
@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
public readonly double EndTime;
|
||||
public readonly double SegmentDuration;
|
||||
|
||||
private readonly int spanCount;
|
||||
public readonly int SpanCount;
|
||||
|
||||
private PatternType convertType;
|
||||
|
||||
@ -42,20 +41,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var distanceData = hitObject as IHasDistance;
|
||||
var repeatsData = hitObject as IHasRepeats;
|
||||
|
||||
spanCount = repeatsData?.SpanCount() ?? 1;
|
||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
||||
|
||||
// The true distance, accounting for any repeats
|
||||
double distance = (distanceData?.Distance ?? 0) * spanCount;
|
||||
double distance = (distanceData?.Distance ?? 0) * SpanCount;
|
||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
|
||||
// The duration of the osu! hit object
|
||||
double osuDuration = distance / osuVelocity;
|
||||
|
||||
EndTime = hitObject.StartTime + osuDuration;
|
||||
SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
|
||||
SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount;
|
||||
}
|
||||
|
||||
public override IEnumerable<Pattern> Generate()
|
||||
@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (spanCount > 1)
|
||||
if (SpanCount > 1)
|
||||
{
|
||||
if (SegmentDuration <= 90)
|
||||
return generateRandomHoldNotes(HitObject.StartTime, 1);
|
||||
@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
if (SegmentDuration <= 120)
|
||||
{
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
return generateRandomNotes(HitObject.StartTime, spanCount + 1);
|
||||
return generateRandomNotes(HitObject.StartTime, SpanCount + 1);
|
||||
}
|
||||
|
||||
if (SegmentDuration <= 160)
|
||||
@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
if (duration >= 4000)
|
||||
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
|
||||
|
||||
if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
|
||||
if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart)
|
||||
return generateTiledHoldNotes(HitObject.StartTime);
|
||||
|
||||
return generateHoldAndNormalNotes(HitObject.StartTime);
|
||||
@ -251,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
bool increasing = Random.NextDouble() > 0.5;
|
||||
|
||||
for (int i = 0; i <= spanCount; i++)
|
||||
for (int i = 0; i <= SpanCount; i++)
|
||||
{
|
||||
addToPattern(pattern, column, startTime, startTime);
|
||||
startTime += SegmentDuration;
|
||||
@ -302,7 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
|
||||
for (int i = 0; i <= spanCount; i++)
|
||||
for (int i = 0; i <= SpanCount; i++)
|
||||
{
|
||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||
|
||||
@ -393,7 +392,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
int columnRepeat = Math.Min(spanCount, TotalColumns);
|
||||
int columnRepeat = Math.Min(SpanCount, TotalColumns);
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
@ -447,7 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
var rowPattern = new Pattern();
|
||||
|
||||
for (int i = 0; i <= spanCount; i++)
|
||||
for (int i = 0; i <= SpanCount; i++)
|
||||
{
|
||||
if (!(ignoreHead && startTime == HitObject.StartTime))
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
{
|
||||
base.InitialiseDefaults();
|
||||
|
||||
Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 1);
|
||||
Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
|
||||
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -43,6 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
Mods = mods,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
@ -35,7 +34,6 @@ using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Mania
|
||||
new SettingsSlider<double, TimeSlider>
|
||||
{
|
||||
LabelText = "Scroll speed",
|
||||
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime)
|
||||
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||
KeyboardStep = 5
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"RandomW": 3083084786,
|
||||
"RandomX": 273326509,
|
||||
"RandomY": 273553282,
|
||||
"RandomZ": 2659838971,
|
||||
"StartTime": 4836,
|
||||
"Objects": [{
|
||||
"StartTime": 4836,
|
||||
"EndTime": 4836,
|
||||
"Column": 0
|
||||
}]
|
||||
}]
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:1
|
||||
CircleSize:4
|
||||
OverallDifficulty:1
|
||||
ApproachRate:9
|
||||
SliderMultiplier:2.5
|
||||
SliderTickRate:0.5
|
||||
|
||||
[TimingPoints]
|
||||
34,431.654676258993,4,1,0,50,1,0
|
||||
4782,-66.6666666666667,4,1,0,20,0,0
|
||||
|
||||
[HitObjects]
|
||||
15,199,4836,22,0,L,1,46.8750017881394
|
@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Timeout(10000)]
|
||||
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
@ -22,7 +22,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -32,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
private TrackVirtualManual track;
|
||||
|
||||
protected override bool Autoplay => autoplay;
|
||||
private bool autoplay;
|
||||
|
||||
@ -44,11 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private const double fade_in_modifier = -1200;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
track = (TrackVirtualManual)working.Track;
|
||||
return working;
|
||||
}
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetConfigCache configCache)
|
||||
@ -72,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
AddStep("enable autoplay", () => autoplay = true);
|
||||
base.SetUpSteps();
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
AddStep("have autoplay", () => autoplay = true);
|
||||
base.SetUpSteps();
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
AddStep($"seek to {time}", () => MusicController.SeekTo(time));
|
||||
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ using osu.Game.Scoring;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -34,18 +33,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
private TrackVirtualManual track;
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
track = (TrackVirtualManual)working.Track;
|
||||
return working;
|
||||
}
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
private DrawableSpinner drawableSpinner;
|
||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||
@ -55,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First());
|
||||
}
|
||||
|
||||
@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
addSeekStep(0);
|
||||
|
||||
AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
|
||||
AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
|
||||
// autoplay replay frames use track time;
|
||||
// if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time.
|
||||
// therefore we need to apply the rate adjustment to the replay itself to change from track time to real time,
|
||||
@ -230,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
AddStep($"seek to {time}", () => MusicController.SeekTo(time));
|
||||
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
Name = @"Circle Count",
|
||||
Content = circles.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Slider Count",
|
||||
Content = sliders.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Spinner Count",
|
||||
Content = spinners.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -11,6 +11,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
public double SpeedStrain;
|
||||
public double ApproachRate;
|
||||
public double OverallDifficulty;
|
||||
public int MaxCombo;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -38,6 +39,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
new SpinnerCompositionTool()
|
||||
};
|
||||
|
||||
private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" };
|
||||
|
||||
protected override IEnumerable<BindableBool> Toggles => new[]
|
||||
{
|
||||
distanceSnapToggle
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -45,6 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
||||
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
}
|
||||
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||
@ -87,6 +96,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
distanceSnapGridContainer.Clear();
|
||||
distanceSnapGridCache.Invalidate();
|
||||
distanceSnapGrid = null;
|
||||
|
||||
if (!distanceSnapToggle.Value)
|
||||
return;
|
||||
|
||||
switch (BlueprintContainer.CurrentTool)
|
||||
{
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Duration
|
||||
{
|
||||
get => EndTime - StartTime;
|
||||
@ -112,8 +114,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public double TickDistanceMultiplier = 1;
|
||||
|
||||
public HitCircle HeadCircle;
|
||||
public SliderTailCircle TailCircle;
|
||||
[JsonIgnore]
|
||||
public HitCircle HeadCircle { get; protected set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public SliderTailCircle TailCircle { get; protected set; }
|
||||
|
||||
public Slider()
|
||||
{
|
||||
|
@ -30,14 +30,12 @@ using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Statistics;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class OsuRuleset : Ruleset, ILegacyRuleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
|
||||
|
@ -18,6 +18,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
|
||||
|
||||
protected new float CalculatedBorderPortion
|
||||
// Roughly matches osu!stable's slider border portions.
|
||||
=> base.CalculatedBorderPortion * 0.77f;
|
||||
|
||||
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
|
||||
|
||||
protected override Color4 ColourAt(float position)
|
||||
|
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
|
||||
protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius)
|
||||
{
|
||||
if (pointGrid.Content.Length == 0)
|
||||
if (pointGrid.Content.Count == 0)
|
||||
return;
|
||||
|
||||
double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point.
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Timeout(10000)]
|
||||
public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
||||
|
@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
||||
|
||||
[TestCase(2.9811338051242915d, "diffcalc-test")]
|
||||
[TestCase(2.9811338051242915d, "diffcalc-test-strong")]
|
||||
[TestCase(2.2867022617692685d, "diffcalc-test")]
|
||||
[TestCase(2.2867022617692685d, "diffcalc-test-strong")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
@ -22,20 +21,20 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hit Count",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = hits.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Drumroll Count",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = drumrolls.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Swell Count",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
Content = swells.ToString(),
|
||||
Icon = FontAwesome.Regular.Circle
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,140 @@
|
||||
// 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.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects special hit object patterns which are easier to hit using special techniques
|
||||
/// than normally assumed in the fully-alternating play style.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This component detects two basic types of patterns, leveraged by the following techniques:
|
||||
/// <list>
|
||||
/// <item>Rolling allows hitting patterns with quickly and regularly alternating notes with a single hand.</item>
|
||||
/// <item>TL tapping makes hitting longer sequences of consecutive same-colour notes with little to no colour changes in-between.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public class StaminaCheeseDetector
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a roll.
|
||||
/// </summary>
|
||||
private const int roll_min_repetitions = 12;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a TL tap.
|
||||
/// </summary>
|
||||
private const int tl_min_repetitions = 16;
|
||||
|
||||
/// <summary>
|
||||
/// The list of all <see cref="TaikoDifficultyHitObject"/>s in the map.
|
||||
/// </summary>
|
||||
private readonly List<TaikoDifficultyHitObject> hitObjects;
|
||||
|
||||
public StaminaCheeseDetector(List<TaikoDifficultyHitObject> hitObjects)
|
||||
{
|
||||
this.hitObjects = hitObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and marks all objects in <see cref="hitObjects"/> that special difficulty-reducing techiques apply to
|
||||
/// with the <see cref="TaikoDifficultyHitObject.StaminaCheese"/> flag.
|
||||
/// </summary>
|
||||
public void FindCheese()
|
||||
{
|
||||
findRolls(3);
|
||||
findRolls(4);
|
||||
|
||||
findTlTap(0, HitType.Rim);
|
||||
findTlTap(1, HitType.Rim);
|
||||
findTlTap(0, HitType.Centre);
|
||||
findTlTap(1, HitType.Centre);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and marks all sequences hittable using a roll.
|
||||
/// </summary>
|
||||
/// <param name="patternLength">The length of a single repeating pattern to consider (triplets/quadruplets).</param>
|
||||
private void findRolls(int patternLength)
|
||||
{
|
||||
var history = new LimitedCapacityQueue<TaikoDifficultyHitObject>(2 * patternLength);
|
||||
|
||||
// for convenience, we're tracking the index of the item *before* our suspected repeat's start,
|
||||
// as that index can be simply subtracted from the current index to get the number of elements in between
|
||||
// without off-by-one errors
|
||||
int indexBeforeLastRepeat = -1;
|
||||
|
||||
for (int i = 0; i < hitObjects.Count; i++)
|
||||
{
|
||||
history.Enqueue(hitObjects[i]);
|
||||
if (!history.Full)
|
||||
continue;
|
||||
|
||||
if (!containsPatternRepeat(history, patternLength))
|
||||
{
|
||||
// we're setting this up for the next iteration, hence the +1.
|
||||
// right here this index will point at the queue's front (oldest item),
|
||||
// but that item is about to be popped next loop with an enqueue.
|
||||
indexBeforeLastRepeat = i - history.Count + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int repeatedLength = i - indexBeforeLastRepeat;
|
||||
if (repeatedLength < roll_min_repetitions)
|
||||
continue;
|
||||
|
||||
markObjectsAsCheese(i, repeatedLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the objects stored in <paramref name="history"/> contain a repetition of a pattern of length <paramref name="patternLength"/>.
|
||||
/// </summary>
|
||||
private static bool containsPatternRepeat(LimitedCapacityQueue<TaikoDifficultyHitObject> history, int patternLength)
|
||||
{
|
||||
for (int j = 0; j < patternLength; j++)
|
||||
{
|
||||
if (history[j].HitType != history[j + patternLength].HitType)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and marks all sequences hittable using a TL tap.
|
||||
/// </summary>
|
||||
/// <param name="parity">Whether sequences starting with an odd- (1) or even-indexed (0) hit object should be checked.</param>
|
||||
/// <param name="type">The type of hit to check for TL taps.</param>
|
||||
private void findTlTap(int parity, HitType type)
|
||||
{
|
||||
int tlLength = -2;
|
||||
|
||||
for (int i = parity; i < hitObjects.Count; i += 2)
|
||||
{
|
||||
if (hitObjects[i].HitType == type)
|
||||
tlLength += 2;
|
||||
else
|
||||
tlLength = -2;
|
||||
|
||||
if (tlLength < tl_min_repetitions)
|
||||
continue;
|
||||
|
||||
markObjectsAsCheese(i, tlLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks <paramref name="count"/> elements counting backwards from <paramref name="end"/> as <see cref="TaikoDifficultyHitObject.StaminaCheese"/>.
|
||||
/// </summary>
|
||||
private void markObjectsAsCheese(int end, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
hitObjects[end - i].StaminaCheese = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,94 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single hit object in taiko difficulty calculation.
|
||||
/// </summary>
|
||||
public class TaikoDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
public readonly bool HasTypeChange;
|
||||
/// <summary>
|
||||
/// The rhythm required to hit this hit object.
|
||||
/// </summary>
|
||||
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
|
||||
|
||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
|
||||
/// <summary>
|
||||
/// The hit type of this hit object.
|
||||
/// </summary>
|
||||
public readonly HitType? HitType;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the object in the beatmap.
|
||||
/// </summary>
|
||||
public readonly int ObjectIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the object should carry a penalty due to being hittable using special techniques
|
||||
/// making it easier to do so.
|
||||
/// </summary>
|
||||
public bool StaminaCheese;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new difficulty hit object.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The gameplay <see cref="HitObject"/> associated with this difficulty object.</param>
|
||||
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="hitObject"/>.</param>
|
||||
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
|
||||
/// <param name="clockRate">The rate of the gameplay clock. Modified by speed-changing mods.</param>
|
||||
/// <param name="objectIndex">The index of the object in the beatmap.</param>
|
||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, int objectIndex)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
HasTypeChange = (lastObject as Hit)?.Type != (hitObject as Hit)?.Type;
|
||||
var currentHit = hitObject as Hit;
|
||||
|
||||
Rhythm = getClosestRhythm(lastObject, lastLastObject, clockRate);
|
||||
HitType = currentHit?.Type;
|
||||
|
||||
ObjectIndex = objectIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of most common rhythm changes in taiko maps.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The general guidelines for the values are:
|
||||
/// <list type="bullet">
|
||||
/// <item>rhythm changes with ratio closer to 1 (that are <i>not</i> 1) are harder to play,</item>
|
||||
/// <item>speeding up is <i>generally</i> harder than slowing down (with exceptions of rhythm changes requiring a hand switch).</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
private static readonly TaikoDifficultyHitObjectRhythm[] common_rhythms =
|
||||
{
|
||||
new TaikoDifficultyHitObjectRhythm(1, 1, 0.0),
|
||||
new TaikoDifficultyHitObjectRhythm(2, 1, 0.3),
|
||||
new TaikoDifficultyHitObjectRhythm(1, 2, 0.5),
|
||||
new TaikoDifficultyHitObjectRhythm(3, 1, 0.3),
|
||||
new TaikoDifficultyHitObjectRhythm(1, 3, 0.35),
|
||||
new TaikoDifficultyHitObjectRhythm(3, 2, 0.6), // purposefully higher (requires hand switch in full alternating gameplay style)
|
||||
new TaikoDifficultyHitObjectRhythm(2, 3, 0.4),
|
||||
new TaikoDifficultyHitObjectRhythm(5, 4, 0.5),
|
||||
new TaikoDifficultyHitObjectRhythm(4, 5, 0.7)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns the closest rhythm change from <see cref="common_rhythms"/> required to hit this object.
|
||||
/// </summary>
|
||||
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding this one.</param>
|
||||
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
|
||||
/// <param name="clockRate">The rate of the gameplay clock.</param>
|
||||
private TaikoDifficultyHitObjectRhythm getClosestRhythm(HitObject lastObject, HitObject lastLastObject, double clockRate)
|
||||
{
|
||||
double prevLength = (lastObject.StartTime - lastLastObject.StartTime) / clockRate;
|
||||
double ratio = DeltaTime / prevLength;
|
||||
|
||||
return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a rhythm change in a taiko map.
|
||||
/// </summary>
|
||||
public class TaikoDifficultyHitObjectRhythm
|
||||
{
|
||||
/// <summary>
|
||||
/// The difficulty multiplier associated with this rhythm change.
|
||||
/// </summary>
|
||||
public readonly double Difficulty;
|
||||
|
||||
/// <summary>
|
||||
/// The ratio of current <see cref="osu.Game.Rulesets.Difficulty.Preprocessing.DifficultyHitObject.DeltaTime"/>
|
||||
/// to previous <see cref="osu.Game.Rulesets.Difficulty.Preprocessing.DifficultyHitObject.DeltaTime"/> for the rhythm change.
|
||||
/// A <see cref="Ratio"/> above 1 indicates a slow-down; a <see cref="Ratio"/> below 1 indicates a speed-up.
|
||||
/// </summary>
|
||||
public readonly double Ratio;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an object representing a rhythm change.
|
||||
/// </summary>
|
||||
/// <param name="numerator">The numerator for <see cref="Ratio"/>.</param>
|
||||
/// <param name="denominator">The denominator for <see cref="Ratio"/></param>
|
||||
/// <param name="difficulty">The difficulty multiplier associated with this rhythm change.</param>
|
||||
public TaikoDifficultyHitObjectRhythm(int numerator, int denominator, double difficulty)
|
||||
{
|
||||
Ratio = numerator / (double)denominator;
|
||||
Difficulty = difficulty;
|
||||
}
|
||||
}
|
||||
}
|
135
osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
Normal file
135
osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
Normal file
@ -0,0 +1,135 @@
|
||||
// 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.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the colour coefficient of taiko difficulty.
|
||||
/// </summary>
|
||||
public class Colour : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of entries to keep in <see cref="monoHistory"/>.
|
||||
/// </summary>
|
||||
private const int mono_history_max_length = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Queue with the lengths of the last <see cref="mono_history_max_length"/> most recent mono (single-colour) patterns,
|
||||
/// with the most recent value at the end of the queue.
|
||||
/// </summary>
|
||||
private readonly LimitedCapacityQueue<int> monoHistory = new LimitedCapacityQueue<int>(mono_history_max_length);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HitType"/> of the last object hit before the one being considered.
|
||||
/// </summary>
|
||||
private HitType? previousHitType;
|
||||
|
||||
/// <summary>
|
||||
/// Length of the current mono pattern.
|
||||
/// </summary>
|
||||
private int currentMonoLength;
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
// changing from/to a drum roll or a swell does not constitute a colour change.
|
||||
// hits spaced more than a second apart are also exempt from colour strain.
|
||||
if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000))
|
||||
{
|
||||
monoHistory.Clear();
|
||||
|
||||
var currentHit = current.BaseObject as Hit;
|
||||
currentMonoLength = currentHit != null ? 1 : 0;
|
||||
previousHitType = currentHit?.Type;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
var taikoCurrent = (TaikoDifficultyHitObject)current;
|
||||
|
||||
double objectStrain = 0.0;
|
||||
|
||||
if (previousHitType != null && taikoCurrent.HitType != previousHitType)
|
||||
{
|
||||
// The colour has changed.
|
||||
objectStrain = 1.0;
|
||||
|
||||
if (monoHistory.Count < 2)
|
||||
{
|
||||
// There needs to be at least two streaks to determine a strain.
|
||||
objectStrain = 0.0;
|
||||
}
|
||||
else if ((monoHistory[^1] + currentMonoLength) % 2 == 0)
|
||||
{
|
||||
// The last streak in the history is guaranteed to be a different type to the current streak.
|
||||
// If the total number of notes in the two streaks is even, nullify this object's strain.
|
||||
objectStrain = 0.0;
|
||||
}
|
||||
|
||||
objectStrain *= repetitionPenalties();
|
||||
currentMonoLength = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMonoLength += 1;
|
||||
}
|
||||
|
||||
previousHitType = taikoCurrent.HitType;
|
||||
return objectStrain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The penalty to apply due to the length of repetition in colour streaks.
|
||||
/// </summary>
|
||||
private double repetitionPenalties()
|
||||
{
|
||||
const int most_recent_patterns_to_compare = 2;
|
||||
double penalty = 1.0;
|
||||
|
||||
monoHistory.Enqueue(currentMonoLength);
|
||||
|
||||
for (int start = monoHistory.Count - most_recent_patterns_to_compare - 1; start >= 0; start--)
|
||||
{
|
||||
if (!isSamePattern(start, most_recent_patterns_to_compare))
|
||||
continue;
|
||||
|
||||
int notesSince = 0;
|
||||
for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i];
|
||||
penalty *= repetitionPenalty(notesSince);
|
||||
break;
|
||||
}
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the last <paramref name="mostRecentPatternsToCompare"/> patterns have repeated in the history
|
||||
/// of single-colour note sequences, starting from <paramref name="start"/>.
|
||||
/// </summary>
|
||||
private bool isSamePattern(int start, int mostRecentPatternsToCompare)
|
||||
{
|
||||
for (int i = 0; i < mostRecentPatternsToCompare; i++)
|
||||
{
|
||||
if (monoHistory[start + i] != monoHistory[monoHistory.Count - mostRecentPatternsToCompare + i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the strain penalty for a colour pattern repetition.
|
||||
/// </summary>
|
||||
/// <param name="notesSince">The number of notes since the last repetition of the pattern.</param>
|
||||
private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince);
|
||||
}
|
||||
}
|
167
osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
Normal file
167
osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
Normal file
@ -0,0 +1,167 @@
|
||||
// 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.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the rhythm coefficient of taiko difficulty.
|
||||
/// </summary>
|
||||
public class Rhythm : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 10;
|
||||
protected override double StrainDecayBase => 0;
|
||||
|
||||
/// <summary>
|
||||
/// The note-based decay for rhythm strain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="StrainDecayBase"/> is not used here, as it's time- and not note-based.
|
||||
/// </remarks>
|
||||
private const double strain_decay = 0.96;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of entries in <see cref="rhythmHistory"/>.
|
||||
/// </summary>
|
||||
private const int rhythm_history_max_length = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the last <see cref="rhythm_history_max_length"/> changes in note sequence rhythms.
|
||||
/// </summary>
|
||||
private readonly LimitedCapacityQueue<TaikoDifficultyHitObject> rhythmHistory = new LimitedCapacityQueue<TaikoDifficultyHitObject>(rhythm_history_max_length);
|
||||
|
||||
/// <summary>
|
||||
/// Contains the rolling rhythm strain.
|
||||
/// Used to apply per-note decay.
|
||||
/// </summary>
|
||||
private double currentStrain;
|
||||
|
||||
/// <summary>
|
||||
/// Number of notes since the last rhythm change has taken place.
|
||||
/// </summary>
|
||||
private int notesSinceRhythmChange;
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
// drum rolls and swells are exempt.
|
||||
if (!(current.BaseObject is Hit))
|
||||
{
|
||||
resetRhythmAndStrain();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
currentStrain *= strain_decay;
|
||||
|
||||
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
||||
notesSinceRhythmChange += 1;
|
||||
|
||||
// rhythm difficulty zero (due to rhythm not changing) => no rhythm strain.
|
||||
if (hitObject.Rhythm.Difficulty == 0.0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double objectStrain = hitObject.Rhythm.Difficulty;
|
||||
|
||||
objectStrain *= repetitionPenalties(hitObject);
|
||||
objectStrain *= patternLengthPenalty(notesSinceRhythmChange);
|
||||
objectStrain *= speedPenalty(hitObject.DeltaTime);
|
||||
|
||||
// careful - needs to be done here since calls above read this value
|
||||
notesSinceRhythmChange = 0;
|
||||
|
||||
currentStrain += objectStrain;
|
||||
return currentStrain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a penalty to apply to the current hit object caused by repeating rhythm changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Repetitions of more recent patterns are associated with a higher penalty.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The current hit object being considered.</param>
|
||||
private double repetitionPenalties(TaikoDifficultyHitObject hitObject)
|
||||
{
|
||||
double penalty = 1;
|
||||
|
||||
rhythmHistory.Enqueue(hitObject);
|
||||
|
||||
for (int mostRecentPatternsToCompare = 2; mostRecentPatternsToCompare <= rhythm_history_max_length / 2; mostRecentPatternsToCompare++)
|
||||
{
|
||||
for (int start = rhythmHistory.Count - mostRecentPatternsToCompare - 1; start >= 0; start--)
|
||||
{
|
||||
if (!samePattern(start, mostRecentPatternsToCompare))
|
||||
continue;
|
||||
|
||||
int notesSince = hitObject.ObjectIndex - rhythmHistory[start].ObjectIndex;
|
||||
penalty *= repetitionPenalty(notesSince);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the rhythm change pattern starting at <paramref name="start"/> is a repeat of any of the
|
||||
/// <paramref name="mostRecentPatternsToCompare"/>.
|
||||
/// </summary>
|
||||
private bool samePattern(int start, int mostRecentPatternsToCompare)
|
||||
{
|
||||
for (int i = 0; i < mostRecentPatternsToCompare; i++)
|
||||
{
|
||||
if (rhythmHistory[start + i].Rhythm != rhythmHistory[rhythmHistory.Count - mostRecentPatternsToCompare + i].Rhythm)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a single rhythm repetition penalty.
|
||||
/// </summary>
|
||||
/// <param name="notesSince">Number of notes since the last repetition of a rhythm change.</param>
|
||||
private static double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a penalty based on the number of notes since the last rhythm change.
|
||||
/// Both rare and frequent rhythm changes are penalised.
|
||||
/// </summary>
|
||||
/// <param name="patternLength">Number of notes since the last rhythm change.</param>
|
||||
private static double patternLengthPenalty(int patternLength)
|
||||
{
|
||||
double shortPatternPenalty = Math.Min(0.15 * patternLength, 1.0);
|
||||
double longPatternPenalty = Math.Clamp(2.5 - 0.15 * patternLength, 0.0, 1.0);
|
||||
return Math.Min(shortPatternPenalty, longPatternPenalty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a penalty for objects that do not require alternating hands.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">Time (in milliseconds) since the last hit object.</param>
|
||||
private double speedPenalty(double deltaTime)
|
||||
{
|
||||
if (deltaTime < 80) return 1;
|
||||
if (deltaTime < 210) return Math.Max(0, 1.4 - 0.005 * deltaTime);
|
||||
|
||||
resetRhythmAndStrain();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the rolling strain value and <see cref="notesSinceRhythmChange"/> counter.
|
||||
/// </summary>
|
||||
private void resetRhythmAndStrain()
|
||||
{
|
||||
currentStrain = 0.0;
|
||||
notesSinceRhythmChange = 0;
|
||||
}
|
||||
}
|
||||
}
|
113
osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
Normal file
113
osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
Normal file
@ -0,0 +1,113 @@
|
||||
// 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 osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the stamina coefficient of taiko difficulty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
|
||||
/// </remarks>
|
||||
public class Stamina : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of entries to keep in <see cref="notePairDurationHistory"/>.
|
||||
/// </summary>
|
||||
private const int max_history_length = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the hand this <see cref="Stamina"/> instance is associated with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of 0 indicates the left hand (full alternating gameplay starting with left hand is assumed).
|
||||
/// This naturally translates onto index offsets of the objects in the map.
|
||||
/// </remarks>
|
||||
private readonly int hand;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the last <see cref="max_history_length"/> durations between notes hit with the hand indicated by <see cref="hand"/>.
|
||||
/// </summary>
|
||||
private readonly LimitedCapacityQueue<double> notePairDurationHistory = new LimitedCapacityQueue<double>(max_history_length);
|
||||
|
||||
/// <summary>
|
||||
/// Stores the <see cref="DifficultyHitObject.DeltaTime"/> of the last object that was hit by the <i>other</i> hand.
|
||||
/// </summary>
|
||||
private double offhandObjectDuration = double.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Stamina"/> skill.
|
||||
/// </summary>
|
||||
/// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param>
|
||||
public Stamina(bool rightHand)
|
||||
{
|
||||
hand = rightHand ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
if (!(current.BaseObject is Hit))
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
|
||||
|
||||
if (hitObject.ObjectIndex % 2 == hand)
|
||||
{
|
||||
double objectStrain = 1;
|
||||
|
||||
if (hitObject.ObjectIndex == 1)
|
||||
return 1;
|
||||
|
||||
notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration);
|
||||
|
||||
double shortestRecentNote = notePairDurationHistory.Min();
|
||||
objectStrain += speedBonus(shortestRecentNote);
|
||||
|
||||
if (hitObject.StaminaCheese)
|
||||
objectStrain *= cheesePenalty(hitObject.DeltaTime + offhandObjectDuration);
|
||||
|
||||
return objectStrain;
|
||||
}
|
||||
|
||||
offhandObjectDuration = hitObject.DeltaTime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a penalty for hit objects marked with <see cref="TaikoDifficultyHitObject.StaminaCheese"/>.
|
||||
/// </summary>
|
||||
/// <param name="notePairDuration">The duration between the current and previous note hit using the hand indicated by <see cref="hand"/>.</param>
|
||||
private double cheesePenalty(double notePairDuration)
|
||||
{
|
||||
if (notePairDuration > 125) return 1;
|
||||
if (notePairDuration < 100) return 0.6;
|
||||
|
||||
return 0.6 + (notePairDuration - 100) * 0.016;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a speed bonus dependent on the time since the last hit performed using this hand.
|
||||
/// </summary>
|
||||
/// <param name="notePairDuration">The duration between the current and previous note hit using the hand indicated by <see cref="hand"/>.</param>
|
||||
private double speedBonus(double notePairDuration)
|
||||
{
|
||||
if (notePairDuration >= 200) return 0;
|
||||
|
||||
double bonus = 200 - notePairDuration;
|
||||
bonus *= bonus;
|
||||
return bonus / 100000;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
public class Strain : Skill
|
||||
{
|
||||
private const double rhythm_change_base_threshold = 0.2;
|
||||
private const double rhythm_change_base = 2.0;
|
||||
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.3;
|
||||
|
||||
private ColourSwitch lastColourSwitch = ColourSwitch.None;
|
||||
|
||||
private int sameColourCount = 1;
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
double addition = 1;
|
||||
|
||||
// We get an extra addition if we are not a slider or spinner
|
||||
if (current.LastObject is Hit && current.BaseObject is Hit && current.BaseObject.StartTime - current.LastObject.StartTime < 1000)
|
||||
{
|
||||
if (hasColourChange(current))
|
||||
addition += 0.75;
|
||||
|
||||
if (hasRhythmChange(current))
|
||||
addition += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastColourSwitch = ColourSwitch.None;
|
||||
sameColourCount = 1;
|
||||
}
|
||||
|
||||
double additionFactor = 1;
|
||||
|
||||
// Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50
|
||||
if (current.DeltaTime < 50)
|
||||
additionFactor = 0.4 + 0.6 * current.DeltaTime / 50;
|
||||
|
||||
return additionFactor * addition;
|
||||
}
|
||||
|
||||
private bool hasRhythmChange(DifficultyHitObject current)
|
||||
{
|
||||
// We don't want a division by zero if some random mapper decides to put two HitObjects at the same time.
|
||||
if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0)
|
||||
return false;
|
||||
|
||||
double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime);
|
||||
|
||||
if (timeElapsedRatio >= 8)
|
||||
return false;
|
||||
|
||||
double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
|
||||
|
||||
return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold;
|
||||
}
|
||||
|
||||
private bool hasColourChange(DifficultyHitObject current)
|
||||
{
|
||||
var taikoCurrent = (TaikoDifficultyHitObject)current;
|
||||
|
||||
if (!taikoCurrent.HasTypeChange)
|
||||
{
|
||||
sameColourCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldColourSwitch = lastColourSwitch;
|
||||
var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd;
|
||||
|
||||
lastColourSwitch = newColourSwitch;
|
||||
sameColourCount = 1;
|
||||
|
||||
// We only want a bonus if the parity of the color switch changes
|
||||
return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch;
|
||||
}
|
||||
|
||||
private enum ColourSwitch
|
||||
{
|
||||
None,
|
||||
Even,
|
||||
Odd
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double StaminaStrain;
|
||||
public double RhythmStrain;
|
||||
public double ColourStrain;
|
||||
public double ApproachRate;
|
||||
public double GreatHitWindow;
|
||||
public int MaxCombo;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -19,39 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double star_scaling_factor = 0.04125;
|
||||
private const double rhythm_skill_multiplier = 0.014;
|
||||
private const double colour_skill_multiplier = 0.01;
|
||||
private const double stamina_skill_multiplier = 0.02;
|
||||
|
||||
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
HitWindows hitWindows = new TaikoHitWindows();
|
||||
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
||||
|
||||
return new TaikoDifficultyAttributes
|
||||
{
|
||||
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
|
||||
Mods = mods,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||
Skills = skills
|
||||
new Colour(),
|
||||
new Rhythm(),
|
||||
new Stamina(true),
|
||||
new Stamina(false),
|
||||
};
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() };
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
{
|
||||
@ -60,5 +44,124 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
new TaikoModEasy(),
|
||||
new TaikoModHardRock(),
|
||||
};
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
List<TaikoDifficultyHitObject> taikoDifficultyHitObjects = new List<TaikoDifficultyHitObject>();
|
||||
|
||||
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
taikoDifficultyHitObjects.Add(
|
||||
new TaikoDifficultyHitObject(
|
||||
beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, i
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
new StaminaCheeseDetector(taikoDifficultyHitObjects).FindCheese();
|
||||
return taikoDifficultyHitObjects;
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
var colour = (Colour)skills[0];
|
||||
var rhythm = (Rhythm)skills[1];
|
||||
var staminaRight = (Stamina)skills[2];
|
||||
var staminaLeft = (Stamina)skills[3];
|
||||
|
||||
double colourRating = colour.DifficultyValue() * colour_skill_multiplier;
|
||||
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||
double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier;
|
||||
|
||||
double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
|
||||
staminaRating *= staminaPenalty;
|
||||
|
||||
double combinedRating = locallyCombinedDifficulty(colour, rhythm, staminaRight, staminaLeft, staminaPenalty);
|
||||
double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating);
|
||||
double starRating = 1.4 * separatedRating + 0.5 * combinedRating;
|
||||
starRating = rescale(starRating);
|
||||
|
||||
HitWindows hitWindows = new TaikoHitWindows();
|
||||
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
||||
|
||||
return new TaikoDifficultyAttributes
|
||||
{
|
||||
StarRating = starRating,
|
||||
Mods = mods,
|
||||
StaminaStrain = staminaRating,
|
||||
RhythmStrain = rhythmRating,
|
||||
ColourStrain = colourRating,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)hitWindows.WindowFor(HitResult.Great) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the penalty for the stamina skill for maps with low colour difficulty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Some maps (especially converts) can be easy to read despite a high note density.
|
||||
/// This penalty aims to reduce the star rating of such maps by factoring in colour difficulty to the stamina skill.
|
||||
/// </remarks>
|
||||
private double simpleColourPenalty(double staminaDifficulty, double colorDifficulty)
|
||||
{
|
||||
if (colorDifficulty <= 0) return 0.79 - 0.25;
|
||||
|
||||
return 0.79 - Math.Atan(staminaDifficulty / colorDifficulty - 12) / Math.PI / 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector.
|
||||
/// </summary>
|
||||
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
|
||||
/// <param name="values">The coefficients of the vector.</param>
|
||||
private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the partial star rating of the beatmap, calculated using peak strains from all sections of the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
||||
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
||||
/// </remarks>
|
||||
private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft, double staminaPenalty)
|
||||
{
|
||||
List<double> peaks = new List<double>();
|
||||
|
||||
for (int i = 0; i < colour.StrainPeaks.Count; i++)
|
||||
{
|
||||
double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier;
|
||||
double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier;
|
||||
double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty;
|
||||
peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak));
|
||||
}
|
||||
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
foreach (double strain in peaks.OrderByDescending(d => d))
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a final re-scaling of the star rating to bring maps with recorded full combos below 9.5 stars.
|
||||
/// </summary>
|
||||
/// <param name="sr">The raw star rating value before re-scaling.</param>
|
||||
private double rescale(double sr)
|
||||
{
|
||||
if (sr < 0) return sr;
|
||||
|
||||
return 10.43 * Math.Log(sr / 8 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
strainValue *= Math.Pow(0.985, countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (Attributes.MaxCombo > 0)
|
||||
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0);
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
strainValue *= 1.025;
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ using osu.Game.Rulesets.Taiko.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Taiko.Edit;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@ -32,7 +31,6 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class TaikoRuleset : Ruleset, ILegacyRuleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
|
||||
|
@ -10,6 +10,7 @@ using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@ -19,6 +20,7 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
@ -26,18 +28,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[TestFixture]
|
||||
public class LegacyBeatmapEncoderTest
|
||||
{
|
||||
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
|
||||
private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore();
|
||||
|
||||
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu"));
|
||||
|
||||
[TestCaseSource(nameof(allBeatmaps))]
|
||||
public void TestEncodeDecodeStability(string name)
|
||||
{
|
||||
var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name));
|
||||
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded));
|
||||
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
|
||||
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
|
||||
|
||||
sort(decoded);
|
||||
sort(decodedAfterEncode);
|
||||
sort(decoded.beatmap);
|
||||
sort(decodedAfterEncode.beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize()));
|
||||
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
|
||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
||||
}
|
||||
|
||||
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
|
||||
{
|
||||
// equal to null, no need to SequenceEqual
|
||||
if (a.ComboColours == null && b.ComboColours == null)
|
||||
return true;
|
||||
|
||||
if (a.ComboColours == null || b.ComboColours == null)
|
||||
return false;
|
||||
|
||||
return a.ComboColours.SequenceEqual(b.ComboColours);
|
||||
}
|
||||
|
||||
private void sort(IBeatmap beatmap)
|
||||
@ -50,18 +67,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private IBeatmap decodeFromLegacy(Stream stream)
|
||||
private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name)
|
||||
{
|
||||
using (var reader = new LineBufferedReader(stream))
|
||||
return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader));
|
||||
{
|
||||
var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
|
||||
var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
|
||||
return (convert(beatmap), beatmapSkin);
|
||||
}
|
||||
}
|
||||
|
||||
private Stream encodeToLegacy(IBeatmap beatmap)
|
||||
private class TestLegacySkin : LegacySkin
|
||||
{
|
||||
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
|
||||
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
|
||||
{
|
||||
var (beatmap, beatmapSkin) = fullBeatmap;
|
||||
var stream = new MemoryStream();
|
||||
|
||||
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(beatmap).Encode(writer);
|
||||
new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer);
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
@ -106,7 +136,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
||||
|
||||
protected override Track GetTrack() => throw new NotImplementedException();
|
||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
@ -90,6 +92,38 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(2, difficulty.SliderTickRate);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodePostConverted()
|
||||
{
|
||||
var converted = new OsuBeatmapConverter(decodeAsJson(normal), new OsuRuleset()).Convert();
|
||||
|
||||
var processor = new OsuBeatmapProcessor(converted);
|
||||
|
||||
processor.PreProcess();
|
||||
foreach (var o in converted.HitObjects)
|
||||
o.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
|
||||
processor.PostProcess();
|
||||
|
||||
var beatmap = converted.Serialize().Deserialize<Beatmap>();
|
||||
|
||||
var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats;
|
||||
var positionData = beatmap.HitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.IsNotNull(curveData);
|
||||
Assert.AreEqual(90, curveData.Path.Distance);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
|
||||
positionData = beatmap.HitObjects[1] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeHitObjects()
|
||||
{
|
||||
@ -100,6 +134,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.IsNotNull(curveData);
|
||||
Assert.AreEqual(90, curveData.Path.Distance);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
|
@ -15,8 +15,10 @@ using osu.Framework.Extensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
@ -32,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportWhenClosed()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -49,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportThenDelete()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -70,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportThenImport()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -96,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportThenImportWithReZip()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -154,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportThenImportWithChangedFile()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -205,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportThenImportWithDifferentFilename()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -257,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportCorruptThenImport()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -299,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestRollbackOnFailure()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -376,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportThenDeleteThenImport()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -404,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}"))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString()))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -438,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public async Task TestImportWithDuplicateBeatmapIDs()
|
||||
{
|
||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -524,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportWhenFileOpen()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -546,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportWithDuplicateHashes()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -588,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportNestedStructure()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -633,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestImportWithIgnoredDirectoryInArchive()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -687,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestUpdateBeatmapInfo()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -717,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[Test]
|
||||
public async Task TestUpdateBeatmapFile()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile)))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -756,6 +758,63 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateNewEmptyBeatmap()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
|
||||
|
||||
manager.Save(working.BeatmapInfo, working.Beatmap);
|
||||
|
||||
var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
|
||||
|
||||
// Check that the new file is referenced correctly by attempting a retrieval
|
||||
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
|
||||
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateNewBeatmapWithObject()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
|
||||
|
||||
((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 });
|
||||
|
||||
manager.Save(working.BeatmapInfo, working.Beatmap);
|
||||
|
||||
var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
|
||||
|
||||
// Check that the new file is referenced correctly by attempting a retrieval
|
||||
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
|
||||
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
|
||||
Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
|
||||
{
|
||||
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
|
||||
|
@ -23,15 +23,19 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestHitObjectAddEvent()
|
||||
{
|
||||
var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||
|
||||
HitObject addedObject = null;
|
||||
editorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
|
||||
var hitCircle = new HitCircle();
|
||||
|
||||
editorBeatmap.Add(hitCircle);
|
||||
Assert.That(addedObject, Is.EqualTo(hitCircle));
|
||||
HitObject addedObject = null;
|
||||
EditorBeatmap editorBeatmap = null;
|
||||
|
||||
AddStep("add beatmap", () =>
|
||||
{
|
||||
Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||
editorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
});
|
||||
|
||||
AddStep("add hitobject", () => editorBeatmap.Add(hitCircle));
|
||||
AddAssert("received add event", () => addedObject == hitCircle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -41,13 +45,15 @@ namespace osu.Game.Tests.Beatmaps
|
||||
public void HitObjectRemoveEvent()
|
||||
{
|
||||
var hitCircle = new HitCircle();
|
||||
var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
|
||||
|
||||
HitObject removedObject = null;
|
||||
EditorBeatmap editorBeatmap = null;
|
||||
AddStep("add beatmap", () =>
|
||||
{
|
||||
Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
|
||||
editorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
|
||||
editorBeatmap.Remove(hitCircle);
|
||||
Assert.That(removedObject, Is.EqualTo(hitCircle));
|
||||
});
|
||||
AddStep("remove hitobject", () => editorBeatmap.Remove(editorBeatmap.HitObjects.First()));
|
||||
AddAssert("received remove event", () => removedObject == hitCircle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -147,6 +153,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
public void TestResortWhenStartTimeChanged()
|
||||
{
|
||||
var hitCircle = new HitCircle { StartTime = 1000 };
|
||||
|
||||
var editorBeatmap = new EditorBeatmap(new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
|
221
osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
Normal file
221
osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
Normal file
@ -0,0 +1,221 @@
|
||||
// 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.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
[TestFixture]
|
||||
public class ImportCollectionsTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestImportEmptyDatabase()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
await osu.CollectionManager.Import(new MemoryStream());
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestImportWithNoBeatmaps()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
|
||||
Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.Zero);
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second"));
|
||||
Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestImportWithBeatmaps()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host, true);
|
||||
|
||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
|
||||
Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second"));
|
||||
Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestImportMalformedDatabase()
|
||||
{
|
||||
bool exceptionThrown = false;
|
||||
UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true;
|
||||
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += setException;
|
||||
|
||||
var osu = loadOsu(host, true);
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var bw = new BinaryWriter(ms, Encoding.UTF8, true))
|
||||
{
|
||||
for (int i = 0; i < 10000; i++)
|
||||
bw.Write((byte)i);
|
||||
}
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
await osu.CollectionManager.Import(ms);
|
||||
}
|
||||
|
||||
Assert.That(host.UpdateThread.Running, Is.True);
|
||||
Assert.That(exceptionThrown, Is.False);
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
AppDomain.CurrentDomain.UnhandledException -= setException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestSaveAndReload()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host, true);
|
||||
|
||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||
|
||||
// Move first beatmap from second collection into the first.
|
||||
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
||||
osu.CollectionManager.Collections[1].Beatmaps.RemoveAt(0);
|
||||
|
||||
// Rename the second collecction.
|
||||
osu.CollectionManager.Collections[1].Name.Value = "Another";
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host, true);
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
|
||||
Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another"));
|
||||
Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TestOsuGameBase loadOsu(GameHost host, bool withBeatmap = false)
|
||||
{
|
||||
var osu = new TestOsuGameBase(withBeatmap);
|
||||
|
||||
#pragma warning disable 4014
|
||||
Task.Run(() => host.Run(osu));
|
||||
#pragma warning restore 4014
|
||||
|
||||
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||
|
||||
return osu;
|
||||
}
|
||||
|
||||
private void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
|
||||
{
|
||||
Task task = Task.Run(() =>
|
||||
{
|
||||
while (!result()) Thread.Sleep(200);
|
||||
});
|
||||
|
||||
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||
}
|
||||
|
||||
private class TestOsuGameBase : OsuGameBase
|
||||
{
|
||||
public CollectionManager CollectionManager { get; private set; }
|
||||
|
||||
private readonly bool withBeatmap;
|
||||
|
||||
public TestOsuGameBase(bool withBeatmap)
|
||||
{
|
||||
this.withBeatmap = withBeatmap;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// Beatmap must be imported before the collection manager is loaded.
|
||||
if (withBeatmap)
|
||||
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||
|
||||
AddInternal(CollectionManager = new CollectionManager(Storage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -351,7 +351,7 @@ namespace osu.Game.Tests.Editing
|
||||
using (var encoded = new MemoryStream())
|
||||
{
|
||||
using (var sw = new StreamWriter(encoded))
|
||||
new LegacyBeatmapEncoder(beatmap).Encode(sw);
|
||||
new LegacyBeatmapEncoder(beatmap, null).Encode(sw);
|
||||
|
||||
return encoded.ToArray();
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -19,7 +17,14 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
GameplayClockContainer gcc = null;
|
||||
|
||||
AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty<Mod>(), 0)));
|
||||
AddStep("create container", () =>
|
||||
{
|
||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
working.LoadTrack();
|
||||
|
||||
Add(gcc = new GameplayClockContainer(working, 0));
|
||||
});
|
||||
|
||||
AddStep("start track", () => gcc.Start());
|
||||
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
|
||||
}
|
||||
|
@ -28,6 +28,20 @@ namespace osu.Game.Tests.Gameplay
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlyBonusScore()
|
||||
{
|
||||
var beatmap = new Beatmap<TestBonusHitObject> { HitObjects = { new TestBonusHitObject() } };
|
||||
|
||||
var scoreProcessor = new ScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
// Apply a judgement
|
||||
scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect });
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
private class TestHitObject : HitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new TestJudgement();
|
||||
@ -37,5 +51,17 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
protected override int NumericResultFor(HitResult result) => 100;
|
||||
}
|
||||
|
||||
private class TestBonusHitObject : HitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new TestBonusJudgement();
|
||||
}
|
||||
|
||||
private class TestBonusJudgement : Judgement
|
||||
{
|
||||
public override bool AffectsCombo => false;
|
||||
|
||||
protected override int NumericResultFor(HitResult result) => 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,10 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
AddStep("create container", () =>
|
||||
{
|
||||
Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty<Mod>(), 0));
|
||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
working.LoadTrack();
|
||||
|
||||
Add(gameplayContainer = new GameplayClockContainer(working, 0));
|
||||
|
||||
gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
||||
{
|
||||
@ -103,7 +106,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
|
||||
SelectedMods.Value = new[] { testedMod };
|
||||
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0));
|
||||
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0));
|
||||
|
||||
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
||||
{
|
||||
|
119
osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs
Normal file
119
osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs
Normal file
@ -0,0 +1,119 @@
|
||||
// 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.Game.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class LimitedCapacityQueueTest
|
||||
{
|
||||
private const int capacity = 3;
|
||||
|
||||
private LimitedCapacityQueue<int> queue;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
queue = new LimitedCapacityQueue<int>(capacity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyQueue()
|
||||
{
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = queue[0]);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => _ = queue.Dequeue());
|
||||
|
||||
int count = 0;
|
||||
foreach (var _ in queue)
|
||||
count++;
|
||||
|
||||
Assert.AreEqual(0, count);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void TestBelowCapacity(int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
queue.Enqueue(i);
|
||||
|
||||
Assert.AreEqual(count, queue.Count);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
Assert.AreEqual(i, queue[i]);
|
||||
|
||||
int j = 0;
|
||||
foreach (var item in queue)
|
||||
Assert.AreEqual(j++, item);
|
||||
|
||||
for (int i = queue.Count; i < queue.Count + capacity; i++)
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = queue[i]);
|
||||
}
|
||||
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestEnqueueAtFullCapacity(int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
queue.Enqueue(i);
|
||||
|
||||
Assert.AreEqual(capacity, queue.Count);
|
||||
|
||||
for (int i = 0; i < queue.Count; ++i)
|
||||
Assert.AreEqual(count - capacity + i, queue[i]);
|
||||
|
||||
int j = count - capacity;
|
||||
foreach (var item in queue)
|
||||
Assert.AreEqual(j++, item);
|
||||
|
||||
for (int i = queue.Count; i < queue.Count + capacity; i++)
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = queue[i]);
|
||||
}
|
||||
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestDequeueAtFullCapacity(int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
queue.Enqueue(i);
|
||||
|
||||
for (int i = 0; i < capacity; ++i)
|
||||
{
|
||||
Assert.AreEqual(count - capacity + i, queue.Dequeue());
|
||||
Assert.AreEqual(2 - i, queue.Count);
|
||||
}
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => queue.Dequeue());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClearQueue()
|
||||
{
|
||||
queue.Enqueue(3);
|
||||
queue.Enqueue(5);
|
||||
Assert.AreEqual(2, queue.Count);
|
||||
|
||||
queue.Clear();
|
||||
Assert.AreEqual(0, queue.Count);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = queue[0]);
|
||||
|
||||
queue.Enqueue(7);
|
||||
Assert.AreEqual(1, queue.Count);
|
||||
Assert.AreEqual(7, queue[0]);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => _ = queue[1]);
|
||||
|
||||
queue.Enqueue(9);
|
||||
Assert.AreEqual(2, queue.Count);
|
||||
Assert.AreEqual(9, queue[1]);
|
||||
}
|
||||
}
|
||||
}
|
BIN
osu.Game.Tests/Resources/Collections/collections.db
Normal file
BIN
osu.Game.Tests/Resources/Collections/collections.db
Normal file
Binary file not shown.
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
[Test]
|
||||
public async Task TestBasicImport()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
[Test]
|
||||
public async Task TestImportMods()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
[Test]
|
||||
public async Task TestImportStatistics()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
[Test]
|
||||
public async Task TestImportWithDeletedBeatmapSet()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet"))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
[Test]
|
||||
public async Task TestOnlineScoreIsAvailableLocally()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -26,6 +26,7 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
|
||||
beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]);
|
||||
beatmap.LoadTrack();
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,244 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Collections
|
||||
{
|
||||
public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene
|
||||
{
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly Container content;
|
||||
private readonly DialogOverlay dialogOverlay;
|
||||
private readonly CollectionManager manager;
|
||||
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapManager beatmapManager;
|
||||
|
||||
private ManageCollectionsDialog dialog;
|
||||
|
||||
public TestSceneManageCollectionsDialog()
|
||||
{
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
manager = new CollectionManager(LocalStorage),
|
||||
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||
dialogOverlay = new DialogOverlay()
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
|
||||
|
||||
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
dependencies.Cache(manager);
|
||||
dependencies.Cache(dialogOverlay);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
manager.Collections.Clear();
|
||||
Child = dialog = new ManageCollectionsDialog();
|
||||
});
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("show dialog", () => dialog.Show());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideDialog()
|
||||
{
|
||||
AddWaitStep("wait for animation", 3);
|
||||
AddStep("hide dialog", () => dialog.Hide());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastItemIsPlaceholder()
|
||||
{
|
||||
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddCollectionExternal()
|
||||
{
|
||||
AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } }));
|
||||
assertCollectionCount(1);
|
||||
assertCollectionName(0, "First collection");
|
||||
|
||||
AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } }));
|
||||
assertCollectionCount(2);
|
||||
assertCollectionName(1, "Second collection");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFocusPlaceholderDoesNotCreateCollection()
|
||||
{
|
||||
AddStep("focus placeholder", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
assertCollectionCount(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddCollectionViaPlaceholder()
|
||||
{
|
||||
DrawableCollectionListItem placeholderItem = null;
|
||||
|
||||
AddStep("focus placeholder", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(placeholderItem = dialog.ChildrenOfType<DrawableCollectionListItem>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
// Done directly via the collection since InputManager methods cannot add text to textbox...
|
||||
AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a");
|
||||
assertCollectionCount(1);
|
||||
AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model));
|
||||
|
||||
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRemoveCollectionExternal()
|
||||
{
|
||||
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||
{
|
||||
new BeatmapCollection { Name = { Value = "1" } },
|
||||
new BeatmapCollection { Name = { Value = "2" } },
|
||||
}));
|
||||
|
||||
AddStep("remove first collection", () => manager.Collections.RemoveAt(0));
|
||||
assertCollectionCount(1);
|
||||
assertCollectionName(0, "2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRemoveCollectionViaButton()
|
||||
{
|
||||
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||
{
|
||||
new BeatmapCollection { Name = { Value = "1" } },
|
||||
new BeatmapCollection { Name = { Value = "2" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
|
||||
}));
|
||||
|
||||
assertCollectionCount(2);
|
||||
|
||||
AddStep("click first delete button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("dialog not displayed", () => dialogOverlay.CurrentDialog == null);
|
||||
assertCollectionCount(1);
|
||||
assertCollectionName(0, "2");
|
||||
|
||||
AddStep("click first delete button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog);
|
||||
AddStep("click confirmation", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
assertCollectionCount(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionNotRemovedWhenDialogCancelled()
|
||||
{
|
||||
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||
{
|
||||
new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
|
||||
}));
|
||||
|
||||
assertCollectionCount(1);
|
||||
|
||||
AddStep("click first delete button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog);
|
||||
AddStep("click cancellation", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
assertCollectionCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRenamedExternal()
|
||||
{
|
||||
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||
{
|
||||
new BeatmapCollection { Name = { Value = "1" } },
|
||||
new BeatmapCollection { Name = { Value = "2" } },
|
||||
}));
|
||||
|
||||
AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First");
|
||||
|
||||
assertCollectionName(0, "First");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRenamedOnTextChange()
|
||||
{
|
||||
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||
{
|
||||
new BeatmapCollection { Name = { Value = "1" } },
|
||||
new BeatmapCollection { Name = { Value = "2" } },
|
||||
}));
|
||||
|
||||
assertCollectionCount(2);
|
||||
|
||||
AddStep("change first collection name", () => dialog.ChildrenOfType<TextBox>().First().Text = "First");
|
||||
AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First");
|
||||
}
|
||||
|
||||
private void assertCollectionCount(int count)
|
||||
=> AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType<DrawableCollectionListItem>().Count(i => i.IsCreated.Value) == count);
|
||||
|
||||
private void assertCollectionName(int index, string name)
|
||||
=> AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType<DrawableCollectionListItem>().ElementAt(index).ChildrenOfType<TextBox>().First().Text == name);
|
||||
}
|
||||
}
|
@ -1,28 +1,27 @@
|
||||
// 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.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneEditorChangeStates : EditorTestScene
|
||||
{
|
||||
private EditorBeatmap editorBeatmap;
|
||||
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
public override void SetUpSteps()
|
||||
[Test]
|
||||
public void TestSelectedObjects()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("get beatmap", () => editorBeatmap = Editor.ChildrenOfType<EditorBeatmap>().Single());
|
||||
HitCircle obj = null;
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(obj = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("select hitobject", () => EditorBeatmap.SelectedHitObjects.Add(obj));
|
||||
AddAssert("confirm 1 selected", () => EditorBeatmap.SelectedHitObjects.Count == 1);
|
||||
AddStep("deselect hitobject", () => EditorBeatmap.SelectedHitObjects.Remove(obj));
|
||||
AddAssert("confirm 0 selected", () => EditorBeatmap.SelectedHitObjects.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -30,11 +29,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
int hitObjectCount = 0;
|
||||
|
||||
AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count);
|
||||
AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count);
|
||||
|
||||
addUndoSteps();
|
||||
|
||||
AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count);
|
||||
AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count);
|
||||
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -42,11 +42,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
int hitObjectCount = 0;
|
||||
|
||||
AddStep("get initial state", () => hitObjectCount = editorBeatmap.HitObjects.Count);
|
||||
AddStep("get initial state", () => hitObjectCount = EditorBeatmap.HitObjects.Count);
|
||||
|
||||
addRedoSteps();
|
||||
|
||||
AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count);
|
||||
AddAssert("no change occurred", () => hitObjectCount == EditorBeatmap.HitObjects.Count);
|
||||
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -58,15 +59,17 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("bind removal", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
editorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
EditorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
});
|
||||
|
||||
AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddAssert("hitobject added", () => addedObject == expectedObject);
|
||||
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
|
||||
|
||||
addUndoSteps();
|
||||
AddAssert("hitobject removed", () => removedObject == expectedObject);
|
||||
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -78,11 +81,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("bind removal", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
editorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
EditorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
});
|
||||
|
||||
AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
addUndoSteps();
|
||||
|
||||
AddStep("reset variables", () =>
|
||||
@ -94,6 +97,17 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
addRedoSteps();
|
||||
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
|
||||
AddAssert("no hitobject removed", () => removedObject == null);
|
||||
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddObjectThenSaveHasNoUnsavedChanges()
|
||||
{
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(new HitCircle { StartTime = 1000 }));
|
||||
|
||||
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
|
||||
AddStep("save changes", () => Editor.Save());
|
||||
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -105,12 +119,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("bind removal", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
editorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
EditorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
});
|
||||
|
||||
AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("remove object", () => editorBeatmap.Remove(expectedObject));
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("remove object", () => EditorBeatmap.Remove(expectedObject));
|
||||
AddStep("reset variables", () =>
|
||||
{
|
||||
addedObject = null;
|
||||
@ -120,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
addUndoSteps();
|
||||
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
|
||||
AddAssert("no hitobject removed", () => removedObject == null);
|
||||
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); // 2 steps performed, 1 undone
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -131,12 +146,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("bind removal", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
editorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
EditorBeatmap.HitObjectAdded += h => addedObject = h;
|
||||
EditorBeatmap.HitObjectRemoved += h => removedObject = h;
|
||||
});
|
||||
|
||||
AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("remove object", () => editorBeatmap.Remove(expectedObject));
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||
AddStep("remove object", () => EditorBeatmap.Remove(expectedObject));
|
||||
addUndoSteps();
|
||||
|
||||
AddStep("reset variables", () =>
|
||||
@ -148,19 +163,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
addRedoSteps();
|
||||
AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo)
|
||||
AddAssert("no hitobject added", () => addedObject == null);
|
||||
AddAssert("no changes", () => !Editor.HasUnsavedChanges); // end result is empty beatmap, matching original state
|
||||
}
|
||||
|
||||
private void addUndoSteps() => AddStep("undo", () => ((TestEditor)Editor).Undo());
|
||||
private void addUndoSteps() => AddStep("undo", () => Editor.Undo());
|
||||
|
||||
private void addRedoSteps() => AddStep("redo", () => ((TestEditor)Editor).Redo());
|
||||
|
||||
protected override Editor CreateEditor() => new TestEditor();
|
||||
|
||||
private class TestEditor : Editor
|
||||
{
|
||||
public new void Undo() => base.Undo();
|
||||
|
||||
public new void Redo() => base.Redo();
|
||||
}
|
||||
private void addRedoSteps() => AddStep("redo", () => Editor.Redo());
|
||||
}
|
||||
}
|
||||
|
154
osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs
Normal file
154
osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs
Normal file
@ -0,0 +1,154 @@
|
||||
// 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.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneEditorClipboard : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[Test]
|
||||
public void TestCutRemovesObjects()
|
||||
{
|
||||
var addedObject = new HitCircle { StartTime = 1000 };
|
||||
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
|
||||
AddStep("cut hitobject", () => Editor.Cut());
|
||||
|
||||
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
}
|
||||
|
||||
[TestCase(1000)]
|
||||
[TestCase(2000)]
|
||||
public void TestCutPaste(double newTime)
|
||||
{
|
||||
var addedObject = new HitCircle { StartTime = 1000 };
|
||||
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
|
||||
AddStep("cut hitobject", () => Editor.Cut());
|
||||
|
||||
AddStep("move forward in time", () => EditorClock.Seek(newTime));
|
||||
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
|
||||
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCutPasteSlider()
|
||||
{
|
||||
var addedObject = new Slider
|
||||
{
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(),
|
||||
new PathControlPoint(new Vector2(100, 0), PathType.Bezier)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
|
||||
AddStep("cut hitobject", () => Editor.Cut());
|
||||
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
|
||||
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
AddAssert("path matches", () =>
|
||||
{
|
||||
var path = ((Slider)EditorBeatmap.HitObjects.Single()).Path;
|
||||
return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCutPasteSpinner()
|
||||
{
|
||||
var addedObject = new Spinner
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 5000
|
||||
};
|
||||
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
|
||||
AddStep("cut hitobject", () => Editor.Cut());
|
||||
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
|
||||
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
|
||||
AddAssert("duration matches", () => ((Spinner)EditorBeatmap.HitObjects.Single()).Duration == 5000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCopyPaste()
|
||||
{
|
||||
var addedObject = new HitCircle { StartTime = 1000 };
|
||||
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
|
||||
AddStep("copy hitobject", () => Editor.Copy());
|
||||
|
||||
AddStep("move forward in time", () => EditorClock.Seek(2000));
|
||||
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
|
||||
AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2);
|
||||
|
||||
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCutNothing()
|
||||
{
|
||||
AddStep("cut hitobject", () => Editor.Cut());
|
||||
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCopyNothing()
|
||||
{
|
||||
AddStep("copy hitobject", () => Editor.Copy());
|
||||
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPasteNothing()
|
||||
{
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
new RadioButton("Item 1", () => { }),
|
||||
new RadioButton("Item 2", () => { }),
|
||||
new RadioButton("Item 3", () => { }),
|
||||
new RadioButton("Item 3", () => { }, () => new SpriteIcon { Icon = FontAwesome.Regular.Angry }),
|
||||
new RadioButton("Item 4", () => { }),
|
||||
new RadioButton("Item 5", () => { })
|
||||
}
|
||||
|
32
osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs
Normal file
32
osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneSetupScreen : EditorClockTestScene
|
||||
{
|
||||
[Cached(typeof(EditorBeatmap))]
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
public TestSceneSetupScreen()
|
||||
{
|
||||
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
||||
Child = new SetupScreen();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneCompletionCancellation : OsuPlayerTestScene
|
||||
{
|
||||
private Track track;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audio { get; set; }
|
||||
|
||||
@ -34,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
base.SetUpSteps();
|
||||
|
||||
// Ensure track has actually running before attempting to seek
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -73,13 +70,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void complete()
|
||||
{
|
||||
AddStep("seek to completion", () => track.Seek(5000));
|
||||
AddStep("seek to completion", () => Beatmap.Value.Track.Seek(5000));
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private void cancel()
|
||||
{
|
||||
AddStep("rewind to cancel", () => track.Seek(4000));
|
||||
AddStep("rewind to cancel", () => Beatmap.Value.Track.Seek(4000));
|
||||
AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
@ -91,11 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||
track = working.Track;
|
||||
return working;
|
||||
}
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -21,19 +20,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
private Track track;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
track = working.Track;
|
||||
return working;
|
||||
}
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
|
||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
[Test]
|
||||
public void TestNoJudgementsOnRewind()
|
||||
{
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
addSeekStep(3000);
|
||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
|
||||
@ -46,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time));
|
||||
|
||||
// Allow a few frames of lenience
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
|
54
osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
Normal file
54
osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.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.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneOverlayActivation : OsuPlayerTestScene
|
||||
{
|
||||
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivation()
|
||||
{
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivationPaused()
|
||||
{
|
||||
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
AddStep("pause gameplay", () => Player.Pause());
|
||||
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivationReplayLoaded()
|
||||
{
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
|
||||
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplayOverlayActivationBreaks()
|
||||
{
|
||||
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
|
||||
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
|
||||
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
|
||||
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
|
||||
}
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
|
||||
|
||||
protected class OverlayTestPlayer : TestPlayer
|
||||
{
|
||||
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private class ExampleContainer : PlayerSettingsGroup
|
||||
{
|
||||
protected override string Title => @"example";
|
||||
public ExampleContainer()
|
||||
: base("example")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
// 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.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
@ -32,7 +30,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
requestCount = 0;
|
||||
increment = skip_time;
|
||||
|
||||
Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty<Mod>(), 0)
|
||||
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
|
||||
working.LoadTrack();
|
||||
|
||||
Child = gameplayClockContainer = new GameplayClockContainer(working, 0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
|
@ -22,19 +22,32 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TestFixture]
|
||||
public class TestSceneStoryboard : OsuTestScene
|
||||
{
|
||||
private readonly Container<DrawableStoryboard> storyboardContainer;
|
||||
private Container<DrawableStoryboard> storyboardContainer;
|
||||
private DrawableStoryboard storyboard;
|
||||
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
[Test]
|
||||
public void TestStoryboard()
|
||||
{
|
||||
AddStep("Restart", restart);
|
||||
AddToggleStep("Passing", passing =>
|
||||
{
|
||||
if (storyboard != null) storyboard.Passing = passing;
|
||||
});
|
||||
}
|
||||
|
||||
public TestSceneStoryboard()
|
||||
[Test]
|
||||
public void TestStoryboardMissingVideo()
|
||||
{
|
||||
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Clock = new FramedClock();
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
musicController,
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -58,32 +71,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
State = { Value = Visibility.Visible },
|
||||
}
|
||||
});
|
||||
|
||||
Beatmap.BindValueChanged(beatmapChanged, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboard()
|
||||
{
|
||||
AddStep("Restart", restart);
|
||||
AddToggleStep("Passing", passing =>
|
||||
{
|
||||
if (storyboard != null) storyboard.Passing = passing;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStoryboardMissingVideo()
|
||||
{
|
||||
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.ValueChanged += beatmapChanged;
|
||||
}
|
||||
|
||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
|
||||
=> loadStoryboard(e.NewValue);
|
||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e) => loadStoryboard(e.NewValue);
|
||||
|
||||
private void restart()
|
||||
{
|
||||
|
@ -2,8 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Menu;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
@ -15,11 +15,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
public TestSceneIntroWelcome()
|
||||
{
|
||||
AddUntilStep("wait for load", () => getTrack() != null);
|
||||
|
||||
AddAssert("check if menu music loops", () => getTrack().Looping);
|
||||
AddUntilStep("wait for load", () => MusicController.TrackLoaded);
|
||||
AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1));
|
||||
AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping);
|
||||
}
|
||||
|
||||
private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track;
|
||||
}
|
||||
}
|
||||
|
83
osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
Normal file
83
osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Navigation;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public class TestSceneMusicActionHandling : OsuGameTestScene
|
||||
{
|
||||
private GlobalActionContainer globalActionContainer => Game.ChildrenOfType<GlobalActionContainer>().First();
|
||||
|
||||
[Test]
|
||||
public void TestMusicPlayAction()
|
||||
{
|
||||
AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething());
|
||||
AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||
AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused);
|
||||
AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||
AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMusicNavigationActions()
|
||||
{
|
||||
int importId = 0;
|
||||
Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
|
||||
|
||||
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
||||
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo
|
||||
{
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty(),
|
||||
}
|
||||
},
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = $"a test map {importId++}",
|
||||
Title = "title",
|
||||
}
|
||||
}).Wait(), 5);
|
||||
|
||||
AddStep("import beatmap with track", () =>
|
||||
{
|
||||
var setWithTrack = Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result;
|
||||
Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
|
||||
});
|
||||
|
||||
AddStep("bind to track change", () =>
|
||||
{
|
||||
trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>();
|
||||
Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection));
|
||||
});
|
||||
|
||||
AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000));
|
||||
AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000);
|
||||
|
||||
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||
AddAssert("no track change", () => trackChangeQueue.Count == 0);
|
||||
AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
|
||||
|
||||
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||
AddAssert("track changed to previous", () =>
|
||||
trackChangeQueue.Count == 1 &&
|
||||
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
|
||||
|
||||
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||
AddAssert("track changed to next", () =>
|
||||
trackChangeQueue.Count == 1 &&
|
||||
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
@ -11,14 +10,10 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public class TestSceneSongTicker : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
public TestSceneSongTicker()
|
||||
{
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
musicController,
|
||||
new SongTicker
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
public class TestToolbar : Toolbar
|
||||
{
|
||||
public new Bindable<OverlayActivation> OverlayActivationMode => base.OverlayActivationMode;
|
||||
public new Bindable<OverlayActivation> OverlayActivationMode => base.OverlayActivationMode as Bindable<OverlayActivation>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +162,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that the global mod instances are not retained by the rooms, as global mod instances are retained and re-used by the mod select overlay.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestGlobalModInstancesNotRetained()
|
||||
{
|
||||
OsuModDoubleTime mod = null;
|
||||
|
||||
AddStep("set dt mod and store", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
|
||||
// Mod select overlay replaces our mod.
|
||||
mod = (OsuModDoubleTime)SelectedMods.Value[0];
|
||||
});
|
||||
|
||||
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
|
||||
|
||||
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
|
||||
AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
|
||||
}
|
||||
|
||||
private class TestMatchSongSelect : MatchSongSelect
|
||||
{
|
||||
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
@ -132,6 +133,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
return () => imported;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some tests test waiting for a particular screen twice in a row, but expect a new instance each time.
|
||||
/// There's a case where they may succeed incorrectly if we don't compare against the previous instance.
|
||||
/// </summary>
|
||||
private IScreen lastWaitedScreen;
|
||||
|
||||
private void presentAndConfirm(Func<ScoreInfo> getImport, ScorePresentType type)
|
||||
{
|
||||
AddStep("present score", () => Game.PresentScore(getImport(), type));
|
||||
@ -139,13 +146,15 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
switch (type)
|
||||
{
|
||||
case ScorePresentType.Results:
|
||||
AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen);
|
||||
AddUntilStep("wait for results", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ResultsScreen);
|
||||
AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen);
|
||||
AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID);
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID);
|
||||
break;
|
||||
|
||||
case ScorePresentType.Gameplay:
|
||||
AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is ReplayPlayerLoader);
|
||||
AddUntilStep("wait for player loader", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ReplayPlayerLoader);
|
||||
AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen);
|
||||
AddUntilStep("correct score displayed", () => ((ReplayPlayerLoader)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID);
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID);
|
||||
break;
|
||||
|
@ -4,14 +4,16 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
@ -46,7 +48,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
Player player = null;
|
||||
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
Track track() => beatmap().Track;
|
||||
|
||||
PushAndConfirm(() => new TestSongSelect());
|
||||
|
||||
@ -62,30 +63,27 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
|
||||
AddUntilStep("wait for fail", () => player.HasFailed);
|
||||
|
||||
AddUntilStep("wait for track stop", () => !track().IsRunning);
|
||||
AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
|
||||
AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
|
||||
pushEscape();
|
||||
|
||||
AddUntilStep("wait for track playing", () => track().IsRunning);
|
||||
AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying);
|
||||
AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMenuMakesMusic()
|
||||
{
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
Track track() => beatmap().Track;
|
||||
|
||||
TestSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
|
||||
AddUntilStep("wait for no track", () => track() is TrackVirtual);
|
||||
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
||||
|
||||
AddStep("return to menu", () => songSelect.Exit());
|
||||
|
||||
AddUntilStep("wait for track", () => !(track() is TrackVirtual) && track().IsRunning);
|
||||
AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -140,12 +138,58 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded);
|
||||
AddStep("Seek close to end", () =>
|
||||
{
|
||||
Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000);
|
||||
Game.Beatmap.Value.Track.Completed += () => trackCompleted = true;
|
||||
Game.MusicController.SeekTo(Game.MusicController.CurrentTrack.Length - 1000);
|
||||
Game.MusicController.CurrentTrack.Completed += () => trackCompleted = true;
|
||||
});
|
||||
|
||||
AddUntilStep("Track was completed", () => trackCompleted);
|
||||
AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning);
|
||||
AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModSelectInput()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
|
||||
AddStep("Change ruleset to osu!taiko", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.PressKey(Key.Number2);
|
||||
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
InputManager.ReleaseKey(Key.Number2);
|
||||
});
|
||||
|
||||
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1);
|
||||
|
||||
AddAssert("Mods overlay still visible", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapOptionsInput()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
|
||||
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
||||
|
||||
AddStep("Change ruleset to osu!taiko", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.PressKey(Key.Number2);
|
||||
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
InputManager.ReleaseKey(Key.Number2);
|
||||
});
|
||||
|
||||
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1);
|
||||
|
||||
AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
private void pushEscape() =>
|
||||
@ -173,6 +217,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
private class TestSongSelect : PlaySongSelect
|
||||
{
|
||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||
|
||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[TestFixture]
|
||||
public class TestSceneFullscreenOverlay : OsuTestScene
|
||||
{
|
||||
private FullscreenOverlay overlay;
|
||||
private FullscreenOverlay<OverlayHeader> overlay;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
@ -38,10 +38,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("fire count 3", () => fireCount == 3);
|
||||
}
|
||||
|
||||
private class TestFullscreenOverlay : FullscreenOverlay
|
||||
private class TestFullscreenOverlay : FullscreenOverlay<OverlayHeader>
|
||||
{
|
||||
public TestFullscreenOverlay()
|
||||
: base(OverlayColourScheme.Pink)
|
||||
: base(OverlayColourScheme.Pink, null)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
|
||||
|
||||
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
|
||||
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
|
||||
{
|
||||
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
|
||||
});
|
||||
|
@ -1,40 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Profile.Sections;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneProfileCounterPill : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red);
|
||||
|
||||
private readonly CounterPill pill;
|
||||
private readonly BindableInt value = new BindableInt();
|
||||
|
||||
public TestSceneProfileCounterPill()
|
||||
{
|
||||
Child = pill = new CounterPill
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = { BindTarget = value }
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibility()
|
||||
{
|
||||
AddStep("Set value to 0", () => value.Value = 0);
|
||||
AddAssert("Check hidden", () => !pill.IsPresent);
|
||||
AddStep("Set value to 10", () => value.Value = 10);
|
||||
AddAssert("Check visible", () => pill.IsPresent);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
@ -53,16 +53,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private void showPersonalBestWithNullPosition()
|
||||
{
|
||||
leaderboard.TopScore = new APILegacyUserTopScoreInfo
|
||||
{
|
||||
Position = null,
|
||||
Score = new APILegacyScoreInfo
|
||||
leaderboard.TopScore = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.XH,
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() },
|
||||
User = new User
|
||||
{
|
||||
Id = 6602580,
|
||||
@ -73,22 +70,19 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
FlagName = @"ES",
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void showPersonalBest()
|
||||
{
|
||||
leaderboard.TopScore = new APILegacyUserTopScoreInfo
|
||||
leaderboard.TopScore = new ScoreInfo
|
||||
{
|
||||
Position = 999,
|
||||
Score = new APILegacyScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.XH,
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
User = new User
|
||||
{
|
||||
Id = 6602580,
|
||||
@ -99,7 +93,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
FlagName = @"ES",
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,8 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
@ -16,10 +15,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
var overlay = new BeatmapOptionsOverlay();
|
||||
|
||||
overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null, Key.Number1);
|
||||
overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null, Key.Number2);
|
||||
overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number3);
|
||||
overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number4);
|
||||
var colours = new OsuColour();
|
||||
|
||||
overlay.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, null);
|
||||
overlay.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, null);
|
||||
overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null);
|
||||
overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, null);
|
||||
overlay.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, null);
|
||||
|
||||
Add(overlay);
|
||||
|
||||
|
237
osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
Normal file
237
osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
Normal file
@ -0,0 +1,237 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public class TestSceneFilterControl : OsuManualInputManagerTestScene
|
||||
{
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
private readonly CollectionManager collectionManager;
|
||||
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapManager beatmapManager;
|
||||
|
||||
private FilterControl control;
|
||||
|
||||
public TestSceneFilterControl()
|
||||
{
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
collectionManager = new CollectionManager(LocalStorage),
|
||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
|
||||
|
||||
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
dependencies.Cache(collectionManager);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
collectionManager.Collections.Clear();
|
||||
|
||||
Child = control = new FilterControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = FilterControl.HEIGHT,
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEmptyCollectionFilterContainsAllBeatmaps()
|
||||
{
|
||||
assertCollectionDropdownContains("All beatmaps");
|
||||
assertCollectionHeaderDisplays("All beatmaps");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionAddedToDropdown()
|
||||
{
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } }));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRemovedFromDropdown()
|
||||
{
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } }));
|
||||
AddStep("remove collection", () => collectionManager.Collections.RemoveAt(0));
|
||||
|
||||
assertCollectionDropdownContains("1", false);
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRenamed()
|
||||
{
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
var dropdown = control.ChildrenOfType<CollectionFilterDropdown>().Single();
|
||||
dropdown.Current.Value = dropdown.ItemSource.ElementAt(1);
|
||||
});
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First");
|
||||
|
||||
assertCollectionDropdownContains("First");
|
||||
assertCollectionHeaderDisplays("First");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllBeatmapFilterDoesNotHaveAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0)));
|
||||
AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionFilterHasAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1)));
|
||||
AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonDisabledAndEnabledWithBeatmapChanges()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value);
|
||||
|
||||
AddStep("set dummy beatmap", () => Beatmap.SetDefault());
|
||||
AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonChangesWhenAddedAndRemovedFromCollection()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||
|
||||
AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo));
|
||||
AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare));
|
||||
|
||||
AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear());
|
||||
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonAddsAndRemovesBeatmap()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo));
|
||||
AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare));
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo));
|
||||
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManageCollectionsFilterIsNotSelected()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("click manage collections filter", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItems().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1");
|
||||
}
|
||||
|
||||
private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true)
|
||||
=> AddAssert($"collection dropdown header displays '{collectionName}'",
|
||||
() => shouldDisplay == (control.ChildrenOfType<CollectionFilterDropdown.CollectionDropdownHeader>().Single().ChildrenOfType<SpriteText>().First().Text == collectionName));
|
||||
|
||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||
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
|
||||
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<FillFlowContainer>().OfType<IHasText>().First().Text == collectionName)));
|
||||
|
||||
private IconButton getAddOrRemoveButton(int index)
|
||||
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
|
||||
|
||||
private void addExpandHeaderStep() => AddStep("expand header", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(control.ChildrenOfType<CollectionFilterDropdown.CollectionDropdownHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getAddOrRemoveButton(index));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private IEnumerable<Dropdown<CollectionFilterMenuItem>.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems()
|
||||
=> control.ChildrenOfType<CollectionFilterDropdown>().Single().ChildrenOfType<Dropdown<CollectionFilterMenuItem>.DropdownMenu.DrawableDropdownMenuItem>();
|
||||
}
|
||||
}
|
@ -6,11 +6,11 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
public TestSceneUserTopScoreContainer()
|
||||
{
|
||||
UserTopScoreContainer topScoreContainer;
|
||||
UserTopScoreContainer<ScoreInfo> topScoreContainer;
|
||||
|
||||
Add(dialogOverlay = new DialogOverlay
|
||||
{
|
||||
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.DarkGreen,
|
||||
},
|
||||
topScoreContainer = new UserTopScoreContainer
|
||||
topScoreContainer = new UserTopScoreContainer<ScoreInfo>(s => new LeaderboardScore(s, s.Position, false))
|
||||
{
|
||||
Origin = Anchor.BottomCentre,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
@ -52,16 +52,14 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
var scores = new[]
|
||||
{
|
||||
new APILegacyUserTopScoreInfo
|
||||
new ScoreInfo
|
||||
{
|
||||
Position = 999,
|
||||
Score = new APILegacyScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.XH,
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
User = new User
|
||||
{
|
||||
Id = 6602580,
|
||||
@ -72,13 +70,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
FlagName = @"ES",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
new APILegacyUserTopScoreInfo
|
||||
new ScoreInfo
|
||||
{
|
||||
Position = 110000,
|
||||
Score = new APILegacyScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.X,
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
@ -93,13 +88,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
FlagName = @"BR",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
new APILegacyUserTopScoreInfo
|
||||
new ScoreInfo
|
||||
{
|
||||
Position = 22333,
|
||||
Score = new APILegacyScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.S,
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
@ -115,7 +107,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility);
|
||||
|
@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
private readonly NowPlayingOverlay np;
|
||||
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
public TestSceneBeatSyncedContainer()
|
||||
{
|
||||
Clock = new FramedClock();
|
||||
@ -36,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
musicController,
|
||||
new BeatContainer
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
@ -71,6 +67,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private readonly Box flashLayer;
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
|
||||
public BeatContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -165,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
if (timingPoints.Count == 0) return 0;
|
||||
|
||||
if (timingPoints[^1] == current)
|
||||
return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
|
||||
return (int)Math.Ceiling((musicController.CurrentTrack.Length - current.Time) / current.BeatLength);
|
||||
|
||||
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, Audio, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||
|
||||
beatmap = beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result.Beatmaps[0];
|
||||
|
@ -1,15 +1,10 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
@ -20,18 +15,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
private WorkingBeatmap currentBeatmap;
|
||||
|
||||
private NowPlayingOverlay nowPlayingOverlay;
|
||||
|
||||
private RulesetStore rulesets;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, GameHost host)
|
||||
private void load()
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
nowPlayingOverlay = new NowPlayingOverlay
|
||||
@ -51,44 +39,5 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
|
||||
AddStep(@"hide", () => nowPlayingOverlay.Hide());
|
||||
}
|
||||
|
||||
private BeatmapManager manager { get; set; }
|
||||
|
||||
private int importId;
|
||||
|
||||
[Test]
|
||||
public void TestPrevTrackBehavior()
|
||||
{
|
||||
// ensure we have at least two beatmaps available.
|
||||
AddRepeatStep("import beatmap", () => manager.Import(new BeatmapSetInfo
|
||||
{
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty(),
|
||||
}
|
||||
},
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = $"a test map {importId++}",
|
||||
Title = "title",
|
||||
}
|
||||
}).Wait(), 5);
|
||||
|
||||
AddStep(@"Next track", () => musicController.NextTrack());
|
||||
AddStep("Store track", () => currentBeatmap = Beatmap.Value);
|
||||
|
||||
AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000));
|
||||
AddUntilStep(@"Wait for current time to update", () => currentBeatmap.Track.CurrentTime > 5000);
|
||||
|
||||
AddStep(@"Set previous", () => musicController.PreviousTrack());
|
||||
|
||||
AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value);
|
||||
AddUntilStep("Wait for current time to update", () => currentBeatmap.Track.CurrentTime < 5000);
|
||||
|
||||
AddStep(@"Set previous", () => musicController.PreviousTrack());
|
||||
AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Overlays.Profile.Sections;
|
||||
using osu.Framework.Testing;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Framework.Allocation;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestScenePaginatedContainerHeader : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||
|
||||
private PaginatedContainerHeader header;
|
||||
|
||||
[Test]
|
||||
public void TestHiddenCounter()
|
||||
{
|
||||
AddStep("Create header", () => createHeader("Header with hidden counter", CounterVisibilityState.AlwaysHidden));
|
||||
AddAssert("Value is 0", () => header.Current.Value == 0);
|
||||
AddAssert("Counter is hidden", () => header.ChildrenOfType<CounterPill>().First().Alpha == 0);
|
||||
AddStep("Set count 10", () => header.Current.Value = 10);
|
||||
AddAssert("Value is 10", () => header.Current.Value == 10);
|
||||
AddAssert("Counter is hidden", () => header.ChildrenOfType<CounterPill>().First().Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibleCounter()
|
||||
{
|
||||
AddStep("Create header", () => createHeader("Header with visible counter", CounterVisibilityState.AlwaysVisible));
|
||||
AddAssert("Value is 0", () => header.Current.Value == 0);
|
||||
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
||||
AddStep("Set count 10", () => header.Current.Value = 10);
|
||||
AddAssert("Value is 10", () => header.Current.Value == 10);
|
||||
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibleWhenZeroCounter()
|
||||
{
|
||||
AddStep("Create header", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero));
|
||||
AddAssert("Value is 0", () => header.Current.Value == 0);
|
||||
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
||||
AddStep("Set count 10", () => header.Current.Value = 10);
|
||||
AddAssert("Value is 10", () => header.Current.Value == 10);
|
||||
AddAssert("Counter is hidden", () => header.ChildrenOfType<CounterPill>().First().Alpha == 0);
|
||||
AddStep("Set count 0", () => header.Current.Value = 0);
|
||||
AddAssert("Value is 0", () => header.Current.Value == 0);
|
||||
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialVisibility()
|
||||
{
|
||||
AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 0));
|
||||
AddAssert("Value is 0", () => header.Current.Value == 0);
|
||||
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
||||
|
||||
AddStep("Create header with 1 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 1));
|
||||
AddAssert("Value is 1", () => header.Current.Value == 1);
|
||||
AddAssert("Counter is hidden", () => header.ChildrenOfType<CounterPill>().First().Alpha == 0);
|
||||
}
|
||||
|
||||
private void createHeader(string text, CounterVisibilityState state, int initialValue = 0)
|
||||
{
|
||||
Clear();
|
||||
Add(header = new PaginatedContainerHeader(text, state)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = { Value = initialValue }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -2,32 +2,35 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Music;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestScenePlaylistOverlay : OsuTestScene
|
||||
public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene
|
||||
{
|
||||
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
|
||||
|
||||
private PlaylistOverlay playlistOverlay;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
PlaylistOverlay overlay;
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(300, 500),
|
||||
Child = overlay = new PlaylistOverlay
|
||||
Child = playlistOverlay = new PlaylistOverlay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -53,7 +56,45 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
}
|
||||
|
||||
overlay.BeatmapSets.BindTo(beatmapSets);
|
||||
playlistOverlay.BeatmapSets.BindTo(beatmapSets);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestRearrangeItems()
|
||||
{
|
||||
AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any());
|
||||
|
||||
AddStep("hold 1st item handle", () =>
|
||||
{
|
||||
var handle = this.ChildrenOfType<PlaylistItem.PlaylistItemHandle>().First();
|
||||
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("drag to 5th", () =>
|
||||
{
|
||||
var item = this.ChildrenOfType<PlaylistItem>().ElementAt(4);
|
||||
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre);
|
||||
});
|
||||
|
||||
AddAssert("song 1 is 5th", () => beatmapSets[4].Metadata.Title == "Some Song 1");
|
||||
|
||||
AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFiltering()
|
||||
{
|
||||
AddStep("set filter to \"10\"", () =>
|
||||
{
|
||||
var filterControl = playlistOverlay.ChildrenOfType<FilterControl>().Single();
|
||||
filterControl.Search.Current.Value = "10";
|
||||
});
|
||||
|
||||
AddAssert("results filtered correctly",
|
||||
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
|
||||
.Where(item => item.MatchingFilter)
|
||||
.All(item => item.FilterTerms.Any(term => term.Contains("10"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests
|
||||
|
||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||
|
||||
protected override Track GetTrack() => trackStore.Get(firstAudioFile);
|
||||
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
||||
|
||||
private string firstAudioFile
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
41
osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs
Normal file
41
osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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.Game.Tests.Visual;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tournament.Tests.Components
|
||||
{
|
||||
public class TestSceneDateTextBox : OsuManualInputManagerTestScene
|
||||
{
|
||||
private DateTextBox textBox;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Child = textBox = new DateTextBox
|
||||
{
|
||||
Width = 0.3f
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestCommitWithoutSettingBindable()
|
||||
{
|
||||
AddStep("click textbox", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(textBox);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("unfocus", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Vector2.Zero);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
</ItemGroup>
|
||||
|
@ -22,11 +22,12 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
|
||||
// hold a reference to the provided bindable so we don't have to in every settings section.
|
||||
private Bindable<DateTimeOffset> bindable;
|
||||
private Bindable<DateTimeOffset> bindable = new Bindable<DateTimeOffset>();
|
||||
|
||||
public DateTextBox()
|
||||
{
|
||||
base.Bindable = new Bindable<string>();
|
||||
|
||||
((OsuTextBox)Control).OnCommit = (sender, newText) =>
|
||||
{
|
||||
try
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user