1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 19:22:54 +08:00

Merge branch 'master' into expandable-controls

This commit is contained in:
Dean Herbert 2022-02-03 19:23:34 +09:00 committed by GitHub
commit b2efce2656
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
246 changed files with 15591 additions and 1102 deletions

1
.gitignore vendored
View File

@ -339,3 +339,4 @@ inspectcode
# Fody (pulled in by Realm) - schema file
FodyWeavers.xsd
**/FodyWeavers.xml

View File

@ -1,53 +1,75 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
addressable (2.7.0)
CFPropertyList (3.0.5)
rexml
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.413.0)
aws-sdk-core (3.110.0)
aws-eventstream (1.2.0)
aws-partitions (1.551.0)
aws-sdk-core (3.125.5)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.40.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (1.53.0)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.87.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-s3 (1.111.3)
aws-sdk-core (~> 3, >= 3.125.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.3)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.1)
excon (0.78.1)
faraday (1.2.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
emoji_regex (3.2.3)
excon (0.90.0)
faraday (1.9.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.1)
fastlane (2.170.0)
fastimage (2.2.6)
fastlane (2.181.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
@ -68,6 +90,7 @@ GEM
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
@ -94,65 +117,80 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.4.0)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.29.2)
addressable (~> 2.5)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (0.14.0)
googleauth (0.17.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
signet (~> 0.15)
highline (1.7.10)
http-cookie (1.0.3)
http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.5.1)
jwt (2.2.2)
jmespath (1.5.0)
json (2.6.1)
jwt (2.3.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.0.2)
mini_mime (1.1.2)
mini_portile2 (2.4.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.0)
naturally (2.2.1)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
os (1.1.1)
plist (3.5.0)
os (1.1.4)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.3)
representable (3.0.4)
rake (13.0.6)
representable (3.1.1)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.2)
rubyzip (2.3.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
signet (0.16.0)
addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
slack-notifier (2.4.0)
souyuz (0.9.1)
fastlane (>= 1.103.0)
highline (~> 1.7)
@ -160,6 +198,7 @@ GEM
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@ -167,18 +206,20 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
unf_ext (0.0.8)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => true;
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
inputs.Add(new ReplayState<EmptyScrollingAction>
{

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays
protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
inputs.Add(new ReplayState<PippidonAction>
{

View File

@ -12,7 +12,7 @@ Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
or alternatively using `brew install fastlane`
# Available Actions
## Android

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.115.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.125.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.202.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.128.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override double ScoreMultiplier => 1.12;
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{
MinValue = 0.5f,
MaxValue = 1.5f,

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);

View File

@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
case CatchSkinComponents.CatchComboCounter:
if (providesComboCounter)
return new LegacyCatchComboCounter(Skin);
return new LegacyCatchComboCounter();
return null;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
private readonly LegacyRollingCounter explosion;
public LegacyCatchComboCounter(ISkin skin)
public LegacyCatchComboCounter()
{
AutoSizeAxes = Axes.Both;

View File

@ -0,0 +1,103 @@
// 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.Mania.Mods;
using osu.Game.Tests.Visual;
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests.Mods
{
public class TestSceneManiaModHoldOff : ModTestScene
{
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
[Test]
public void TestMapHasNoHoldNotes()
{
var testBeatmap = createModdedBeatmap();
Assert.False(testBeatmap.HitObjects.OfType<HoldNote>().Any());
}
[Test]
public void TestCorrectNoteValues()
{
var testBeatmap = createRawBeatmap();
var noteValues = new List<double>(testBeatmap.HitObjects.OfType<HoldNote>().Count());
foreach (HoldNote h in testBeatmap.HitObjects.OfType<HoldNote>())
{
noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap));
}
noteValues.Sort();
Assert.AreEqual(noteValues, new List<double> { 0.125, 0.250, 0.500, 1.000, 2.000 });
}
[Test]
public void TestCorrectObjectCount()
{
// Ensure that the mod produces the expected number of objects when applied.
var rawBeatmap = createRawBeatmap();
var testBeatmap = createModdedBeatmap();
// Calculate expected number of objects
int expectedObjectCount = 0;
foreach (ManiaHitObject h in rawBeatmap.HitObjects)
{
// Both notes and hold notes account for at least one object
expectedObjectCount++;
if (h.GetType() == typeof(HoldNote))
{
double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap);
if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD)
{
// Should generate an end note if it's longer than the minimum note value
expectedObjectCount++;
}
}
}
Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
}
private static ManiaBeatmap createModdedBeatmap()
{
var beatmap = createRawBeatmap();
var holdOffMod = new ManiaModHoldOff();
foreach (var hitObject in beatmap.HitObjects)
hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty());
holdOffMod.ApplyToBeatmap(beatmap);
return beatmap;
}
private static ManiaBeatmap createRawBeatmap()
{
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60
// Add test hit objects
beatmap.HitObjects.Add(new Note { StartTime = 4000 });
beatmap.HitObjects.Add(new Note { StartTime = 4500 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note
beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note
return beatmap;
}
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new ColumnHitObjectArea(0, new HitObjectContainer())
Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new ColumnHitObjectArea(1, new HitObjectContainer())
Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
public bool Matches(BeatmapInfo beatmapInfo)
{
return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)

View File

@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania
{
[Cached] // Used for touch input, see ColumnTouchInputArea.
public class ManiaInputManager : RulesetInputManager<ManiaAction>
{
public ManiaInputManager(RulesetInfo ruleset, int variant)

View File

@ -243,7 +243,8 @@ namespace osu.Game.Rulesets.Mania
new ManiaModDifficultyAdjust(),
new ManiaModClassic(),
new ManiaModInvert(),
new ManiaModConstantSpeed()
new ManiaModConstantSpeed(),
new ManiaModHoldOff()
};
case ModType.Automation:

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{
MinValue = 0.5f,
MaxValue = 3f,

View File

@ -0,0 +1,72 @@
// 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.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion
{
public override string Name => "Hold Off";
public override string Acronym => "HO";
public override double ScoreMultiplier => 1;
public override string Description => @"Replaces all hold notes with normal notes.";
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
public override ModType Type => ModType.Conversion;
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
public const double END_NOTE_ALLOW_THRESHOLD = 0.5;
public void ApplyToBeatmap(IBeatmap beatmap)
{
var maniaBeatmap = (ManiaBeatmap)beatmap;
var newObjects = new List<ManiaHitObject>();
foreach (var h in beatmap.HitObjects.OfType<HoldNote>())
{
// Add a note for the beginning of the hold note
newObjects.Add(new Note
{
Column = h.Column,
StartTime = h.StartTime,
Samples = h.GetNodeSamples(0)
});
// Don't add an end note if the duration is shorter than the threshold
double noteValue = GetNoteDurationInBeatLength(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc.
if (noteValue >= END_NOTE_ALLOW_THRESHOLD)
{
newObjects.Add(new Note
{
Column = h.Column,
StartTime = h.EndTime,
Samples = h.GetNodeSamples((h.NodeSamples?.Count - 1) ?? 1)
});
}
}
maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType<Note>().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
}
public static double GetNoteDurationInBeatLength(HoldNote holdNote, ManiaBeatmap beatmap)
{
double beatLength = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BeatLength;
return holdNote.Duration / beatLength;
}
}
}

View File

@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override ModType Type => ModType.Conversion;
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
public void ApplyToBeatmap(IBeatmap beatmap)
{
var maniaBeatmap = (ManiaBeatmap)beatmap;

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
inputs.Add(new ReplayState<ManiaAction> { PressedActions = CurrentFrame?.Actions ?? new List<ManiaAction>() });
}

View File

@ -62,13 +62,14 @@ namespace osu.Game.Rulesets.Mania.UI
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
new ColumnTouchInputArea(this)
};
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
@ -139,5 +140,50 @@ namespace osu.Game.Rulesets.Mania.UI
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
public class ColumnTouchInputArea : Drawable
{
private readonly Column column;
[Resolved(canBeNull: true)]
private ManiaInputManager maniaInputManager { get; set; }
private KeyBindingContainer<ManiaAction> keyBindingContainer;
public ColumnTouchInputArea(Column column)
{
RelativeSizeAxes = Axes.Both;
this.column = column;
}
protected override void LoadComplete()
{
keyBindingContainer = maniaInputManager?.KeyBindingContainer;
}
protected override bool OnMouseDown(MouseDownEvent e)
{
keyBindingContainer?.TriggerPressed(column.Action.Value);
return base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseUpEvent e)
{
keyBindingContainer?.TriggerReleased(column.Action.Value);
base.OnMouseUp(e);
}
protected override bool OnTouchDown(TouchDownEvent e)
{
keyBindingContainer?.TriggerPressed(column.Action.Value);
return true;
}
protected override void OnTouchUp(TouchUpEvent e)
{
keyBindingContainer?.TriggerReleased(column.Action.Value);
}
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private readonly Drawable hitTarget;
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
{
AddRangeInternal(new[]

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
AlwaysPresent = true
}
}
}
},
}
};

View File

@ -0,0 +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 NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public class TestSceneOsuModAimAssist : OsuModTestScene
{
[TestCase(0.1f)]
[TestCase(0.5f)]
[TestCase(1)]
public void TestAimAssist(float strength)
{
CreateModTest(new ModTestData
{
Mod = new OsuModAimAssist
{
AssistStrength = { Value = strength },
},
PassCondition = () => true,
Autoplay = false,
});
}
}
}

View 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.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public class TestSceneOsuModAlternate : OsuModTestScene
{
[Test]
public void TestInputAtIntro() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new HitCircle
{
StartTime = 1000,
Position = new Vector2(100),
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(200)),
new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton),
}
});
[Test]
public void TestInputAlternating() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4,
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new HitCircle
{
StartTime = 500,
Position = new Vector2(100),
},
new HitCircle
{
StartTime = 1000,
Position = new Vector2(200, 100),
},
new HitCircle
{
StartTime = 1500,
Position = new Vector2(300, 100),
},
new HitCircle
{
StartTime = 2000,
Position = new Vector2(400, 100),
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(100)),
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
new OsuReplayFrame(1001, new Vector2(200, 100)),
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
new OsuReplayFrame(1501, new Vector2(300, 100)),
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
new OsuReplayFrame(2001, new Vector2(400, 100)),
}
});
[Test]
public void TestInputSingular() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new HitCircle
{
StartTime = 500,
Position = new Vector2(100),
},
new HitCircle
{
StartTime = 1000,
Position = new Vector2(200, 100),
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(100)),
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
}
});
[Test]
public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
Autoplay = false,
Beatmap = new Beatmap
{
Breaks = new List<BreakPeriod>
{
new BreakPeriod(500, 2250),
},
HitObjects = new List<HitObject>
{
new HitCircle
{
StartTime = 500,
Position = new Vector2(100),
},
new HitCircle
{
StartTime = 2500,
Position = new Vector2(100),
}
}
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(100)),
new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(2501, new Vector2(100)),
}
});
}
}

View File

@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private bool isBreak() => Player.IsBreakTime.Value;
private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha);
private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
}
}

View File

@ -118,7 +118,6 @@ namespace osu.Game.Rulesets.Osu.Tests
public Drawable GetDrawableComponent(ISkinComponent component) => null;
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public ISample GetSample(ISampleInfo sampleInfo) => null;
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{

View 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;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
{
public override string Name => "Aim Assist";
public override string Acronym => "AA";
public override IconUsage? Icon => FontAwesome.Solid.MousePointer;
public override ModType Type => ModType.Fun;
public override string Description => "No need to chase the circle the circle chases you!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) };
private IFrameStableClock gameplayClock;
[SettingSource("Assist strength", "How much this mod will assist you.", 0)]
public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f)
{
Precision = 0.05f,
MinValue = 0.05f,
MaxValue = 1.0f,
};
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
gameplayClock = drawableRuleset.FrameStableClock;
// Hide judgment displays and follow points as they won't make any sense.
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
drawableRuleset.Playfield.DisplayJudgements.Value = false;
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
}
public void Update(Playfield playfield)
{
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
{
switch (drawable)
{
case DrawableHitCircle circle:
easeTo(circle, cursorPos);
break;
case DrawableSlider slider:
if (!slider.HeadCircle.Result.HasResult)
easeTo(slider, cursorPos);
else
easeTo(slider, cursorPos - slider.Ball.DrawPosition);
break;
}
}
}
private void easeTo(DrawableHitObject hitObject, Vector2 destination)
{
double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value);
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
hitObject.Position = new Vector2(x, y);
}
}
}

View File

@ -0,0 +1,106 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
{
public override string Name => @"Alternate";
public override string Acronym => @"AL";
public override string Description => @"Don't use the same key twice in a row!";
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) };
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
private double firstObjectValidJudgementTime;
private IBindable<bool> isBreakTime;
private const double flash_duration = 1000;
private OsuAction? lastActionPressed;
private DrawableRuleset<OsuHitObject> ruleset;
private IFrameStableClock gameplayClock;
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
ruleset = drawableRuleset;
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
var firstHitObject = ruleset.Objects.FirstOrDefault();
firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0);
gameplayClock = drawableRuleset.FrameStableClock;
}
public void ApplyToPlayer(Player player)
{
isBreakTime = player.IsBreakTime.GetBoundCopy();
isBreakTime.ValueChanged += e =>
{
if (e.NewValue)
lastActionPressed = null;
};
}
private bool checkCorrectAction(OsuAction action)
{
if (isBreakTime.Value)
return true;
if (gameplayClock.CurrentTime < firstObjectValidJudgementTime)
return true;
switch (action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
break;
// Any action which is not left or right button should be ignored.
default:
return true;
}
if (lastActionPressed != action)
{
// User alternated correctly.
lastActionPressed = action;
return true;
}
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
return false;
}
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
{
private readonly OsuModAlternate mod;
public InputInterceptor(OsuModAlternate mod)
{
this.mod = mod;
}
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
// if the pressed action is incorrect, block it from reaching gameplay.
=> !mod.checkCorrectAction(e.Action);
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
{
}
}
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) };
public bool PerformFail() => false;

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAutoplay : ModAutoplay
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModCinema : ModCinema<OsuHitObject>
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods
};
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{
MinValue = 0.5f,
MaxValue = 2f,

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Fun;
public override string Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) };
private float theta;

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Fun;
public override string Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) };
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
private const int wiggle_strength = 10; // Higher = stronger wiggles

View File

@ -169,6 +169,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModClassic(),
new OsuModRandom(),
new OsuModMirror(),
new OsuModAlternate(),
};
case ModType.Automation:
@ -193,6 +194,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModApproachDifferent(),
new OsuModMuted(),
new OsuModNoScope(),
new OsuModAimAssist(),
};
case ModType.System:

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);

View File

@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly ProxyContainer approachCircles;
private readonly ProxyContainer spinnerProxies;
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
private readonly FollowPointRenderer followPoints;
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI
protected override void OnHitObjectAdded(HitObject hitObject)
{
base.OnHitObjectAdded(hitObject);
followPoints.AddFollowPoints((OsuHitObject)hitObject);
FollowPoints.AddFollowPoints((OsuHitObject)hitObject);
}
protected override void OnHitObjectRemoved(HitObject hitObject)
{
base.OnHitObjectRemoved(hitObject);
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject);
}
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
if (original.BeatmapInfo.RulesetID == 3)
if (original.BeatmapInfo.Ruleset.OnlineID == 3)
{
// Post processing step to transform mania hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override double ScoreMultiplier => 1.12;
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{
MinValue = 0.5f,
MaxValue = 1.5f,

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
inputs.Add(new ReplayState<TaikoAction> { PressedActions = CurrentFrame?.Actions ?? new List<TaikoAction>() });
}

View File

@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(164471, metadata.PreviewTime);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.IsTrue(beatmapInfo.RulesetID == 0);
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
@ -794,5 +794,74 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(path.Distance, Is.EqualTo(1));
}
}
[Test]
public void TestLegacyDefaultsPreserved()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var memoryStream = new MemoryStream())
using (var stream = new LineBufferedReader(memoryStream))
{
var decoded = decoder.Decode(stream);
Assert.Multiple(() =>
{
Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0));
Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f));
Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False);
Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False);
Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False);
Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False);
Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False);
Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal));
Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0));
Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
});
}
}
[Test]
public void TestUndefinedApproachRateInheritsOverallDifficulty()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoded = decoder.Decode(stream);
Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1));
Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
}
}
[Test]
public void TestApproachRateDefinedBeforeOverallDifficulty()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoded = decoder.Decode(stream);
Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
}
}
[Test]
public void TestApproachRateDefinedAfterOverallDifficulty()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoded = decoder.Decode(stream);
Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
}
}
}
}

View File

@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
private IBeatmap convert(IBeatmap beatmap)
{
switch (beatmap.BeatmapInfo.RulesetID)
switch (beatmap.BeatmapInfo.Ruleset.OnlineID)
{
case 0:
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;

View File

@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
Assert.IsTrue(beatmapInfo.RulesetID == 0);
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);

View File

@ -87,11 +87,6 @@ namespace osu.Game.Tests.Database
hasThreadedUsage.Wait();
// Usually the host would run the synchronization context work per frame.
// For the sake of keeping this test simple (there's only one update invocation),
// let's replace it so we can ensure work is run immediately.
SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext());
Assert.Throws<TimeoutException>(() =>
{
using (realm.BlockAllOperations())
@ -107,10 +102,5 @@ namespace osu.Game.Tests.Database
}
});
}
private class ImmediateExecuteSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object? state) => d(state);
}
}
}

View File

@ -47,16 +47,14 @@ namespace osu.Game.Tests.Database
liveBeatmap = beatmap.ToLive(realm);
});
using (realm.BlockAllOperations())
{
// recycle realm before migrating
}
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
migratedStorage.DeleteDirectory(string.Empty);
storage.Migrate(migratedStorage);
using (realm.BlockAllOperations())
{
storage.Migrate(migratedStorage);
}
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
}

View File

@ -114,7 +114,7 @@ namespace osu.Game.Tests.Database
}
protected static RulesetInfo CreateRuleset() =>
new RulesetInfo(0, "osu!", "osu", true);
new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true };
private class RealmTestGame : Framework.Game
{

View File

@ -52,7 +52,7 @@ namespace osu.Game.Tests.Editing.Checks
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
// Should fail to load, but not produce an error due to the extension not being expected to load.
Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
Assert.IsEmpty(check.Run(getContext(null)));
}
[Test]
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Editing.Checks
{
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
{
Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true)));
Assert.IsEmpty(check.Run(getContext(resourceStream)));
}
}
@ -107,7 +107,7 @@ namespace osu.Game.Tests.Editing.Checks
}
}
private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false)
private BeatmapVerifierContext getContext(Stream resourceStream)
{
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);

View File

@ -131,8 +131,6 @@ namespace osu.Game.Tests.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)

View File

@ -2,7 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
@ -12,47 +18,93 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneMasterGameplayClockContainer : OsuTestScene
{
private OsuConfigManager localConfig;
[BackgroundDependencyLoader]
private void load()
{
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
}
[Test]
public void TestStartThenElapsedTime()
{
GameplayClockContainer gcc = null;
GameplayClockContainer gameplayClockContainer = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gcc = new MasterGameplayClockContainer(working, 0));
Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
});
AddStep("start clock", () => gcc.Start());
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
AddStep("start clock", () => gameplayClockContainer.Start());
AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0);
}
[Test]
public void TestElapseThenReset()
{
GameplayClockContainer gcc = null;
GameplayClockContainer gameplayClockContainer = null;
AddStep("create container", () =>
{
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
working.LoadTrack();
Add(gcc = new MasterGameplayClockContainer(working, 0));
Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
});
AddStep("start clock", () => gcc.Start());
AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000);
AddStep("start clock", () => gameplayClockContainer.Start());
AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000);
double timeAtReset = 0;
AddStep("reset clock", () =>
{
timeAtReset = gcc.GameplayClock.CurrentTime;
gcc.Reset();
timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime;
gameplayClockContainer.Reset();
});
AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset);
AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset);
}
[Test]
public void TestSeekPerformsInGameplayTime(
[Values(1.0, 0.5, 2.0)] double clockRate,
[Values(0.0, 200.0, -200.0)] double userOffset,
[Values(false, true)] bool whileStopped)
{
ClockBackedTestWorkingBeatmap working = null;
GameplayClockContainer gameplayClockContainer = null;
AddStep("create container", () =>
{
working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio);
working.LoadTrack();
Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0));
if (whileStopped)
gameplayClockContainer.Stop();
gameplayClockContainer.Reset();
});
AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate)));
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f));
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f));
}
protected override void Dispose(bool isDisposing)
{
localConfig?.Dispose();
base.Dispose(isDisposing);
}
}
}

View File

@ -1,11 +1,17 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
@ -42,6 +48,43 @@ namespace osu.Game.Tests.Gameplay
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
}
[Test]
public void TestResetFromReplayFrame()
{
var beatmap = new Beatmap<HitObject> { HitObjects = { new HitCircle() } };
var scoreProcessor = new ScoreProcessor();
scoreProcessor.ApplyBeatmap(beatmap);
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
// No header shouldn't cause any change
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame());
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
// Reset with a miss instead.
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
{
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
});
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
// Reset with no judged hit.
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
{
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
});
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
}
private class TestJudgement : Judgement
{
public override HitResult MaxResult { get; }

View File

@ -16,7 +16,11 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
Ruleset = new RulesetInfo { OnlineID = 0 },
Ruleset = new RulesetInfo
{
ShortName = "osu",
OnlineID = 0
},
StarRating = 4.0d,
Difficulty = new BeatmapDifficulty
{
@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { OnlineID = 6 }
Ruleset = new RulesetInfo { ShortName = "catch" }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
@ -78,6 +82,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.IsFalse(carouselItem.Filtered.Value);
}
[Test]
public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets()
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { OnlineID = -1 },
AllowConvertedBeatmaps = true
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.IsFalse(carouselItem.Filtered.Value);
}
[Test]
[TestCase(true)]
[TestCase(false)]

View File

@ -0,0 +1,38 @@
// 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.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class RulesetInfoOrderingTest
{
[Test]
public void TestOrdering()
{
var rulesets = new[]
{
new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
new OsuRuleset().RulesetInfo,
new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
new CatchRuleset().RulesetInfo,
new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
};
var orderedRulesets = rulesets.OrderBy(r => r);
// Ensure all customs are after official.
Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 }));
// Ensure customs are grouped next to each other (ie. stably sorted).
Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2"));
Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3"));
}
}
}

View File

@ -26,12 +26,16 @@ namespace osu.Game.Tests.NonVisual
score.Statistics[HitResult.Good]++;
score.Rank = ScoreRank.X;
score.RealmUser.Username = "test";
Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10));
Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11));
Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B));
Assert.That(score.Rank, Is.EqualTo(ScoreRank.X));
Assert.That(scoreCopy.RealmUser.Username, Is.Empty);
Assert.That(score.RealmUser.Username, Is.EqualTo("test"));
}
[Test]

View File

@ -61,7 +61,6 @@ namespace osu.Game.Tests.NonVisual.Skinning
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotSupportedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
}
private class TestAnimationTimeReference : IAnimationTimeReference

View File

@ -47,7 +47,7 @@ namespace osu.Game.Tests.Online
{
Dependencies.Cache(rulesets = new RulesetStore(Realm));
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host));
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API));
}
[SetUp]
@ -173,14 +173,14 @@ namespace osu.Game.Tests.Online
protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
{
return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue);
return new TestBeatmapModelManager(this, storage, realm, onlineLookupQueue);
}
internal class TestBeatmapModelManager : BeatmapModelManager
{
private readonly TestBeatmapManager testBeatmapManager;
public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
: base(databaseAccess, storage, beatmapOnlineLookupQueue)
{
this.testBeatmapManager = testBeatmapManager;
@ -196,7 +196,7 @@ namespace osu.Game.Tests.Online
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
{
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider, GameHost gameHost)
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider)
: base(importer, apiProvider)
{
}

View File

@ -0,0 +1,3 @@
[Difficulty]
OverallDifficulty:1
ApproachRate:9

View File

@ -0,0 +1,3 @@
[Difficulty]
ApproachRate:9
OverallDifficulty:1

View File

@ -0,0 +1,2 @@
[Difficulty]
OverallDifficulty:1

View File

@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set target difficulty", () =>
{
targetDifficulty = sameRuleset
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName)
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName);
});
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);

View File

@ -4,11 +4,13 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Select;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
@ -116,5 +118,24 @@ namespace osu.Game.Tests.Visual.Editing
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
}
[Test]
public void TestExitWithoutSaveFromExistingBeatmap()
{
const string tags_to_save = "these tags will be saved";
const string tags_to_discard = "these tags should be discarded";
AddStep("Set tags", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_save);
SaveEditor();
AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
ReloadEditorToSameBeatmap();
AddAssert("Tags saved correctly", () => EditorBeatmap.BeatmapInfo.Metadata.Tags == tags_to_save);
AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard);
AddStep("Exit editor", () => Editor.Exit());
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
}
}
}

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Editing
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
=> beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0));
protected override void LoadEditor()
{
@ -70,7 +70,11 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () =>
{
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
// the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
// due to the beatmap refetch logic ran on editor suspend.
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
@ -99,7 +103,11 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () =>
{
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
// the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
// due to the beatmap refetch logic ran on editor suspend.
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});

View File

@ -3,9 +3,9 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osuTK.Input;
@ -19,28 +19,35 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
[BackgroundDependencyLoader]
private void load()
private HoldForMenuButton holdForMenuButton;
[SetUpSteps]
public void SetUpSteps()
{
HoldForMenuButton holdForMenuButton;
Add(holdForMenuButton = new HoldForMenuButton
AddStep("create button", () =>
{
Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight,
Action = () => exitAction = true
exitAction = false;
Child = holdForMenuButton = new HoldForMenuButton
{
Scale = new Vector2(2),
Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight,
Action = () => exitAction = true
};
});
}
var text = holdForMenuButton.Children.OfType<SpriteText>().First();
[Test]
public void TestMovementAndTrigger()
{
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton));
AddUntilStep("Text visible", () => text.IsPresent && !exitAction);
AddUntilStep("Text visible", () => getSpriteText().IsPresent && !exitAction);
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction);
AddUntilStep("Text is not visible", () => !getSpriteText().IsPresent && !exitAction);
AddStep("Trigger exit action", () =>
{
exitAction = false;
InputManager.MoveMouseTo(holdForMenuButton);
InputManager.PressButton(MouseButton.Left);
});
@ -50,6 +57,17 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction);
AddStep("Release", () => InputManager.ReleaseButton(MouseButton.Left));
}
[Test]
public void TestFadeOnNoInput()
{
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.One));
AddUntilStep("wait for text fade out", () => !getSpriteText().IsPresent);
AddUntilStep("wait for button fade out", () => holdForMenuButton.Alpha < 0.1f);
}
private SpriteText getSpriteText() => holdForMenuButton.Children.OfType<SpriteText>().First();
}
}

View File

@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new TaikoRuleset().RulesetInfo.ID);
AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new TaikoRuleset().RulesetInfo.ShortName);
}
[Test]
@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.RulesetID == new ManiaRuleset().RulesetInfo.ID);
AddAssert("submitted score has correct ruleset ID", () => Player.SubmittedScore?.ScoreInfo.Ruleset.ShortName == new ManiaRuleset().RulesetInfo.ShortName);
}
[Test]

View File

@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
}
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });

View File

@ -301,8 +301,6 @@ namespace osu.Game.Tests.Visual.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
@ -314,8 +312,6 @@ namespace osu.Game.Tests.Visual.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => throw new NotImplementedException();
}
[Cached(typeof(ISkinSource))]

View File

@ -15,11 +15,14 @@ using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Tests.Visual.Spectator;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -62,7 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("import beatmap", () =>
{
importedBeatmap = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely();
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID;
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.Ruleset.OnlineID == 0).OnlineID;
});
}
@ -200,6 +203,37 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator);
}
[Test]
public void TestFinalFramesPurgedBeforeEndingPlay()
{
AddStep("begin playing", () => spectatorClient.BeginPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()), new Score()));
AddStep("send frames and finish play", () =>
{
spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero));
spectatorClient.EndPlaying();
});
// We can't access API because we're an "online" test.
AddAssert("last received frame has time = 1000", () => spectatorClient.LastReceivedUserFrames.First().Value.Time == 1000);
}
[Test]
public void TestFinalFrameInBundleHasHeader()
{
FrameDataBundle lastBundle = null;
AddStep("bind to client", () => spectatorClient.OnNewFrames += (_, bundle) => lastBundle = bundle);
start(-1234);
sendFrames();
finish();
AddUntilStep("bundle received", () => lastBundle != null);
AddAssert("first frame does not have header", () => lastBundle.Frames[0].Header == null);
AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null);
}
private OsuFramedReplayInputHandler replayHandler =>
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;

View File

@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void onNewFrames(int userId, FrameDataBundle frames)
{
Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})");
Logger.Log($"Received {frames.Frames.Count} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})");
foreach (var legacyFrame in frames.Frames)
{
@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
}
public override void CollectPendingInputs(List<IInput> inputs)
protected override void CollectReplayInputs(List<IInput> inputs)
{
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
inputs.Add(new ReplayState<TestAction> { PressedActions = CurrentFrame?.Actions ?? new List<TestAction>() });

View File

@ -5,6 +5,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK;
@ -18,10 +20,17 @@ namespace osu.Game.Tests.Visual.Menus
[Cached]
private OsuLogo logo;
protected abstract bool IntroReliesOnTrack { get; }
protected OsuScreenStack IntroStack;
private IntroScreen intro;
[Cached]
private NotificationOverlay notifications;
private ScheduledDelegate trackResetDelegate;
protected IntroTestScene()
{
Children = new Drawable[]
@ -38,6 +47,11 @@ namespace osu.Game.Tests.Visual.Menus
RelativePositionAxes = Axes.Both,
Depth = float.MinValue,
Position = new Vector2(0.5f),
},
notifications = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
}
};
}
@ -63,6 +77,41 @@ namespace osu.Game.Tests.Visual.Menus
AddUntilStep("wait for menu", () => intro.DidLoadMenu);
}
[Test]
public virtual void TestPlayIntroWithFailingAudioDevice()
{
AddStep("hide notifications", () => notifications.Hide());
AddStep("restart sequence", () =>
{
logo.FinishTransforms();
logo.IsTracking = false;
IntroStack?.Expire();
Add(IntroStack = new OsuScreenStack
{
RelativeSizeAxes = Axes.Both,
});
IntroStack.Push(intro = CreateScreen());
});
AddStep("trigger failure", () =>
{
trackResetDelegate = Scheduler.AddDelayed(() =>
{
intro.Beatmap.Value.Track.Seek(0);
}, 0, true);
});
AddUntilStep("wait for menu", () => intro.DidLoadMenu);
if (IntroReliesOnTrack)
AddUntilStep("wait for notification", () => notifications.UnreadCount.Value == 1);
AddStep("uninstall delegate", () => trackResetDelegate?.Cancel());
}
protected abstract IntroScreen CreateScreen();
}
}

View File

@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneIntroCircles : IntroTestScene
{
protected override bool IntroReliesOnTrack => false;
protected override IntroScreen CreateScreen() => new IntroCircles();
}
}

View File

@ -9,6 +9,7 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneIntroTriangles : IntroTestScene
{
protected override bool IntroReliesOnTrack => true;
protected override IntroScreen CreateScreen() => new IntroTriangles();
}
}

View File

@ -10,6 +10,7 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneIntroWelcome : IntroTestScene
{
protected override bool IntroReliesOnTrack => false;
protected override IntroScreen CreateScreen() => new IntroWelcome();
public override void TestPlayIntro()

View File

@ -60,8 +60,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0);
InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0);
});
AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));

View File

@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void load()
{
importedSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely();
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
importedBeatmapId = importedBeatmap.OnlineID;
}
@ -347,19 +347,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true)
/// <summary>
/// Tests spectating with a gameplay start time set to a negative value.
/// Simulating beatmaps with high <see cref="BeatmapInfo.AudioLeadIn"/> or negative time storyboard elements.
/// </summary>
[Test]
public void TestNegativeGameplayStartTime()
{
AddStep("load screen", () =>
start(PLAYER_1_ID);
loadSpectateScreen(false, -500);
// to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay().
// (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete)
AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100));
AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded);
AddWaitStep("wait for progression", 3);
assertNotCatchingUp(PLAYER_1_ID);
assertRunning(PLAYER_1_ID);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null)
{
AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () =>
{
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
Ruleset.Value = importedBeatmap.Ruleset;
LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray()));
LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime));
});
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
}
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
private void start(int[] userIds, int? beatmapId = null)
{
AddStep("start play", () =>
@ -419,6 +444,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void assertMuted(int userId, bool muted)
=> AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted);
private void assertRunning(int userId)
=> AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning);
private void assertNotCatchingUp(int userId)
=> AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp);
private void waitForCatchup(int userId)
=> AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp);
@ -429,5 +460,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId);
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
private class TestMultiSpectatorScreen : MultiSpectatorScreen
{
private readonly double? gameplayStartTime;
public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null)
: base(users)
{
this.gameplayStartTime = gameplayStartTime;
}
protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap)
=> new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue);
}
}
}

View File

@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -372,7 +372,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -392,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -414,7 +414,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -453,7 +453,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -492,7 +492,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -531,7 +531,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -565,7 +565,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -605,7 +605,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -625,7 +625,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
AllowedMods = { new OsuModHidden() }
}
@ -665,7 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -696,7 +696,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -714,7 +714,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem
{
ID = 2,
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
});
});
@ -742,7 +742,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -778,7 +778,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -817,7 +817,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -828,7 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
{
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID
})));
AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2);
@ -848,7 +848,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -859,7 +859,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
{
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID
})));
AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2);

View File

@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
BeatmapInfo selectedBeatmap = null;
AddStep("select beatmap",
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.RulesetID == new OsuRuleset().LegacyID).ElementAt(1)));
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1)));
AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
AddStep("exit song select", () => songSelect.Exit());
@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
AddStep("select beatmap",
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.RulesetID == new TaikoRuleset().LegacyID)));
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID)));
AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() });

View File

@ -15,8 +15,12 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
@ -77,6 +81,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => RoomJoined);
}
[Test]
public void TestTaikoOnlyMod()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new TaikoRuleset().RulesetInfo },
AllowedMods = { new TaikoModSwap() }
});
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
AddStep("select swap mod", () => Client.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() }));
AddUntilStep("participant panel has mod", () => this.ChildrenOfType<ParticipantPanel>().Any(p => p.ChildrenOfType<ModIcon>().Any(m => m.Mod is TaikoModSwap)));
}
[Test]
public void TestSettingValidity()
{

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
});
AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));

View File

@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
});
AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));

View File

@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}

View File

@ -16,6 +16,7 @@ using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Navigation
{
@ -92,6 +93,9 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestFromSongSelect([Values] ScorePresentType type)
{
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
var firstImport = importScore(1);
presentAndConfirm(firstImport, type);
@ -102,6 +106,9 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type)
{
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
var firstImport = importScore(1);
presentAndConfirm(firstImport, type);

View File

@ -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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -10,16 +11,19 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Screens.Select.Options;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
@ -96,35 +100,87 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestRetryFromResults()
{
Player player = null;
ResultsScreen results = null;
var getOriginalPlayer = playToResults();
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
AddStep("attempt to retry", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType<HotkeyRetryOverlay>().First().Action());
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player);
}
Screens.Select.SongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
[Test]
public void TestDeleteAllScoresAfterPlaying()
{
playToResults();
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
ScoreInfo score = null;
LeaderboardScore scorePanel = null;
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score);
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } });
AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find<ScoreInfo>(score.ID)?.DeletePending == false));
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
AddUntilStep("wait for player", () =>
AddStep("show local scores", () => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
AddStep("open options", () => InputManager.Key(Key.F3));
AddStep("choose clear all scores", () => InputManager.Key(Key.Number4));
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
AddUntilStep("wait for dialog", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog != null);
AddStep("confirm deletion", () => InputManager.Key(Key.Number1));
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find<ScoreInfo>(score.ID)?.DeletePending == true));
AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null);
}
[Test]
public void TestDeleteScoreAfterPlaying()
{
playToResults();
ScoreInfo score = null;
LeaderboardScore scorePanel = null;
AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score);
AddAssert("ensure score is databased", () => Game.Realm.Run(r => r.Find<ScoreInfo>(score.ID)?.DeletePending == false));
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
AddStep("show local scores", () => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
AddStep("right click panel", () =>
{
// dismiss any notifications that may appear (ie. muted notification).
clickMouseInCentre();
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
InputManager.MoveMouseTo(scorePanel);
InputManager.Click(MouseButton.Right);
});
AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning);
AddStep("seek to near end", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000));
AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded);
AddStep("attempt to retry", () => results.ChildrenOfType<HotkeyRetryOverlay>().First().Action());
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);
AddStep("click delete", () =>
{
var dropdownItem = Game
.ChildrenOfType<PlayBeatmapDetailArea>().First()
.ChildrenOfType<OsuContextMenu>().First()
.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.ToString() == "Delete");
InputManager.MoveMouseTo(dropdownItem);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
AddUntilStep("wait for dialog", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog != null);
AddStep("confirm deletion", () => InputManager.Key(Key.Number1));
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
AddAssert("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find<ScoreInfo>(score.ID)?.DeletePending == true));
AddUntilStep("wait for score panel removal", () => scorePanel.Parent == null);
}
[TestCase(true)]
@ -432,6 +488,37 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("test dispose doesn't crash", () => Game.Dispose());
}
private Func<Player> playToResults()
{
Player player = null;
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
Screens.Select.SongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } });
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () =>
{
// dismiss any notifications that may appear (ie. muted notification).
clickMouseInCentre();
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
});
AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning);
AddStep("seek to near end", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000));
AddUntilStep("wait for pass", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true);
return () => player;
}
private void clickMouseInCentre()
{
InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre);

View File

@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets;
@ -40,6 +41,36 @@ namespace osu.Game.Tests.Visual.SongSelect
this.rulesets = rulesets;
}
[Test]
public void TestScrollPositionMaintainedOnAdd()
{
loadBeatmaps(count: 1, randomDifficulties: false);
for (int i = 0; i < 10; i++)
{
AddRepeatStep("Add some sets", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo()), 4);
checkSelectionIsCentered();
}
}
[Test]
public void TestScrollPositionMaintainedOnDelete()
{
loadBeatmaps(count: 50, randomDifficulties: false);
for (int i = 0; i < 10; i++)
{
AddRepeatStep("Remove some sets", () =>
carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item)
.OfType<CarouselBeatmapSet>()
.OrderBy(item => item.GetHashCode())
.First(item => item.State.Value != CarouselItemState.Selected && item.Visible).BeatmapSet), 4);
checkSelectionIsCentered();
}
}
[Test]
public void TestManyPanels()
{
@ -570,7 +601,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
BeatmapSetInfo testMixed = null;
createCarousel();
createCarousel(new List<BeatmapSetInfo>());
AddStep("add mixed ruleset beatmapset", () =>
{
@ -586,7 +617,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("filter to ruleset 0", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.RulesetID == 0);
AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 0);
AddStep("remove mixed set", () =>
{
@ -734,22 +765,22 @@ namespace osu.Game.Tests.Visual.SongSelect
{
bool changed = false;
createCarousel(c =>
if (beatmapSets == null)
{
beatmapSets = new List<BeatmapSetInfo>();
for (int i = 1; i <= (count ?? set_count); i++)
{
beatmapSets.Add(randomDifficulties
? TestResources.CreateTestBeatmapSetInfo()
: TestResources.CreateTestBeatmapSetInfo(3));
}
}
createCarousel(beatmapSets, c =>
{
carouselAdjust?.Invoke(c);
if (beatmapSets == null)
{
beatmapSets = new List<BeatmapSetInfo>();
for (int i = 1; i <= (count ?? set_count); i++)
{
beatmapSets.Add(randomDifficulties
? TestResources.CreateTestBeatmapSetInfo()
: TestResources.CreateTestBeatmapSetInfo(3));
}
}
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
carousel.BeatmapSetsChanged = () => changed = true;
carousel.BeatmapSets = beatmapSets;
@ -758,7 +789,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("Wait for load", () => changed);
}
private void createCarousel(Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
private void createCarousel(List<BeatmapSetInfo> beatmapSets, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
{
AddStep("Create carousel", () =>
{
@ -772,6 +803,8 @@ namespace osu.Game.Tests.Visual.SongSelect
carouselAdjust?.Invoke(carousel);
carousel.BeatmapSets = beatmapSets;
(target ?? this).Child = carousel;
});
}
@ -813,6 +846,18 @@ namespace osu.Game.Tests.Visual.SongSelect
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
}
private void checkSelectionIsCentered()
{
AddAssert("Selected panel is centered", () =>
{
return Precision.AlmostEquals(
carousel.ScreenSpaceDrawQuad.Centre,
carousel.Items
.First(i => i.Item.State.Value == CarouselItemState.Selected)
.ScreenSpaceDrawQuad.Centre, 100);
});
}
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
private void nextRandom() =>

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect
public void TestGlobalScoresDisplay()
{
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global);
AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null));
AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo())));
}
[Test]
@ -113,24 +114,18 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestPlaceholderStates()
{
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
}
AddStep("ensure no scores displayed", () => leaderboard.SetScores(null));
[Test]
public void TestBeatmapStates()
{
foreach (BeatmapOnlineStatus status in Enum.GetValues(typeof(BeatmapOnlineStatus)))
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure));
AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn));
AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable));
AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected));
}
private void showPersonalBestWithNullPosition()
{
leaderboard.TopScore = new ScoreInfo
leaderboard.SetScores(leaderboard.Scores, new ScoreInfo
{
Rank = ScoreRank.XH,
Accuracy = 1,
@ -148,12 +143,12 @@ namespace osu.Game.Tests.Visual.SongSelect
FlagName = @"ES",
},
},
};
});
}
private void showPersonalBest()
{
leaderboard.TopScore = new ScoreInfo
leaderboard.SetScores(leaderboard.Scores, new ScoreInfo
{
Position = 999,
Rank = ScoreRank.XH,
@ -172,7 +167,7 @@ namespace osu.Game.Tests.Visual.SongSelect
FlagName = @"ES",
},
},
};
});
}
private void loadMoreScores(Func<BeatmapInfo> beatmapInfo)
@ -202,7 +197,24 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[]
{
new OsuModHidden(),
new OsuModHardRock(),
new OsuModFlashlight
{
FollowDelay = { Value = 200 },
SizeMultiplier = { Value = 5 },
},
new OsuModDifficultyAdjust
{
CircleSize = { Value = 11 },
ApproachRate = { Value = 10 },
OverallDifficulty = { Value = 10 },
DrainRate = { Value = 10 },
ExtendedLimits = { Value = true }
}
},
Ruleset = new OsuRuleset().RulesetInfo,
BeatmapInfo = beatmapInfo,
User = new APIUser
@ -222,7 +234,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
User = new APIUser
@ -242,7 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -263,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -284,7 +296,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -305,7 +317,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 0.9826,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -326,7 +338,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 0.9654,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -347,7 +359,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 0.6025,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -368,7 +380,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 0.5140,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -389,7 +401,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 0.4222,
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
Ruleset = new OsuRuleset().RulesetInfo,
@ -407,21 +419,10 @@ namespace osu.Game.Tests.Visual.SongSelect
};
}
private void showBeatmapWithStatus(BeatmapOnlineStatus status)
{
leaderboard.BeatmapInfo = new BeatmapInfo
{
OnlineID = 1113057,
Status = status,
};
}
private class FailableLeaderboard : BeatmapLeaderboard
{
public void SetRetrievalState(PlaceholderState state)
{
PlaceholderState = state;
}
public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state);
public new void SetScores(IEnumerable<ScoreInfo> scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore);
}
}
}

View File

@ -13,6 +13,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
@ -68,6 +69,8 @@ namespace osu.Game.Tests.Visual.SongSelect
{
Ruleset.Value = new OsuRuleset().RulesetInfo;
Beatmap.SetDefault();
songSelect = null;
});
AddStep("delete all beatmaps", () => manager?.Delete());
@ -325,10 +328,10 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(2);
addRulesetImportStep(2);
addRulesetImportStep(1);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2);
changeRuleset(1);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 1);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1);
changeRuleset(0);
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
@ -341,7 +344,7 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(2);
addRulesetImportStep(2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2);
addRulesetImportStep(0);
addRulesetImportStep(0);
@ -352,7 +355,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select beatmap/ruleset externally", () =>
{
target = manager.GetAllUsableBeatmapSets()
.Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
.Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last();
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0);
Beatmap.Value = manager.GetWorkingBeatmap(target);
@ -371,7 +374,7 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(2);
addRulesetImportStep(2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2);
addRulesetImportStep(0);
addRulesetImportStep(0);
@ -382,7 +385,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select beatmap/ruleset externally", () =>
{
target = manager.GetAllUsableBeatmapSets()
.Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
.Last(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 0)).Beatmaps.Last();
Beatmap.Value = manager.GetWorkingBeatmap(target);
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0);
@ -493,9 +496,9 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select beatmap externally", () =>
{
target = manager.GetAllUsableBeatmapSets()
.First(b => b.Beatmaps.Any(bi => bi.RulesetID == targetRuleset))
.First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == targetRuleset))
.Beatmaps
.First(bi => bi.RulesetID == targetRuleset);
.First(bi => bi.Ruleset.OnlineID == targetRuleset);
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
@ -544,7 +547,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
target = manager
.GetAllUsableBeatmapSets()
.First(b => b.Beatmaps.Any(bi => bi.RulesetID == 1))
.First(b => b.Beatmaps.Any(bi => bi.Ruleset.OnlineID == 1))
.Beatmaps.First();
Beatmap.Value = manager.GetWorkingBeatmap(target);
@ -799,8 +802,8 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestChangeRulesetWhilePresentingScore()
{
BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0);
BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1);
BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0);
BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1);
changeRuleset(0);
@ -831,8 +834,8 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestChangeBeatmapWhilePresentingScore()
{
BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 0);
BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.RulesetID == 1);
BeatmapInfo getPresentBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 0);
BeatmapInfo getSwitchBeatmap() => manager.GetAllUsableBeatmapSets().Where(s => !s.DeletePending).SelectMany(s => s.Beatmaps).First(b => b.Ruleset.OnlineID == 1);
changeRuleset(0);
@ -870,9 +873,16 @@ namespace osu.Game.Tests.Visual.SongSelect
return set.ChildrenOfType<FilterableDifficultyIcon>().ToList().FindIndex(i => i == icon);
}
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
private void addRulesetImportStep(int id)
{
Live<BeatmapSetInfo> imported = null;
AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id));
// This is specifically for cases where the add is happening post song select load.
// For cases where song select is null, the assertions are provided by the load checks.
AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID));
}
private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray()));
private Live<BeatmapSetInfo> importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray()));
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);

View File

@ -128,21 +128,16 @@ namespace osu.Game.Tests.Visual.UserInterface
scoreManager.Undelete(r.All<ScoreInfo>().Where(s => s.DeletePending).ToList());
});
leaderboard.Scores = null;
leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables
leaderboard.BeatmapInfo = beatmapInfo;
leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed
leaderboard.RefetchScores(); // Required in the case that the beatmap hasn't changed
});
[SetUpSteps]
public void SetupSteps()
{
// Ensure the leaderboard has finished async-loading drawables
AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType<LeaderboardScore>().Any());
// Ensure the leaderboard items have finished showing up
AddStep("finish transforms", () => leaderboard.FinishTransforms(true));
AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType<LeaderboardScore>().Any());
}
[Test]

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Music;
using osu.Game.Tests.Resources;
@ -18,11 +19,11 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestScenePlaylistOverlay : OsuManualInputManagerTestScene
{
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
private readonly BindableList<Live<BeatmapSetInfo>> beatmapSets = new BindableList<Live<BeatmapSetInfo>>();
private PlaylistOverlay playlistOverlay;
private BeatmapSetInfo first;
private Live<BeatmapSetInfo> first;
[SetUp]
public void Setup() => Schedule(() =>
@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.UserInterface
for (int i = 0; i < 100; i++)
{
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo());
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged());
}
first = beatmapSets.First();
@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("hold 1st item handle", () =>
{
var handle = this.ChildrenOfType<OsuRearrangeableListItem<BeatmapSetInfo>.PlaylistItemHandle>().First();
var handle = this.ChildrenOfType<OsuRearrangeableListItem<Live<BeatmapSetInfo>>.PlaylistItemHandle>().First();
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
InputManager.PressButton(MouseButton.Left);
});
@ -68,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("drag to 5th", () =>
{
var item = this.ChildrenOfType<PlaylistItem>().ElementAt(4);
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre);
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft);
});
AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first));

View File

@ -47,7 +47,7 @@ namespace osu.Game.Tournament.Components
return;
}
var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0);
var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.OnlineID ?? 0);
var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym);
if (modIcon == null)

View File

@ -61,18 +61,15 @@ namespace osu.Game.Tournament
loadingSpinner.Show();
BracketLoadTask.ContinueWith(t =>
BracketLoadTask.ContinueWith(t => Schedule(() =>
{
if (t.IsFaulted)
{
Schedule(() =>
{
loadingSpinner.Hide();
loadingSpinner.Expire();
loadingSpinner.Hide();
loadingSpinner.Expire();
Logger.Error(t.Exception, "Couldn't load bracket with error");
Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details."));
});
Logger.Error(t.Exception, "Couldn't load bracket with error");
Add(new WarningBox($"Your {BRACKET_FILENAME} file could not be parsed. Please check runtime.log for more details."));
return;
}
@ -143,7 +140,7 @@ namespace osu.Game.Tournament
windowMode.Value = WindowMode.Windowed;
}), true);
});
});
}));
}
}
}

View File

@ -99,11 +99,11 @@ namespace osu.Game.Beatmaps
public bool LetterboxInBreaks { get; set; }
public bool WidescreenStoryboard { get; set; }
public bool WidescreenStoryboard { get; set; } = true;
public bool EpilepsyWarning { get; set; }
public bool SamplesMatchPlaybackRate { get; set; }
public bool SamplesMatchPlaybackRate { get; set; } = true;
public double DistanceSpacing { get; set; }
@ -155,7 +155,6 @@ namespace osu.Game.Beatmaps
[Ignored]
public int RulesetID
{
get => Ruleset.OnlineID;
set
{
if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo))

View File

@ -90,14 +90,7 @@ namespace osu.Game.Beatmaps
{
Beatmaps =
{
new BeatmapInfo
{
Difficulty = new BeatmapDifficulty(),
Ruleset = ruleset,
Metadata = metadata,
WidescreenStoryboard = true,
SamplesMatchPlaybackRate = true,
}
new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata)
}
};

View File

@ -39,14 +39,14 @@ namespace osu.Game.Beatmaps
/// The time in milliseconds to begin playing the track for preview purposes.
/// If -1, the track should begin playing at 40% of its length.
/// </summary>
public int PreviewTime { get; set; }
public int PreviewTime { get; set; } = -1;
public string AudioFile { get; set; } = string.Empty;
public string BackgroundFile { get; set; } = string.Empty;
public BeatmapMetadata(RealmUser? user = null)
{
Author = new RealmUser();
Author = user ?? new RealmUser();
}
[UsedImplicitly] // Realm

View File

@ -30,6 +30,7 @@ namespace osu.Game.Beatmaps.Drawables
{
background = new Box
{
Alpha = 0.9f,
RelativeSizeAxes = Axes.Both
},
new FillFlowContainer

View File

@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
{
this.beatmap = beatmap;
beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.RulesetID).RulesetInfo;
beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo;
if (beatmapId.HasValue)
beatmap.BeatmapInfo.OnlineID = beatmapId.Value;

View File

@ -56,6 +56,8 @@ namespace osu.Game.Beatmaps.Formats
this.beatmap = beatmap;
this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
applyLegacyDefaults(this.beatmap.BeatmapInfo);
base.ParseStreamInto(stream, beatmap);
flushPendingPoints();
@ -70,6 +72,19 @@ namespace osu.Game.Beatmaps.Formats
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty);
}
/// <summary>
/// Some `BeatmapInfo` members have default values that differ from the default values used by stable.
/// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case
/// the legacy default values should be used.
/// This method's intention is to restore those legacy defaults.
/// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29
/// </summary>
private void applyLegacyDefaults(BeatmapInfo beatmapInfo)
{
beatmapInfo.WidescreenStoryboard = false;
beatmapInfo.SamplesMatchPlaybackRate = false;
}
protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_');
protected override void ParseLine(Beatmap beatmap, Section section, string line)
@ -141,9 +156,11 @@ namespace osu.Game.Beatmaps.Formats
break;
case @"Mode":
beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value);
int rulesetID = Parsing.ParseInt(pair.Value);
switch (beatmap.BeatmapInfo.RulesetID)
beatmap.BeatmapInfo.RulesetID = rulesetID;
switch (rulesetID)
{
case 0:
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
@ -294,10 +311,13 @@ namespace osu.Game.Beatmaps.Formats
case @"OverallDifficulty":
difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value);
if (!hasApproachRate)
difficulty.ApproachRate = difficulty.OverallDifficulty;
break;
case @"ApproachRate":
difficulty.ApproachRate = Parsing.ParseFloat(pair.Value);
hasApproachRate = true;
break;
case @"SliderMultiplier":
@ -397,7 +417,7 @@ namespace osu.Game.Beatmaps.Formats
OmitFirstBarLine = omitFirstBarSignature,
};
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
bool isOsuRuleset = beatmap.BeatmapInfo.Ruleset.OnlineID == 0;
// scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments.
if (!isOsuRuleset)
effectPoint.ScrollSpeed = speedMultiplier;
@ -415,6 +435,7 @@ namespace osu.Game.Beatmaps.Formats
private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>();
private readonly HashSet<Type> pendingControlPointTypes = new HashSet<Type>();
private double pendingControlPointsTime;
private bool hasApproachRate;
private void addControlPoint(double time, ControlPoint point, bool timingChange)
{

View File

@ -35,6 +35,8 @@ namespace osu.Game.Beatmaps.Formats
[CanBeNull]
private readonly ISkin skin;
private readonly int onlineRulesetID;
/// <summary>
/// Creates a new <see cref="LegacyBeatmapEncoder"/>.
/// </summary>
@ -45,7 +47,9 @@ namespace osu.Game.Beatmaps.Formats
this.beatmap = beatmap;
this.skin = skin;
if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3)
onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;
if (onlineRulesetID < 0 || onlineRulesetID > 3)
throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap));
}
@ -88,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}"));
writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}"));
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}"));
writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}"));
writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}"));
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}"));
// if (beatmap.BeatmapInfo.UseSkinSprites)
// writer.WriteLine(@"UseSkinSprites: 1");
@ -102,7 +106,7 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(@"EpilepsyWarning: 1");
if (beatmap.BeatmapInfo.CountdownOffset > 0)
writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}"));
if (beatmap.BeatmapInfo.RulesetID == 3)
if (onlineRulesetID == 3)
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
@ -147,7 +151,7 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}"));
// Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER)
writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1
writer.WriteLine(onlineRulesetID == 1
? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}")
: FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}"));
@ -179,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats
SampleControlPoint lastRelevantSamplePoint = null;
DifficultyControlPoint lastRelevantDifficultyPoint = null;
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
bool isOsuRuleset = onlineRulesetID == 0;
// iterate over hitobjects and pull out all required sample and difficulty changes
extractDifficultyControlPoints(beatmap.HitObjects);
@ -318,7 +322,7 @@ namespace osu.Game.Beatmaps.Formats
{
Vector2 position = new Vector2(256, 192);
switch (beatmap.BeatmapInfo.RulesetID)
switch (onlineRulesetID)
{
case 0:
case 2:
@ -372,7 +376,7 @@ namespace osu.Game.Beatmaps.Formats
break;
case IHasDuration _:
if (beatmap.BeatmapInfo.RulesetID == 3)
if (onlineRulesetID == 3)
type |= LegacyHitObjectType.Hold;
else
type |= LegacyHitObjectType.Spinner;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Collections
/// <summary>
/// The beatmaps contained by the collection.
/// </summary>
public readonly BindableList<IBeatmapInfo> Beatmaps = new BindableList<IBeatmapInfo>();
public readonly BindableList<BeatmapInfo> Beatmaps = new BindableList<BeatmapInfo>();
/// <summary>
/// The date when this collection was last modified.

View File

@ -38,7 +38,7 @@ namespace osu.Game.Collections
}
private readonly IBindableList<BeatmapCollection> collections = new BindableList<BeatmapCollection>();
private readonly IBindableList<IBeatmapInfo> beatmaps = new BindableList<IBeatmapInfo>();
private readonly IBindableList<BeatmapInfo> beatmaps = new BindableList<BeatmapInfo>();
private readonly BindableList<CollectionFilterMenuItem> filters = new BindableList<CollectionFilterMenuItem>();
[Resolved(CanBeNull = true)]
@ -196,7 +196,7 @@ namespace osu.Game.Collections
private IBindable<WorkingBeatmap> beatmap { get; set; }
[CanBeNull]
private readonly BindableList<IBeatmapInfo> collectionBeatmaps;
private readonly BindableList<BeatmapInfo> collectionBeatmaps;
[NotNull]
private readonly Bindable<string> collectionName;

View File

@ -50,9 +50,14 @@ namespace osu.Game.Collections
this.storage = storage;
}
[Resolved(canBeNull: true)]
private DatabaseContextFactory efContextFactory { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
efContextFactory?.WaitForMigrationCompletion();
Collections.CollectionChanged += collectionsChanged;
if (storage.Exists(database_backup_name))

Some files were not shown because too many files have changed in this diff Show More