1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 17:33:02 +08:00

Merge branch 'master' into pinned-rooms

This commit is contained in:
Dean Herbert
2025-09-01 16:01:50 +09:00
committed by GitHub
Unverified
118 changed files with 1575 additions and 705 deletions
+1 -3
View File
@@ -148,9 +148,7 @@ jobs:
# https://github.com/dotnet/macios/issues/19157
# https://github.com/actions/runner-images/issues/12758
- name: Use Xcode 16.4
run: |
sudo xcode-select -switch /Applications/Xcode_16.4.app
xcodebuild -downloadPlatform iOS
run: sudo xcode-select -switch /Applications/Xcode_16.4.app
- name: Build
run: dotnet build -c Debug osu.iOS.slnf
+1 -1
View File
@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.808.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.829.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override float DefaultFlashlightSize => 325;
public override float DefaultFlashlightSize => 203.125f;
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override string Acronym => "FF";
public override LocalisableString Description => "The fruits are... floating?";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Cloud;
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override LocalisableString Description => "Dashing by default, slow down!";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Running;
public override IconUsage? Icon => OsuIcon.ModMovingFast;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
private DrawableCatchRuleset drawableRuleset = null!;
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
[TestCase]
public void TestFilterIntersection()
public void TestKeysFilterIntersection()
{
var criteria = new ManiaFilterCriteria();
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
[TestCase]
public void TestInvalidFilters()
public void TestInvalidKeysFilters()
{
var criteria = new ManiaFilterCriteria();
@@ -183,5 +183,132 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6"));
}
[TestCase]
public void TestLnsEqual()
{
var criteria = new ManiaFilterCriteria();
var filterCriteria = new FilterCriteria
{
Ruleset = new ManiaRuleset().RulesetInfo
};
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
BeatmapInfo beatmapInfo1 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 0,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "100");
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 100
};
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "1");
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0.1");
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 1000,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
}
[TestCase]
public void TestLnsGreaterOrEqual()
{
var criteria = new ManiaFilterCriteria();
var filterCriteria = new FilterCriteria
{
Ruleset = new ManiaRuleset().RulesetInfo
};
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
BeatmapInfo beatmapInfo1 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 0,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 0
};
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "100");
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 100
};
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1");
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 100,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0.1");
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
{
TotalObjectCount = 1000,
EndTimeObjectCount = 1
};
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
}
[TestCase]
public void TestLnsNotManiaRuleset()
{
var criteria = new ManiaFilterCriteria();
var filterCriteria = new FilterCriteria
{
Ruleset = new ManiaRuleset().RulesetInfo
};
criteria.TryParseCustomKeywordCriteria("lns", Operator.LessOrEqual, "100");
BeatmapInfo beatmapInfo = new BeatmapInfo
{
TotalObjectCount = 100,
EndTimeObjectCount = 50
};
Assert.False(criteria.Matches(beatmapInfo, filterCriteria));
}
[TestCase]
public void TestInvalidLnsFilters()
{
var criteria = new ManiaFilterCriteria();
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "some text"));
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1some text"));
}
}
}
+23 -1
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.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
@@ -19,12 +20,16 @@ namespace osu.Game.Rulesets.Mania
public class ManiaFilterCriteria : IRulesetFilterCriteria
{
private readonly HashSet<int> includedKeyCounts = Enumerable.Range(1, LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT).ToHashSet();
private FilterCriteria.OptionalRange<float> longNotePercentage;
public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria)
{
int keyCount = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods);
return includedKeyCounts.Contains(keyCount);
bool keyCountMatch = includedKeyCounts.Contains(keyCount);
bool longNotePercentageMatch = !longNotePercentage.HasFilter || (!isConvertedBeatmap(beatmapInfo) && longNotePercentage.IsInRange(calculateLongNotePercentage(beatmapInfo)));
return keyCountMatch && longNotePercentageMatch;
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string strValues)
@@ -84,6 +89,10 @@ namespace osu.Game.Rulesets.Mania
return false;
}
}
case "ln":
case "lns":
return FilterQueryParser.TryUpdateCriteriaRange(ref longNotePercentage, op, strValues);
}
return false;
@@ -103,5 +112,18 @@ namespace osu.Game.Rulesets.Mania
return false;
}
private static bool isConvertedBeatmap(BeatmapInfo beatmapInfo)
{
return !beatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
}
private static float calculateLongNotePercentage(BeatmapInfo beatmapInfo)
{
int holdNotes = beatmapInfo.EndTimeObjectCount;
int totalNotes = Math.Max(1, beatmapInfo.TotalObjectCount);
return holdNotes / (float)totalNotes * 100;
}
}
}
@@ -4,6 +4,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => FontAwesome.Solid.Equals;
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
public override ModType Type => ModType.Conversion;
@@ -4,8 +4,10 @@
using System;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
@@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override string Name => "Cover";
public override string Acronym => "CO";
public override IconUsage? Icon => OsuIcon.ModCover;
public override LocalisableString Description => @"Decrease the playfield's viewing area.";
@@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods;
@@ -13,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages";
public override string Acronym => "DS";
public override LocalisableString Description => @"Double the stages, double the fun!";
public override IconUsage? Icon => OsuIcon.ModDualStages;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
@@ -3,7 +3,9 @@
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
@@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override string Name => "Fade In";
public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModFadeIn;
public override LocalisableString Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
public override bool ValidForFreestyleAsRequiredMod => false;
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
public override IconUsage? Icon => OsuIcon.ModHoldOff;
public override ModType Type => ModType.Conversion;
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "Hold the keys. To the beat.";
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
public override IconUsage? Icon => OsuIcon.ModInvert;
public override ModType Type => ModType.Conversion;
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 1;
public override string Name => "One Key";
public override string Acronym => "1K";
public override IconUsage? Icon => OsuIcon.ModOneKey;
public override LocalisableString Description => @"Play with one key.";
public override bool Ranked => false;
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 10;
public override string Name => "Ten Keys";
public override string Acronym => "10K";
public override IconUsage? Icon => OsuIcon.ModTenKeys;
public override LocalisableString Description => @"Play with ten keys.";
public override bool Ranked => false;
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 2;
public override string Name => "Two Keys";
public override string Acronym => "2K";
public override IconUsage? Icon => OsuIcon.ModTwoKeys;
public override LocalisableString Description => @"Play with two keys.";
public override bool Ranked => false;
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 3;
public override string Name => "Three Keys";
public override string Acronym => "3K";
public override IconUsage? Icon => OsuIcon.ModThreeKeys;
public override LocalisableString Description => @"Play with three keys.";
public override bool Ranked => false;
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 4;
public override string Name => "Four Keys";
public override string Acronym => "4K";
public override IconUsage? Icon => OsuIcon.ModFourKeys;
public override LocalisableString Description => @"Play with four keys.";
}
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 5;
public override string Name => "Five Keys";
public override string Acronym => "5K";
public override IconUsage? Icon => OsuIcon.ModFiveKeys;
public override LocalisableString Description => @"Play with five keys.";
}
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 6;
public override string Name => "Six Keys";
public override string Acronym => "6K";
public override IconUsage? Icon => OsuIcon.ModSixKeys;
public override LocalisableString Description => @"Play with six keys.";
}
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 7;
public override string Name => "Seven Keys";
public override string Acronym => "7K";
public override IconUsage? Icon => OsuIcon.ModSevenKeys;
public override LocalisableString Description => @"Play with seven keys.";
}
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 8;
public override string Name => "Eight Keys";
public override string Acronym => "8K";
public override IconUsage? Icon => OsuIcon.ModEightKeys;
public override LocalisableString Description => @"Play with eight keys.";
}
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 9;
public override string Name => "Nine Keys";
public override string Acronym => "9K";
public override IconUsage? Icon => OsuIcon.ModNineKeys;
public override LocalisableString Description => @"Play with nine keys.";
}
}
@@ -4,8 +4,10 @@
using System;
using System.Linq;
using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 0.9;
public override IconUsage? Icon => OsuIcon.ModNoRelease;
public override ModType Type => ModType.DifficultyReduction;
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
@@ -32,26 +32,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
[Test]
public void TestPlayfieldBasedSize()
{
OsuModFlashlight flashlight;
CreateModTest(new ModTestData
{
Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()],
PassCondition = () =>
{
var flashlightOverlay = Player.DrawableRuleset.Overlays
.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
.First();
// the combo check is here because the flashlight radius decreases for the first time at 100 combo
// and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()`
return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100;
}
});
}
[Test]
public void TestSliderDimsOnlyAfterStartTime()
{
+1 -1
View File
@@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Osu.HUD
if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null)
{
hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition,
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * 0.5f;
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * (inner_portion / 2);
}
else
{
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => @"Alternate";
public override string Acronym => @"AL";
public override LocalisableString Description => @"Don't use the same key twice in a row!";
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
public override IconUsage? Icon => OsuIcon.ModAlternate;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "AD";
public override LocalisableString Description => "Never trust the approach circles...";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
+2 -1
View File
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Play with blinds on your screen.";
public override string Acronym => "BL";
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
public override IconUsage? Icon => OsuIcon.ModBlinds;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
@@ -3,9 +3,11 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
@@ -21,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Bloom";
public override string Acronym => "BM";
public override IconUsage? Icon => OsuIcon.ModBloom;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "The cursor blooms into.. a larger cursor!";
public override double ScoreMultiplier => 1;
@@ -11,7 +11,9 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -34,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModBubbles;
public override ModType Type => ModType.Fun;
// Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect
+2 -1
View File
@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "DF";
public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
public override IconUsage? Icon => OsuIcon.ModDeflate;
public override LocalisableString Description => "Hit them at the right size!";
+2 -1
View File
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Depth";
public override string Acronym => "DP";
public override IconUsage? Icon => FontAwesome.Solid.Cube;
public override IconUsage? Icon => OsuIcon.ModDepth;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "3D. Almost.";
public override double ScoreMultiplier => 1;
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override float DefaultFlashlightSize => 200;
public override float DefaultFlashlightSize => 125;
private OsuFlashlight flashlight = null!;
@@ -4,8 +4,10 @@
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -19,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "FR";
public override IconUsage? Icon => OsuIcon.ModFreezeFrame;
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "Burn the notes into your memory.";
+2 -1
View File
@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "GR";
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
public override IconUsage? Icon => OsuIcon.ModGrow;
public override LocalisableString Description => "Hit them at the right size!";
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Magnetised";
public override string Acronym => "MG";
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
public override IconUsage? Icon => OsuIcon.ModMagnetised;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "No need to chase the circles your cursor is a magnet!";
public override double ScoreMultiplier => 0.5;
@@ -4,10 +4,12 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -23,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Repel";
public override string Acronym => "RP";
public override IconUsage? Icon => OsuIcon.ModRepel;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Hit objects run away!";
public override double ScoreMultiplier => 1;
@@ -3,7 +3,9 @@
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -11,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => @"Single Tap";
public override string Acronym => @"SG";
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"You must only use one key!";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
+2 -1
View File
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spin In";
public override string Acronym => "SI";
public override IconUsage? Icon => FontAwesome.Solid.Undo;
public override IconUsage? Icon => OsuIcon.ModSpinIn;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;
@@ -4,8 +4,10 @@
using System;
using System.Linq;
using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -24,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => @"Strict Tracking";
public override string Acronym => @"ST";
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
public override double ScoreMultiplier => 1.0;
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Target Practice";
public override string Acronym => "TP";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.ModTarget;
public override IconUsage? Icon => OsuIcon.ModTargetPractice;
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 0.1;
@@ -4,7 +4,9 @@
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -18,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Traceable";
public override string Acronym => "TC";
public override IconUsage? Icon => OsuIcon.ModTraceable;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Transform";
public override string Acronym => "TR";
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
public override IconUsage? Icon => OsuIcon.ModTransform;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
+2 -1
View File
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
@@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Wiggle";
public override string Acronym => "WG";
public override IconUsage? Icon => FontAwesome.Solid.Certificate;
public override IconUsage? Icon => OsuIcon.ModWiggle;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
@@ -3,7 +3,10 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.UI;
using osuTK;
@@ -12,6 +15,34 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
{
public partial class TestSceneTaikoModFlashlight : TaikoModTestScene
{
[Test]
public void TestAspectRatios([Values] bool withClassicMod)
{
if (withClassicMod)
CreateModTest(new ModTestData { Mods = new Mod[] { new TaikoModFlashlight(), new TaikoModClassic() }, PassCondition = () => true });
else
CreateModTest(new ModTestData { Mod = new TaikoModFlashlight(), PassCondition = () => true });
AddStep("clear dim", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0.0));
AddStep("reset", () => Stack.FillMode = FillMode.Stretch);
AddStep("set to 16:9", () =>
{
Stack.FillAspectRatio = 16 / 9f;
Stack.FillMode = FillMode.Fit;
});
AddStep("set to 4:3", () =>
{
Stack.FillAspectRatio = 4 / 3f;
Stack.FillMode = FillMode.Fit;
});
AddSliderStep("aspect ratio", 0.01f, 5f, 1f, v =>
{
Stack.FillAspectRatio = v;
Stack.FillMode = FillMode.Fit;
});
}
[TestCase(1f)]
[TestCase(0.5f)]
[TestCase(1.25f)]
@@ -4,6 +4,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.Mods;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Acronym => "CS";
public override double ScoreMultiplier => 0.9;
public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => FontAwesome.Solid.Equals;
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
public override ModType Type => ModType.Conversion;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
@@ -47,28 +47,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
this.taikoPlayfield = taikoPlayfield;
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
FlashlightSize = new Vector2(0, GetSize());
FlashlightSmoothness = 1.4f;
AddLayout(flashlightProperties);
}
/// <summary>
/// Returns the aspect ratio-adjusted size of the flashlight.
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
/// </summary>
/// <param name="size">
/// The size of the flashlight.
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
/// </param>
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
{
return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
}
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
@@ -82,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
ClearTransforms(targetMember: nameof(FlashlightSize));
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
FlashlightSize = new Vector2(0, GetSize());
flashlightProperties.Validate();
}
@@ -5,10 +5,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
@@ -21,6 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Acronym => "SR";
public override double ScoreMultiplier => 0.6;
public override LocalisableString Description => "Simplify tricky rhythms!";
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
public override ModType Type => ModType.DifficultyReduction;
[SettingSource("1/3 to 1/2 conversion", "Converts 1/3 patterns to 1/2 rhythm.")]
@@ -6,9 +6,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Objects;
@@ -24,6 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override string Name => @"Single Tap";
public override string Acronym => @"SG";
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"One key for dons, one key for kats.";
public override double ScoreMultiplier => 1.0;
@@ -3,8 +3,10 @@
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
@@ -16,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Name => "Swap";
public override string Acronym => "SW";
public override LocalisableString Description => @"Dons become kats, kats become dons";
public override IconUsage? Icon => OsuIcon.ModSwap;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
@@ -24,6 +24,14 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected TestReplayPlayer Player = null!;
[Test]
public void TestFailedBeatmapLoad()
{
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo, withHitObjects: false));
AddUntilStep("wait for exit", () => Player.IsCurrentScreen());
}
[Test]
public void TestPauseViaSpace()
{
@@ -28,6 +28,7 @@ using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -302,9 +303,69 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 10);
}
[Test]
[Explicit("Test relies on timing of arriving frames to exercise assertions which doesn't work headless.")]
public void TestMaximisedUserIsAudioSource()
{
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();
// With no frames, the synchronisation state will be TooFarAhead.
// In this state, all players should be muted.
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, true);
// Send frames for both players.
sendFrames(PLAYER_1_ID, 20);
sendFrames(PLAYER_2_ID, 40);
waitUntilRunning(PLAYER_1_ID);
AddStep("maximise player 1", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
waitUntilPaused(PLAYER_1_ID);
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
AddStep("minimise player 1", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
AddStep("maximise player 2", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
waitUntilPaused(PLAYER_2_ID);
sendFrames(PLAYER_1_ID, 60);
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
AddStep("minimise player 2", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
}
[Test]
[FlakyTest]
public void TestMostInSyncUserIsAudioSource()
public void TestMostInSyncUserIsAudioSourceIfNoneMaximised()
{
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();
@@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("beatmap in song select", () =>
{
var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen;
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetInfo bsi && bsi.MatchesOnlineID(getImport()));
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is GroupedBeatmapSet gbs && gbs.BeatmapSet.MatchesOnlineID(getImport()));
});
}
@@ -133,6 +133,8 @@ namespace osu.Game.Tests.Visual.Settings
{
public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>();
public Bindable<Vector2> AreaSize { get; } = new Bindable<Vector2>();
public Bindable<Vector2> OutputAreaOffset { get; } = new Bindable<Vector2>();
public Bindable<Vector2> OutputAreaSize { get; } = new Bindable<Vector2>();
public Bindable<float> Rotation { get; } = new Bindable<float>();
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
];
var results = await runGrouping(GroupMode.None, beatmapSets);
Assert.That(results.Select(r => r.Model).OfType<BeatmapSetInfo>(), Is.EquivalentTo(beatmapSets));
Assert.That(results.Select(r => r.Model).OfType<GroupedBeatmapSet>().Select(groupedSet => groupedSet.BeatmapSet), Is.EquivalentTo(beatmapSets));
Assert.That(results.Select(r => r.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(allBeatmaps));
assertTotal(results, beatmapSets.Count + allBeatmaps.Length);
}
@@ -74,11 +74,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyBeatmap('_'), beatmapSets, out var underscoreBeatmap);
var results = await runGrouping(mode, beatmapSets);
assertGroup(results, 0, "0-9", new[] { fiveBeatmap, fourBeatmap }, ref total);
assertGroup(results, 1, "A", new[] { aBeatmap }, ref total);
assertGroup(results, 2, "F", new[] { fBeatmap }, ref total);
assertGroup(results, 3, "Z", new[] { zBeatmap }, ref total);
assertGroup(results, 4, "Other", new[] { dashBeatmap, underscoreBeatmap }, ref total);
assertGroup(results, 0, "0-9", fiveBeatmap.Beatmaps.Concat(fourBeatmap.Beatmaps), ref total);
assertGroup(results, 1, "A", aBeatmap.Beatmaps, ref total);
assertGroup(results, 2, "F", fBeatmap.Beatmaps, ref total);
assertGroup(results, 3, "Z", zBeatmap.Beatmaps, ref total);
assertGroup(results, 4, "Other", dashBeatmap.Beatmaps.Concat(underscoreBeatmap.Beatmaps), ref total);
assertTotal(results, total);
}
@@ -115,12 +115,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddMonths(-2).AddDays(-3), beatmapSets, out var twoMonthsAgoBeatmap);
var results = await runGrouping(GroupMode.DateAdded, beatmapSets);
assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total);
assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total);
assertGroup(results, 5, "2 months ago", new[] { twoMonthsAgoBeatmap }, ref total);
assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total);
assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total);
assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total);
assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total);
assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total);
assertGroup(results, 5, "2 months ago", twoMonthsAgoBeatmap.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -139,13 +139,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap);
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total);
assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total);
assertGroup(results, 5, "2 months ago", new[] { twoMonthsBeatmap }, ref total);
assertGroup(results, 6, "Never", new[] { neverBeatmap }, ref total);
assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total);
assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total);
assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total);
assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total);
assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total);
assertGroup(results, 5, "2 months ago", twoMonthsBeatmap.Beatmaps, ref total);
assertGroup(results, 6, "Never", neverBeatmap.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -162,7 +162,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
int total = 0;
assertGroup(results, 0, "Today", new[] { set }, ref total);
assertGroup(results, 0, "Today", [set.Beatmaps[2]], ref total);
assertGroup(results, 1, "Never", [set.Beatmaps[0], set.Beatmaps[1]], ref total);
assertTotal(results, total);
}
@@ -176,8 +177,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
int total = 0;
assertGroup(results, 0, "Over 5 months ago", new[] { overFiveMonthsBeatmap }, ref total);
assertGroup(results, 1, "Never", new[] { neverBeatmap }, ref total);
assertGroup(results, 0, "Over 5 months ago", overFiveMonthsBeatmap.Beatmaps, ref total);
assertGroup(results, 1, "Never", neverBeatmap.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -207,14 +208,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.LocallyModified, beatmapSets, out var localBeatmap);
var results = await runGrouping(GroupMode.RankedStatus, beatmapSets);
assertGroup(results, 0, "Ranked", new[] { rankedBeatmap, approvedBeatmap }, ref total);
assertGroup(results, 1, "Qualified", new[] { qualifiedBeatmap }, ref total);
assertGroup(results, 2, "WIP", new[] { wipBeatmap }, ref total);
assertGroup(results, 3, "Pending", new[] { pendingBeatmap }, ref total);
assertGroup(results, 4, "Graveyard", new[] { graveyardBeatmap }, ref total);
assertGroup(results, 5, "Local", new[] { localBeatmap }, ref total);
assertGroup(results, 6, "Unknown", new[] { noneBeatmap }, ref total);
assertGroup(results, 7, "Loved", new[] { lovedBeatmap }, ref total);
assertGroup(results, 0, "Ranked", rankedBeatmap.Beatmaps.Concat(approvedBeatmap.Beatmaps), ref total);
assertGroup(results, 1, "Qualified", qualifiedBeatmap.Beatmaps, ref total);
assertGroup(results, 2, "WIP", wipBeatmap.Beatmaps, ref total);
assertGroup(results, 3, "Pending", pendingBeatmap.Beatmaps, ref total);
assertGroup(results, 4, "Graveyard", graveyardBeatmap.Beatmaps, ref total);
assertGroup(results, 5, "Local", localBeatmap.Beatmaps, ref total);
assertGroup(results, 6, "Unknown", noneBeatmap.Beatmaps, ref total);
assertGroup(results, 7, "Loved", lovedBeatmap.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -240,12 +241,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyBPM(330), beatmapSets, out var beatmap330);
var results = await runGrouping(GroupMode.BPM, beatmapSets);
assertGroup(results, 0, "Under 60 BPM", new[] { beatmap30 }, ref total);
assertGroup(results, 1, "60 - 70 BPM", new[] { beatmap59, beatmap60 }, ref total);
assertGroup(results, 2, "90 - 100 BPM", new[] { beatmap90, beatmap95 }, ref total);
assertGroup(results, 3, "270 - 280 BPM", new[] { beatmap269, beatmap270 }, ref total);
assertGroup(results, 4, "290 - 300 BPM", new[] { beatmap299 }, ref total);
assertGroup(results, 5, "Over 300 BPM", new[] { beatmap300, beatmap330 }, ref total);
assertGroup(results, 0, "Under 60 BPM", beatmap30.Beatmaps, ref total);
assertGroup(results, 1, "60 - 70 BPM", (beatmap59.Beatmaps.Concat(beatmap60.Beatmaps)), ref total);
assertGroup(results, 2, "90 - 100 BPM", (beatmap90.Beatmaps.Concat(beatmap95.Beatmaps)), ref total);
assertGroup(results, 3, "270 - 280 BPM", (beatmap269.Beatmaps.Concat(beatmap270.Beatmaps)), ref total);
assertGroup(results, 4, "290 - 300 BPM", beatmap299.Beatmaps, ref total);
assertGroup(results, 5, "Over 300 BPM", (beatmap300.Beatmaps.Concat(beatmap330.Beatmaps)), ref total);
assertTotal(results, total);
}
@@ -272,10 +273,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7);
var results = await runGrouping(GroupMode.Difficulty, beatmapSets);
assertGroup(results, 0, "Below 1 Star", new[] { beatmapBelow1 }, ref total);
assertGroup(results, 1, "1 Star", new[] { beatmapAbove1, beatmapAlmost2 }, ref total);
assertGroup(results, 2, "2 Stars", new[] { beatmap2, beatmapAbove2 }, ref total);
assertGroup(results, 3, "7 Stars", new[] { beatmap7 }, ref total);
assertGroup(results, 0, "Below 1 Star", beatmapBelow1.Beatmaps, ref total);
assertGroup(results, 1, "1 Star", (beatmapAbove1.Beatmaps.Concat(beatmapAlmost2.Beatmaps)), ref total);
assertGroup(results, 2, "2 Stars", (beatmap2.Beatmaps.Concat(beatmapAbove2.Beatmaps)), ref total);
assertGroup(results, 3, "7 Stars", beatmap7.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -304,11 +305,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyLength(630_000), beatmapSets, out var beatmap10Min30Sec);
var results = await runGrouping(GroupMode.Length, beatmapSets);
assertGroup(results, 0, "1 minute or less", new[] { beatmap30Sec, beatmap1Min }, ref total);
assertGroup(results, 1, "2 minutes or less", new[] { beatmap1Min30Sec, beatmap2Min }, ref total);
assertGroup(results, 2, "5 minutes or less", new[] { beatmap5Min }, ref total);
assertGroup(results, 3, "10 minutes or less", new[] { beatmap6Min, beatmap10Min }, ref total);
assertGroup(results, 4, "Over 10 minutes", new[] { beatmap10Min30Sec }, ref total);
assertGroup(results, 0, "1 minute or less", (beatmap30Sec.Beatmaps.Concat(beatmap1Min.Beatmaps)), ref total);
assertGroup(results, 1, "2 minutes or less", (beatmap1Min30Sec.Beatmaps.Concat(beatmap2Min.Beatmaps)), ref total);
assertGroup(results, 2, "5 minutes or less", beatmap5Min.Beatmaps, ref total);
assertGroup(results, 3, "10 minutes or less", (beatmap6Min.Beatmaps.Concat(beatmap10Min.Beatmaps)), ref total);
assertGroup(results, 4, "Over 10 minutes", beatmap10Min30Sec.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -334,10 +335,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.DateRanked = null, beatmapSets, out var beatmapUnranked);
var results = await runGrouping(GroupMode.DateRanked, beatmapSets);
assertGroup(results, 0, "2025", new[] { beatmap2025 }, ref total);
assertGroup(results, 1, "2010", new[] { beatmap2010 }, ref total);
assertGroup(results, 2, "2007", new[] { beatmapOct2007, beatmapDec2007 }, ref total);
assertGroup(results, 3, "Unranked", new[] { beatmapUnranked }, ref total);
assertGroup(results, 0, "2025", beatmap2025.Beatmaps, ref total);
assertGroup(results, 1, "2010", beatmap2010.Beatmaps, ref total);
assertGroup(results, 2, "2007", (beatmapOct2007.Beatmaps.Concat(beatmapDec2007.Beatmaps)), ref total);
assertGroup(results, 3, "Unranked", beatmapUnranked.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -357,9 +358,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced);
var results = await runGrouping(GroupMode.Source, beatmapSets);
assertGroup(results, 0, "Cool Game", new[] { beatmapCoolGame, beatmapCoolGameB }, ref total);
assertGroup(results, 1, "Nice Movie", new[] { beatmapNiceMovie }, ref total);
assertGroup(results, 2, "Unsourced", new[] { beatmapUnsourced }, ref total);
assertGroup(results, 0, "Cool Game", (beatmapCoolGame.Beatmaps.Concat(beatmapCoolGameB.Beatmaps)), ref total);
assertGroup(results, 1, "Nice Movie", beatmapNiceMovie.Beatmaps, ref total);
assertGroup(results, 2, "Unsourced", beatmapUnsourced.Beatmaps, ref total);
assertTotal(results, total);
}
@@ -375,7 +376,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
}
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapSetInfo> expectedBeatmapSets, ref int totalItems)
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapInfo> expectedBeatmaps, ref int totalItems)
{
var groupItem = items.Where(i => i.Model is GroupDefinition).ElementAtOrDefault(index);
@@ -390,7 +391,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
var groupModel = (GroupDefinition)groupItem.Model;
Assert.That(groupModel.Title, Is.EqualTo(expectedTitle));
Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmapSets.SelectMany(bs => bs.Beatmaps)));
Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmaps));
totalItems += itemsInGroup.Count() + 1;
}
@@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
// Using groupingFilter.SetItems.Count alone doesn't work.
// When sorting by difficulty, there can be more than one set panel for the same set displayed.
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetInfo));
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is GroupedBeatmapSet));
}, () => Is.EqualTo(expected));
}
@@ -440,7 +440,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo;
public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet;
public new BeatmapSetInfo? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
public new GroupedBeatmapSet? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
public new GroupDefinition? ExpandedGroup => base.ExpandedGroup;
public TestBeatmapCarousel()
@@ -22,8 +22,6 @@ using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Select.Filter;
using osu.Game.Screens.SelectV2;
@@ -43,9 +41,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
protected Screens.SelectV2.SongSelect SongSelect { get; private set; } = null!;
protected BeatmapCarousel Carousel => SongSelect.ChildrenOfType<BeatmapCarousel>().Single();
[Cached]
protected readonly ScreenFooter Footer;
[Cached]
private readonly OsuLogo logo;
@@ -72,10 +67,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
State = { Value = Visibility.Visible },
},
Footer = new ScreenFooter
{
BackButtonPressed = () => Stack.CurrentScreen.Exit(),
},
logo = new OsuLogo
{
Alpha = 0f,
@@ -111,14 +102,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
Add(beatmapStore);
}
protected override void LoadComplete()
{
base.LoadComplete();
Stack.ScreenPushed += updateFooter;
Stack.ScreenExited += updateFooter;
}
public override void SetUpSteps()
{
base.SetUpSteps();
@@ -207,38 +190,5 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}
protected void WaitForSuspension() => AddUntilStep("wait for not current", () => !SongSelect.AsNonNull().IsCurrentScreen());
private void updateFooter(IScreen? _, IScreen? newScreen)
{
if (newScreen is OsuScreen osuScreen && osuScreen.ShowFooter)
{
Footer.Show();
if (osuScreen.IsLoaded)
updateFooterButtons();
else
{
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
osuScreen.OnLoadComplete += _ => updateFooterButtons();
}
void updateFooterButtons()
{
var buttons = osuScreen.CreateFooterButtons();
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
Footer.SetButtons(buttons);
Footer.Show();
}
}
else
{
Footer.Hide();
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
}
}
}
}
@@ -396,7 +396,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
SelectNextPanel();
AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
AddAssert("keyboard selected is first set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.First()));
}
[Test]
@@ -413,7 +415,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
SelectPrevPanel();
AddAssert("keyboard selected is last set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
AddAssert("keyboard selected is last set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.Last()));
}
[Test]
@@ -428,7 +432,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter last set away", c => c.SearchText = BeatmapSets.First().Metadata.Title);
SelectPrevPanel();
AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
AddAssert("keyboard selected is first set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.First()));
}
[Test]
@@ -444,7 +450,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
// Single result is automatically selected for us, so we iterate once backwards to the set header.
SelectPrevPanel();
AddAssert("keyboard selected is second set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
AddAssert("keyboard selected is second set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.Last()));
}
}
}
@@ -0,0 +1,120 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select.Filter;
using osu.Game.Screens.SelectV2;
namespace osu.Game.Tests.Visual.SongSelectV2
{
[TestFixture]
public partial class TestSceneBeatmapCarouselSetsSplitApart : BeatmapCarouselTestScene
{
[SetUpSteps]
public void SetUpSteps()
{
RemoveAllBeatmaps();
CreateCarousel();
SortAndGroupBy(SortMode.Title, GroupMode.Length);
}
[Test]
public void TestSetTraversal()
{
AddBeatmaps(3, splitApart: true);
AddBeatmaps(3, splitApart: false);
WaitForDrawablePanels();
SelectNextSet();
WaitForSetSelection(set: 0, diff: 0);
SelectNextSet();
WaitForSetSelection(set: 1, diff: 0);
SelectPrevSet();
WaitForSetSelection(set: 0, diff: 0);
SelectPrevSet();
WaitForSetSelection(set: 5, diff: 0);
SelectPrevSet();
SelectPrevSet();
SelectPrevSet();
WaitForSetSelection(set: 2, diff: 4);
AddAssert("only two beatmap panels visible", () => GetVisiblePanels<PanelBeatmap>().Count(), () => Is.EqualTo(2));
}
[Test]
public void TestBeatmapTraversal()
{
AddBeatmaps(3, splitApart: true);
AddBeatmaps(3, splitApart: false);
WaitForDrawablePanels();
SelectNextSet();
WaitForSetSelection(set: 0, diff: 0);
SelectNextPanel();
WaitForSetSelection(set: 0, diff: 1);
SelectNextPanel(); // header of set 1 in group 0
Select();
WaitForSetSelection(set: 1, diff: 0);
SelectPrevPanel(); // header of set 1 in group 0
SelectPrevPanel(); // header of set 0 in group 0
Select();
WaitForSetSelection(set: 0, diff: 0);
SelectPrevPanel(); // header of set 0 in group 0
SelectPrevPanel(); // header of group 0
SelectPrevPanel(); // header of group 2
Select();
SelectNextPanel(); // header of set 0 in group 2
Select();
WaitForSetSelection(set: 0, diff: 4);
}
[Test]
public void TestRandomStaysInGroup()
{
AddBeatmaps(2, splitApart: false);
AddBeatmaps(1, splitApart: true);
WaitForDrawablePanels();
SelectPrevSet();
SelectPrevSet();
WaitForSetSelection(set: 1);
WaitForExpandedGroup(2);
AddStep("select next random", () => Carousel.NextRandom());
WaitForExpandedGroup(2);
AddStep("select next random", () => Carousel.NextRandom());
WaitForExpandedGroup(2);
}
protected void AddBeatmaps(int count, bool splitApart) => AddStep($"add {count} beatmaps ({(splitApart ? "" : "not ")}split apart)", () =>
{
var beatmapSets = new List<BeatmapSetInfo>();
for (int i = 0; i < count; i++)
{
var beatmapSet = CreateTestBeatmapSetInfo(6, false);
for (int j = 0; j < beatmapSet.Beatmaps.Count; j++)
{
beatmapSet.Beatmaps[j].Length = splitApart ? 30_000 * (j + 1) : 180_000;
}
beatmapSets.Add(beatmapSet);
}
BeatmapSets.AddRange(beatmapSets);
});
}
}
@@ -22,12 +22,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
private BeatmapSetInfo baseTestBeatmap = null!;
private const int initial_filter_count = 3;
[SetUpSteps]
public void SetUpSteps()
{
RemoveAllBeatmaps();
CreateCarousel();
WaitForFiltering();
AddBeatmaps(1, 3);
WaitForFiltering();
AddStep("generate and add test beatmap", () =>
{
baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3);
@@ -42,8 +46,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
b.Metadata = metadata;
BeatmapSets.Add(baseTestBeatmap);
});
WaitForFiltering();
AddAssert("filter count correct", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
}
[Test]
@@ -81,12 +86,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddUntilStep("is scrolled to end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
updateBeatmap(b => b.Metadata = new BeatmapMetadata
updateBeatmap(b =>
{
Artist = "updated test",
Title = $"beatmap {RNG.Next().ToString()}"
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
b.Hash = "new hash";
b.Metadata = new BeatmapMetadata
{
Artist = "updated test",
Title = $"beatmap {RNG.Next().ToString()}"
};
});
assertDidFilter();
WaitForFiltering();
AddAssert("scroll is still at end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
@@ -113,8 +124,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddStep("find panel", () => panel = Carousel.ChildrenOfType<PanelBeatmapSet>().Single(p => p.ChildrenOfType<OsuSpriteText>().Any(t => t.Text.ToString() == "beatmap")));
updateBeatmap(b => b.Metadata = metadata);
updateBeatmap(b =>
{
b.Metadata = metadata;
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
b.Hash = "new hash";
});
assertDidFilter();
WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
@@ -123,7 +140,41 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}
[Test]
public void TestSelectionHeld()
public void TestOnlineStatusUpdated()
{
List<Panel> originalDrawables = new List<Panel>();
AddStep("store drawable references", () =>
{
originalDrawables.Clear();
originalDrawables.AddRange(Carousel.ChildrenOfType<Panel>().ToList());
});
updateBeatmap(b => b.Status = BeatmapOnlineStatus.Graveyard);
assertDidFilter();
WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
}
[Test]
public void TestNoUpdateTriggeredOnUserTagsChange()
{
var metadata = new BeatmapMetadata
{
Artist = "updated test",
Title = "new beatmap title",
UserTags = { "hi" }
};
updateBeatmap(b => b.Metadata = metadata);
assertDidNotFilter();
}
[TestCase(false)]
[TestCase(true)]
public void TestSelectionHeld(bool hashChanged)
{
SelectNextSet();
@@ -131,7 +182,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap();
updateBeatmap(b =>
{
if (hashChanged)
b.Hash = "new hash";
});
if (hashChanged)
assertDidFilter();
else
assertDidNotFilter();
WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -148,6 +209,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => b.DifficultyName = "new name");
assertDidFilter();
WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -164,6 +226,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => b.OnlineID = b.OnlineID + 1);
assertDidFilter();
WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -339,6 +402,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("Order didn't change", () => Carousel.PostFilterBeatmaps.Select(b => b.ID), () => Is.EqualTo(originalOrder));
}
private void assertDidFilter() => AddAssert("did filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count + 1));
private void assertDidNotFilter() => AddAssert("did not filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
private void updateBeatmap(Action<BeatmapInfo>? updateBeatmap = null, Action<BeatmapSetInfo>? updateSet = null)
{
AddStep("update beatmap with different reference", () =>
@@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
@@ -42,7 +43,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private DialogOverlay dialogOverlay = null!;
private LeaderboardManager leaderboardManager = null!;
private RealmPopulatingOnlineLookupSource lookupSource = null!;
private readonly IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -52,7 +54,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
dependencies.Cache(leaderboardManager = new LeaderboardManager());
dependencies.Cache(lookupSource = new RealmPopulatingOnlineLookupSource());
dependencies.CacheAs(onlineLookupResult);
Dependencies.Cache(Realm);
@@ -68,7 +70,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
LoadComponent(leaderboardManager);
LoadComponent(lookupSource);
Child = contentContainer = new OsuContextMenuContainer
{
@@ -4,13 +4,12 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.SelectV2;
@@ -18,64 +17,25 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
public partial class TestSceneBeatmapMetadataWedge : SongSelectComponentsTestScene
{
private APIBeatmapSet? currentOnlineSet;
private BeatmapMetadataWedge wedge = null!;
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))]
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
protected override void LoadComplete()
{
base.LoadComplete();
var lookupSource = new RealmPopulatingOnlineLookupSource();
Child = new DependencyProvidingContainer
Child = wedge = new BeatmapMetadataWedge
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = [(typeof(RealmPopulatingOnlineLookupSource), lookupSource)],
Children =
[
lookupSource,
wedge = new BeatmapMetadataWedge
{
State = { Value = Visibility.Visible },
}
]
State = { Value = Visibility.Visible },
};
}
[SetUpSteps]
public override void SetUpSteps()
{
AddStep("register request handling", () =>
{
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
default:
return false;
}
};
});
}
[Test]
public void TestShowHide()
{
AddStep("all metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
AddStep("hide wedge", () => wedge.Hide());
AddStep("show wedge", () => wedge.Show());
@@ -84,67 +44,63 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestVariousMetrics()
{
AddStep("all metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
AddStep("null beatmap", () => Beatmap.SetDefault());
AddStep("no source", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
working.Metadata.Source = string.Empty;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no success rate", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().PlayCount = 0;
onlineSet.Beatmaps.Single().PassCount = 0;
online.Result!.Beatmaps.Single().PlayCount = 0;
online.Result!.Beatmaps.Single().PassCount = 0;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no user ratings", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Ratings = Array.Empty<int>();
online.Result!.Ratings = Array.Empty<int>();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no fail times", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().FailTimes = null;
online.Result!.Beatmaps.Single().FailTimes = null;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Ratings = Array.Empty<int>();
onlineSet.Beatmaps.Single().FailTimes = null;
online.Result!.Ratings = Array.Empty<int>();
online.Result!.Beatmaps.Single().FailTimes = null;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("local beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, _) = createTestBeatmap();
working.BeatmapInfo.OnlineID = 0;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = null;
Beatmap.Value = working;
});
}
@@ -154,16 +110,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
AddStep("long text", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
working.BeatmapInfo.Metadata.Author = new RealmUser { Username = "Verrrrryyyy llooonngggggg author" };
working.BeatmapInfo.Metadata.Source = "Verrrrryyyy llooonngggggg source";
working.BeatmapInfo.Metadata.Tags = string.Join(' ', Enumerable.Repeat(working.BeatmapInfo.Metadata.Tags, 3));
onlineSet.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
onlineSet.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
onlineSet.Beatmaps.Single().TopTags = Enumerable.Repeat(onlineSet.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
online.Result!.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
online.Result!.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
online.Result!.Beatmaps.Single().TopTags = Enumerable.Repeat(online.Result!.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
}
@@ -171,22 +127,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestOnlineAvailability()
{
AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("rating wedge visible", () => wedge.RatingsVisible);
AddUntilStep("fail time wedge visible", () => wedge.FailRetryVisible);
AddStep("online beatmapset with local diff", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, lookupResult) = createTestBeatmap();
working.BeatmapInfo.ResetOnlineInfo();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = lookupResult;
Beatmap.Value = working;
});
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
@@ -195,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
var (working, _) = createTestBeatmap();
currentOnlineSet = null;
onlineLookupResult.Value = null;
Beatmap.Value = working;
});
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
@@ -205,21 +156,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestUserTags()
{
AddStep("user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
AddStep("user tags", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("no user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().TopTags = null;
onlineSet.RelatedTags = null;
online.Result!.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
}
@@ -227,72 +174,60 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestLoading()
{
AddStep("override request handling", () =>
{
currentOnlineSet = null;
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
Scheduler.AddDelayed(() => set.TriggerSuccess(currentOnlineSet!), 500);
return true;
default:
return false;
}
};
});
AddStep("set beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
AddStep("set beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.RelatedTags![0].Name = "other/tag";
onlineSet.RelatedTags[1].Name = "another/tag";
onlineSet.RelatedTags[2].Name = "some/tag";
online.Result!.RelatedTags![0].Name = "other/tag";
online.Result!.RelatedTags[1].Name = "another/tag";
online.Result!.RelatedTags[2].Name = "some/tag";
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
AddStep("no user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().TopTags = null;
onlineSet.RelatedTags = null;
online.Result!.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
AddStep("no user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().TopTags = null;
onlineSet.RelatedTags = null;
online.Result!.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
}
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap()
{
var working = CreateWorkingBeatmap(Ruleset.Value);
var onlineSet = new APIBeatmapSet
@@ -346,7 +281,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
return (working, onlineSet);
working.Metadata.UserTags.AddRange(onlineSet.RelatedTags.Select(t => t.Name));
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
}
}
}
@@ -11,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
@@ -41,10 +42,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private BeatmapTitleWedge titleWedge = null!;
private BeatmapTitleWedge.DifficultyDisplay difficultyDisplay => titleWedge.ChildrenOfType<BeatmapTitleWedge.DifficultyDisplay>().Single();
private APIBeatmapSet? currentOnlineSet;
[Cached]
private RealmPopulatingOnlineLookupSource lookupSource = new RealmPopulatingOnlineLookupSource();
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))]
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
@@ -58,7 +57,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddRange(new Drawable[]
{
lookupSource,
new Container
{
RelativeSizeAxes = Axes.Both,
@@ -142,44 +140,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestOnlineAvailability()
{
AddStep("set up request handler", () =>
{
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
return false;
default:
return false;
}
};
});
AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("play count is 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("10,000"));
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
AddStep("online beatmapset with local diff", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, lookupResult) = createTestBeatmap();
working.BeatmapInfo.ResetOnlineInfo();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
onlineLookupResult.Value = lookupResult;
});
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-"));
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
@@ -187,8 +159,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
var (working, _) = createTestBeatmap();
currentOnlineSet = null;
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
});
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-"));
AddUntilStep("favourites count is -", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("-"));
@@ -205,15 +177,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
case PostBeatmapFavouriteRequest favourite:
Task.Run(() =>
{
@@ -228,13 +191,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
};
});
AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("play count is 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("10,000"));
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
@@ -251,13 +209,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
AddStep("change to another beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
onlineSet.FavouriteCount = 9999;
onlineSet.HasFavourited = true;
working.BeatmapSetInfo.OnlineID = onlineSet.OnlineID = 99999;
var (working, online) = createTestBeatmap();
online.Result!.FavouriteCount = 9999;
online.Result!.HasFavourited = true;
working.BeatmapSetInfo.OnlineID = online.Result!.OnlineID = 99999;
currentOnlineSet = onlineSet;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("allow request to complete", () => resetEvent.Set());
AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999"));
@@ -268,15 +226,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
case PostBeatmapFavouriteRequest favourite:
Task.Run(() =>
{
@@ -350,7 +299,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
}
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap()
{
var working = CreateWorkingBeatmap(Ruleset.Value);
var onlineSet = new APIBeatmapSet
@@ -371,7 +320,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
return (working, onlineSet);
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
}
private class TestHitObject : ConvertHitObject;
@@ -75,21 +75,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
new PanelBeatmapSet
{
Item = new CarouselItem(beatmapSet)
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet))
},
new PanelBeatmapSet
{
Item = new CarouselItem(beatmapSet),
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
KeyboardSelected = { Value = true }
},
new PanelBeatmapSet
{
Item = new CarouselItem(beatmapSet),
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
Expanded = { Value = true }
},
new PanelBeatmapSet
{
Item = new CarouselItem(beatmapSet),
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
KeyboardSelected = { Value = true },
Expanded = { Value = true }
},
@@ -23,7 +23,9 @@ using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Screens.SelectV2;
using osu.Game.Tests.Resources;
using osuTK.Input;
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods;
using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions;
using FooterButtonRandom = osu.Game.Screens.SelectV2.FooterButtonRandom;
@@ -302,6 +304,28 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
}
/// <summary>
/// Last played and rank achieved may have changed, so we want to make sure filtering runs on resume to song select.
/// </summary>
[Test]
public void TestFilteringRunsAfterReturningFromGameplay()
{
AddStep("import actual beatmap", () => Beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
LoadSongSelect();
AddUntilStep("wait for filtered", () => SongSelect.ChildrenOfType<BeatmapCarousel>().Single().FilterCount, () => Is.EqualTo(1));
AddStep("enter gameplay", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
AddUntilStep("wait for fail", () => ((Player)Stack.CurrentScreen).GameplayState.HasFailed);
AddStep("exit gameplay", () => InputManager.Key(Key.Escape));
AddStep("exit gameplay", () => InputManager.Key(Key.Escape));
AddUntilStep("wait for filtered", () => SongSelect.ChildrenOfType<BeatmapCarousel>().Single().FilterCount, () => Is.EqualTo(2));
}
[Test]
public void TestAutoplayShortcut()
{
@@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("no-collection group present", () =>
{
var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection");
return group.Value.Select(i => i.Model).OfType<BeatmapSetInfo>().Single().Equals(beatmapSet);
return group.Value.Select(i => i.Model).OfType<GroupedBeatmapSet>().Single().BeatmapSet.Equals(beatmapSet);
});
AddStep("add beatmap to collection", () =>
@@ -4,11 +4,13 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.UserInterface
private FillFlowContainer spreadOutFlow = null!;
private ModDisplay modDisplay = null!;
[Resolved]
private RulesetStore rulesetStore { get; set; } = null!;
[SetUpSteps]
public void SetUpSteps()
{
@@ -70,9 +75,26 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestShowAllMods()
{
AddStep("create mod icons", () =>
createModIconsForRuleset(0);
createModIconsForRuleset(1);
createModIconsForRuleset(2);
createModIconsForRuleset(3);
AddStep("toggle selected", () =>
{
addRange(Ruleset.Value.CreateInstance().CreateAllMods().Select(m =>
foreach (var icon in this.ChildrenOfType<ModIcon>())
icon.Selected.Toggle();
});
}
private void createModIconsForRuleset(int rulesetId)
{
AddStep($"create mod icons for ruleset {rulesetId}", () =>
{
spreadOutFlow.Clear();
modDisplay.Current.Value = [];
addRange(rulesetStore.GetRuleset(rulesetId)!.CreateInstance().CreateAllMods().Select(m =>
{
if (m is OsuModFlashlight fl)
fl.FollowDelay.Value = 1245;
@@ -89,12 +111,6 @@ namespace osu.Game.Tests.Visual.UserInterface
return m;
}));
});
AddStep("toggle selected", () =>
{
foreach (var icon in this.ChildrenOfType<ModIcon>())
icon.Selected.Toggle();
});
}
[Test]
@@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Screens.Gameplay;
@@ -66,6 +67,6 @@ namespace osu.Game.Tournament.Tests.Screens
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));
private void toggleWarmup()
=> AddStep("toggle warmup", () => this.ChildrenOfType<TourneyButton>().First().TriggerClick());
=> AddStep("toggle warmup", () => this.ChildrenOfType<LabelledSwitchButton>().First().ChildrenOfType<SwitchButton>().First().TriggerClick());
}
}
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
@@ -24,7 +24,6 @@ namespace osu.Game.Tournament.Screens.Gameplay
private readonly BindableBool warmup = new BindableBool();
public readonly Bindable<TourneyState> State = new Bindable<TourneyState>();
private OsuButton warmupButton = null!;
private MatchIPCInfo ipc = null!;
[Resolved]
@@ -40,6 +39,8 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
this.ipc = ipc;
LabelledSwitchButton chatToggle;
AddRangeInternal(new Drawable[]
{
new TourneyVideo("gameplay")
@@ -95,17 +96,14 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
Children = new Drawable[]
{
warmupButton = new TourneyButton
new LabelledSwitchButton
{
RelativeSizeAxes = Axes.X,
Text = "Toggle warmup",
Action = () => warmup.Toggle()
Label = "Warmup",
Current = warmup,
},
new TourneyButton
chatToggle = new LabelledSwitchButton
{
RelativeSizeAxes = Axes.X,
Text = "Toggle chat",
Action = () => { State.Value = State.Value == TourneyState.Idle ? TourneyState.Playing : TourneyState.Idle; }
Label = "Show chat",
},
new SettingsSlider<int>
{
@@ -123,13 +121,12 @@ namespace osu.Game.Tournament.Screens.Gameplay
}
});
State.BindValueChanged(state => chatToggle.Current.Value = State.Value == TourneyState.Idle, true);
chatToggle.Current.BindValueChanged(v => State.Value = v.NewValue ? TourneyState.Idle : TourneyState.Playing);
LadderInfo.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true);
warmup.BindValueChanged(w =>
{
warmupButton.Alpha = !w.NewValue ? 0.5f : 1;
header.ShowScores = !w.NewValue;
}, true);
warmup.BindValueChanged(w => header.ShowScores = !w.NewValue, true);
}
protected override void LoadComplete()
+4 -1
View File
@@ -59,7 +59,10 @@ namespace osu.Game.Beatmaps
// An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting
// high precision times (on windows there's generally only 5-10ms reporting intervals, as an example).
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack);
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack)
{
DriftRecoveryHalfLife = 80,
};
if (applyOffsets)
{
@@ -727,7 +727,7 @@ namespace osu.Game.Database
}
catch (Exception e)
{
Logger.Log(@$"Failed to update ranked/submitted dates for beatmap set {id}: {e}");
Logger.Log(@$"Failed to update user tags for beatmap {id}: {e}");
++failedCount;
}
}
+13 -2
View File
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -79,7 +80,7 @@ namespace osu.Game.Graphics.Carousel
/// <summary>
/// The number of times filter operations have been triggered.
/// </summary>
internal int FilterCount { get; private set; }
public int FilterCount { get; private set; }
/// <summary>
/// The number of displayable items currently being tracked (before filtering).
@@ -210,6 +211,12 @@ namespace osu.Game.Graphics.Carousel
return filterTask;
}
/// <summary>
/// Called when <see cref="Items"/> changes in any way.
/// </summary>
/// <returns>Whether a re-filter is required.</returns>
protected virtual bool HandleItemsChanged(NotifyCollectionChangedEventArgs args) => true;
/// <summary>
/// Fired after a filter operation completed.
/// </summary>
@@ -301,7 +308,11 @@ namespace osu.Game.Graphics.Carousel
RelativeSizeAxes = Axes.Both,
};
Items.BindCollectionChanged((_, _) => filterAfterItemsChanged.Invalidate());
Items.BindCollectionChanged((_, args) =>
{
if (HandleItemsChanged(args))
filterAfterItemsChanged.Invalidate();
});
}
[BackgroundDependencyLoader]
+300 -21
View File
@@ -81,27 +81,6 @@ namespace osu.Game.Graphics
public static IconUsage InsaneMania => get(0xe027);
public static IconUsage ExpertMania => get(0xe028);
// mod icons
public static IconUsage ModPerfect => get(0xe049);
public static IconUsage ModAutopilot => get(0xe03a);
public static IconUsage ModAuto => get(0xe03b);
public static IconUsage ModCinema => get(0xe03c);
public static IconUsage ModDoubleTime => get(0xe03d);
public static IconUsage ModEasy => get(0xe03e);
public static IconUsage ModFlashlight => get(0xe03f);
public static IconUsage ModHalftime => get(0xe040);
public static IconUsage ModHardRock => get(0xe041);
public static IconUsage ModHidden => get(0xe042);
public static IconUsage ModNightcore => get(0xe043);
public static IconUsage ModNoFail => get(0xe044);
public static IconUsage ModRelax => get(0xe045);
public static IconUsage ModSpunOut => get(0xe046);
public static IconUsage ModSuddenDeath => get(0xe047);
public static IconUsage ModTarget => get(0xe048);
// Use "Icons/BeatmapDetails/mod-icon" instead
// public static IconUsage ModBg => Get(0xe04a);
#endregion
#region New single-file-based icons
@@ -181,6 +160,88 @@ namespace osu.Game.Graphics
public static IconUsage Tortoise => get(OsuIconMapping.Tortoise);
public static IconUsage Hare => get(OsuIconMapping.Hare);
// mod icons
public static IconUsage ModNoMod => get(OsuIconMapping.ModNoMod);
/*
can be regenerated semi-automatically using osu-web's mod database via
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
sed 's/ //g' | \
awk '{print "public static IconUsage Mod" $0 " => get(OsuIconMapping.Mod" $0 ");"}' | pbcopy
*/
public static IconUsage ModAccuracyChallenge => get(OsuIconMapping.ModAccuracyChallenge);
public static IconUsage ModAdaptiveSpeed => get(OsuIconMapping.ModAdaptiveSpeed);
public static IconUsage ModAlternate => get(OsuIconMapping.ModAlternate);
public static IconUsage ModApproachDifferent => get(OsuIconMapping.ModApproachDifferent);
public static IconUsage ModAutopilot => get(OsuIconMapping.ModAutopilot);
public static IconUsage ModAutoplay => get(OsuIconMapping.ModAutoplay);
public static IconUsage ModBarrelRoll => get(OsuIconMapping.ModBarrelRoll);
public static IconUsage ModBlinds => get(OsuIconMapping.ModBlinds);
public static IconUsage ModBloom => get(OsuIconMapping.ModBloom);
public static IconUsage ModBubbles => get(OsuIconMapping.ModBubbles);
public static IconUsage ModCinema => get(OsuIconMapping.ModCinema);
public static IconUsage ModClassic => get(OsuIconMapping.ModClassic);
public static IconUsage ModConstantSpeed => get(OsuIconMapping.ModConstantSpeed);
public static IconUsage ModCover => get(OsuIconMapping.ModCover);
public static IconUsage ModDaycore => get(OsuIconMapping.ModDaycore);
public static IconUsage ModDeflate => get(OsuIconMapping.ModDeflate);
public static IconUsage ModDepth => get(OsuIconMapping.ModDepth);
public static IconUsage ModDifficultyAdjust => get(OsuIconMapping.ModDifficultyAdjust);
public static IconUsage ModDoubleTime => get(OsuIconMapping.ModDoubleTime);
public static IconUsage ModDualStages => get(OsuIconMapping.ModDualStages);
public static IconUsage ModEasy => get(OsuIconMapping.ModEasy);
public static IconUsage ModEightKeys => get(OsuIconMapping.ModEightKeys);
public static IconUsage ModFadeIn => get(OsuIconMapping.ModFadeIn);
public static IconUsage ModFiveKeys => get(OsuIconMapping.ModFiveKeys);
public static IconUsage ModFlashlight => get(OsuIconMapping.ModFlashlight);
public static IconUsage ModFloatingFruits => get(OsuIconMapping.ModFloatingFruits);
public static IconUsage ModFourKeys => get(OsuIconMapping.ModFourKeys);
public static IconUsage ModFreezeFrame => get(OsuIconMapping.ModFreezeFrame);
public static IconUsage ModGrow => get(OsuIconMapping.ModGrow);
public static IconUsage ModHalfTime => get(OsuIconMapping.ModHalfTime);
public static IconUsage ModHardRock => get(OsuIconMapping.ModHardRock);
public static IconUsage ModHidden => get(OsuIconMapping.ModHidden);
public static IconUsage ModHoldOff => get(OsuIconMapping.ModHoldOff);
public static IconUsage ModInvert => get(OsuIconMapping.ModInvert);
public static IconUsage ModMagnetised => get(OsuIconMapping.ModMagnetised);
public static IconUsage ModMirror => get(OsuIconMapping.ModMirror);
public static IconUsage ModMovingFast => get(OsuIconMapping.ModMovingFast);
public static IconUsage ModMuted => get(OsuIconMapping.ModMuted);
public static IconUsage ModNightcore => get(OsuIconMapping.ModNightcore);
public static IconUsage ModNineKeys => get(OsuIconMapping.ModNineKeys);
public static IconUsage ModNoFail => get(OsuIconMapping.ModNoFail);
public static IconUsage ModNoRelease => get(OsuIconMapping.ModNoRelease);
public static IconUsage ModNoScope => get(OsuIconMapping.ModNoScope);
public static IconUsage ModOneKey => get(OsuIconMapping.ModOneKey);
public static IconUsage ModPerfect => get(OsuIconMapping.ModPerfect);
public static IconUsage ModRandom => get(OsuIconMapping.ModRandom);
public static IconUsage ModRelax => get(OsuIconMapping.ModRelax);
public static IconUsage ModRepel => get(OsuIconMapping.ModRepel);
public static IconUsage ModScoreV2 => get(OsuIconMapping.ModScoreV2);
public static IconUsage ModSevenKeys => get(OsuIconMapping.ModSevenKeys);
public static IconUsage ModSimplifiedRhythm => get(OsuIconMapping.ModSimplifiedRhythm);
public static IconUsage ModSingleTap => get(OsuIconMapping.ModSingleTap);
public static IconUsage ModSixKeys => get(OsuIconMapping.ModSixKeys);
public static IconUsage ModSpinIn => get(OsuIconMapping.ModSpinIn);
public static IconUsage ModSpunOut => get(OsuIconMapping.ModSpunOut);
public static IconUsage ModStrictTracking => get(OsuIconMapping.ModStrictTracking);
public static IconUsage ModSuddenDeath => get(OsuIconMapping.ModSuddenDeath);
public static IconUsage ModSwap => get(OsuIconMapping.ModSwap);
public static IconUsage ModSynesthesia => get(OsuIconMapping.ModSynesthesia);
public static IconUsage ModTargetPractice => get(OsuIconMapping.ModTargetPractice);
public static IconUsage ModTenKeys => get(OsuIconMapping.ModTenKeys);
public static IconUsage ModThreeKeys => get(OsuIconMapping.ModThreeKeys);
public static IconUsage ModTouchDevice => get(OsuIconMapping.ModTouchDevice);
public static IconUsage ModTraceable => get(OsuIconMapping.ModTraceable);
public static IconUsage ModTransform => get(OsuIconMapping.ModTransform);
public static IconUsage ModTwoKeys => get(OsuIconMapping.ModTwoKeys);
public static IconUsage ModWiggle => get(OsuIconMapping.ModWiggle);
public static IconUsage ModWindDown => get(OsuIconMapping.ModWindDown);
public static IconUsage ModWindUp => get(OsuIconMapping.ModWindUp);
private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME);
private enum OsuIconMapping
@@ -400,6 +461,224 @@ namespace osu.Game.Graphics
[Description(@"hare")]
Hare,
// mod icons
[Description(@"Mods/mod-no-mod")]
ModNoMod,
/*
rest can be regenerated semi-automatically using osu-web's mod database via
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
awk '{kebab = $0; gsub(" ", "-", kebab); pascal = $0; gsub(" ", "", pascal); print "[Description(@\"Mods/mod-" tolower(kebab) "\")]\nMod" pascal ",\n" }' | pbcopy
*/
[Description(@"Mods/mod-accuracy-challenge")]
ModAccuracyChallenge,
[Description(@"Mods/mod-adaptive-speed")]
ModAdaptiveSpeed,
[Description(@"Mods/mod-alternate")]
ModAlternate,
[Description(@"Mods/mod-approach-different")]
ModApproachDifferent,
[Description(@"Mods/mod-autopilot")]
ModAutopilot,
[Description(@"Mods/mod-autoplay")]
ModAutoplay,
[Description(@"Mods/mod-barrel-roll")]
ModBarrelRoll,
[Description(@"Mods/mod-blinds")]
ModBlinds,
[Description(@"Mods/mod-bloom")]
ModBloom,
[Description(@"Mods/mod-bubbles")]
ModBubbles,
[Description(@"Mods/mod-cinema")]
ModCinema,
[Description(@"Mods/mod-classic")]
ModClassic,
[Description(@"Mods/mod-constant-speed")]
ModConstantSpeed,
[Description(@"Mods/mod-cover")]
ModCover,
[Description(@"Mods/mod-daycore")]
ModDaycore,
[Description(@"Mods/mod-deflate")]
ModDeflate,
[Description(@"Mods/mod-depth")]
ModDepth,
[Description(@"Mods/mod-difficulty-adjust")]
ModDifficultyAdjust,
[Description(@"Mods/mod-double-time")]
ModDoubleTime,
[Description(@"Mods/mod-dual-stages")]
ModDualStages,
[Description(@"Mods/mod-easy")]
ModEasy,
[Description(@"Mods/mod-eight-keys")]
ModEightKeys,
[Description(@"Mods/mod-fade-in")]
ModFadeIn,
[Description(@"Mods/mod-five-keys")]
ModFiveKeys,
[Description(@"Mods/mod-flashlight")]
ModFlashlight,
[Description(@"Mods/mod-floating-fruits")]
ModFloatingFruits,
[Description(@"Mods/mod-four-keys")]
ModFourKeys,
[Description(@"Mods/mod-freeze-frame")]
ModFreezeFrame,
[Description(@"Mods/mod-grow")]
ModGrow,
[Description(@"Mods/mod-half-time")]
ModHalfTime,
[Description(@"Mods/mod-hard-rock")]
ModHardRock,
[Description(@"Mods/mod-hidden")]
ModHidden,
[Description(@"Mods/mod-hold-off")]
ModHoldOff,
[Description(@"Mods/mod-invert")]
ModInvert,
[Description(@"Mods/mod-magnetised")]
ModMagnetised,
[Description(@"Mods/mod-mirror")]
ModMirror,
[Description(@"Mods/mod-moving-fast")]
ModMovingFast,
[Description(@"Mods/mod-muted")]
ModMuted,
[Description(@"Mods/mod-nightcore")]
ModNightcore,
[Description(@"Mods/mod-nine-keys")]
ModNineKeys,
[Description(@"Mods/mod-no-fail")]
ModNoFail,
[Description(@"Mods/mod-no-release")]
ModNoRelease,
[Description(@"Mods/mod-no-scope")]
ModNoScope,
[Description(@"Mods/mod-one-key")]
ModOneKey,
[Description(@"Mods/mod-perfect")]
ModPerfect,
[Description(@"Mods/mod-random")]
ModRandom,
[Description(@"Mods/mod-relax")]
ModRelax,
[Description(@"Mods/mod-repel")]
ModRepel,
[Description(@"Mods/mod-score-v2")]
ModScoreV2,
[Description(@"Mods/mod-seven-keys")]
ModSevenKeys,
[Description(@"Mods/mod-simplified-rhythm")]
ModSimplifiedRhythm,
[Description(@"Mods/mod-single-tap")]
ModSingleTap,
[Description(@"Mods/mod-six-keys")]
ModSixKeys,
[Description(@"Mods/mod-spin-in")]
ModSpinIn,
[Description(@"Mods/mod-spun-out")]
ModSpunOut,
[Description(@"Mods/mod-strict-tracking")]
ModStrictTracking,
[Description(@"Mods/mod-sudden-death")]
ModSuddenDeath,
[Description(@"Mods/mod-swap")]
ModSwap,
[Description(@"Mods/mod-synesthesia")]
ModSynesthesia,
[Description(@"Mods/mod-target-practice")]
ModTargetPractice,
[Description(@"Mods/mod-ten-keys")]
ModTenKeys,
[Description(@"Mods/mod-three-keys")]
ModThreeKeys,
[Description(@"Mods/mod-touch-device")]
ModTouchDevice,
[Description(@"Mods/mod-traceable")]
ModTraceable,
[Description(@"Mods/mod-transform")]
ModTransform,
[Description(@"Mods/mod-two-keys")]
ModTwoKeys,
[Description(@"Mods/mod-wiggle")]
ModWiggle,
[Description(@"Mods/mod-wind-down")]
ModWindDown,
[Description(@"Mods/mod-wind-up")]
ModWindUp,
}
public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore
+1 -1
View File
@@ -180,7 +180,7 @@ namespace osu.Game.Overlays
notification.Closed += () => notificationClosed(notification);
if (notification is IHasCompletionTarget hasCompletionTarget)
hasCompletionTarget.CompletionTarget = Post;
hasCompletionTarget.CompletionTarget ??= Post;
playDebouncedSample(notification.PopInSampleName);
+4 -2
View File
@@ -56,6 +56,9 @@ namespace osu.Game.Rulesets.Mods
{
var bindable = (IBindable)property.GetValue(this)!;
if (bindable.IsDefault)
continue;
string valueText;
switch (bindable)
@@ -69,8 +72,7 @@ namespace osu.Game.Rulesets.Mods
break;
}
if (!bindable.IsDefault)
yield return (attr.Label, valueText);
yield return (attr.Label, valueText);
}
}
}
@@ -6,8 +6,10 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Localisation.HUD;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
@@ -24,6 +26,8 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Fail if your accuracy drops too low!";
public override IconUsage? Icon => OsuIcon.ModAccuracyChallenge;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1.0;
@@ -6,10 +6,12 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Let track speed adapt to you.";
public override IconUsage? Icon => OsuIcon.ModAdaptiveSpeed;
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 0.5;
+1 -1
View File
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Autoplay";
public override string Acronym => "AT";
public override IconUsage? Icon => OsuIcon.ModAuto;
public override IconUsage? Icon => OsuIcon.ModAutoplay;
public override ModType Type => ModType.Automation;
public override LocalisableString Description => "Watch a perfect automated play through the song.";
public override double ScoreMultiplier => 1;
+9 -3
View File
@@ -6,8 +6,10 @@ using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -36,6 +38,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Barrel Roll";
public override string Acronym => "BR";
public override IconUsage? Icon => OsuIcon.ModBarrelRoll;
public override LocalisableString Description => "The whole playfield is on a wheel!";
public override double ScoreMultiplier => 1;
@@ -43,8 +46,10 @@ namespace osu.Game.Rulesets.Mods
{
get
{
yield return ("Roll speed", $"{SpinSpeed.Value:N2} rpm");
yield return ("Direction", Direction.Value.GetDescription());
if (!SpinSpeed.IsDefault)
yield return ("Roll speed", $"{SpinSpeed.Value:N2} rpm");
if (!Direction.IsDefault)
yield return ("Direction", Direction.Value.GetDescription());
}
}
@@ -52,7 +57,8 @@ namespace osu.Game.Rulesets.Mods
public virtual void Update(Playfield playfield)
{
playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
playfieldAdjustmentContainer.Rotation =
CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
}
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
+2 -1
View File
@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 0.96;
public override IconUsage? Icon => FontAwesome.Solid.History;
public override IconUsage? Icon => OsuIcon.ModClassic;
public override LocalisableString Description => "Feeling nostalgic?";
+2 -1
View File
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
namespace osu.Game.Rulesets.Mods
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Daycore";
public override string Acronym => "DC";
public override IconUsage? Icon => null;
public override IconUsage? Icon => OsuIcon.ModDaycore;
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "Whoaaaaa...";
public override bool Ranked => UsesDefaultConfiguration;
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => FontAwesome.Solid.Hammer;
public override IconUsage? Icon => OsuIcon.ModDifficultyAdjust;
public override double ScoreMultiplier => 0.5;
+44 -14
View File
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -14,8 +13,8 @@ using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Shaders.Types;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Layout;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.OpenGL.Vertices;
@@ -85,7 +84,11 @@ namespace osu.Game.Rulesets.Mods
flashlight.Colour = Color4.Black;
flashlight.Combo.BindTo(Combo);
flashlight.GetPlayfieldScale = () => drawableRuleset.PlayfieldAdjustmentContainer.Scale;
var playfieldDrawInfoTracker = new PlayfieldDrawInfoTracker();
drawableRuleset.PlayfieldAdjustmentContainer.Add(playfieldDrawInfoTracker);
flashlight.PlayfieldDrawInfoTracker = playfieldDrawInfoTracker;
drawableRuleset.Overlays.Add(new Container
{
@@ -111,7 +114,9 @@ namespace osu.Game.Rulesets.Mods
public override bool RemoveCompletedTransforms => false;
internal Func<Vector2>? GetPlayfieldScale;
internal PlayfieldDrawInfoTracker PlayfieldDrawInfoTracker { get; set; } = null!;
private DrawInfo playfieldDrawInfo => PlayfieldDrawInfoTracker.DrawInfo;
private readonly float defaultFlashlightSize;
private readonly float sizeMultiplier;
@@ -146,6 +151,8 @@ namespace osu.Game.Rulesets.Mods
isBreakTime.BindTo(player.IsBreakTime);
isBreakTime.BindValueChanged(_ => UpdateFlashlightSize(GetSize()), true);
}
PlayfieldDrawInfoTracker.OnDrawInfoInvalidate += () => Invalidate(Invalidation.DrawNode);
}
protected abstract void UpdateFlashlightSize(float size);
@@ -156,15 +163,6 @@ namespace osu.Game.Rulesets.Mods
{
float size = defaultFlashlightSize * sizeMultiplier;
if (GetPlayfieldScale != null)
{
Vector2 playfieldScale = GetPlayfieldScale();
Debug.Assert(Precision.AlmostEquals(Math.Abs(playfieldScale.X), Math.Abs(playfieldScale.Y)),
@"Playfield has non-proportional scaling. Flashlight implementations should be revisited with regard to balance.");
size *= Math.Abs(playfieldScale.X);
}
if (isBreakTime.Value)
size *= 2.5f;
else if (comboBasedSize)
@@ -265,7 +263,11 @@ namespace osu.Game.Rulesets.Mods
shader = Source.shader;
screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad;
flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix);
flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy;
// scale the flashlight based on the playfield to match gameplay components scale.
Vector2 drawInfoScale = Source.playfieldDrawInfo.Matrix.ExtractScale().Xy;
flashlightSize = Source.FlashlightSize * drawInfoScale;
flashlightDim = Source.FlashlightDim;
flashlightSmoothness = Source.flashlightSmoothness;
}
@@ -321,5 +323,33 @@ namespace osu.Game.Rulesets.Mods
}
}
}
/// <summary>
/// The purpose of this component is to track any changes to <c>Playfield.Parent.DrawInfo</c>
/// (by being added to the content of <see cref="PlayfieldAdjustmentContainer"/>).
/// All in order for the flashlight to invalidate its draw node and read any changes in the playfield's scaling.
/// </summary>
internal partial class PlayfieldDrawInfoTracker : Component
{
private readonly LayoutValue drawInfoLayout = new LayoutValue(Invalidation.DrawInfo);
public Action? OnDrawInfoInvalidate;
public PlayfieldDrawInfoTracker()
{
AddLayout(drawInfoLayout);
}
protected override void Update()
{
base.Update();
if (!drawInfoLayout.IsValid)
{
OnDrawInfoInvalidate?.Invoke();
drawInfoLayout.Validate();
}
}
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Half Time";
public override string Acronym => "HT";
public override IconUsage? Icon => OsuIcon.ModHalftime;
public override IconUsage? Icon => OsuIcon.ModHalfTime;
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "Less zoom...";
public override bool Ranked => SpeedChange.IsDefault;
+4
View File
@@ -1,12 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModMirror : Mod
{
public override string Name => "Mirror";
public override string Acronym => "MR";
public override IconUsage? Icon => OsuIcon.ModMirror;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
}
+2 -1
View File
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Objects;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Muted";
public override string Acronym => "MU";
public override IconUsage? Icon => FontAwesome.Solid.VolumeMute;
public override IconUsage? Icon => OsuIcon.ModMuted;
public override LocalisableString Description => "Can you still feel the rhythm without music?";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
+2 -1
View File
@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "NM";
public override LocalisableString Description => "No mods applied.";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Ban;
public override IconUsage? Icon => OsuIcon.ModNoMod;
public override ModType Type => ModType.System;
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
@@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "No Scope";
public override string Acronym => "NS";
public override ModType Type => ModType.Fun;
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
public override IconUsage? Icon => OsuIcon.ModNoScope;
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
+1 -1
View File
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Random";
public override string Acronym => "RD";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.Dice;
public override IconUsage? Icon => OsuIcon.ModRandom;
public override double ScoreMultiplier => 1;
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
+3
View File
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -13,6 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Score V2";
public override string Acronym => @"SV2";
public override IconUsage? Icon => OsuIcon.ModScoreV2;
public override ModType Type => ModType.System;
public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active.";
public override double ScoreMultiplier => 1;
+3
View File
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "SY";
public override LocalisableString Description => "Colours hit objects based on the rhythm.";
public override double ScoreMultiplier => 0.8;
public override IconUsage? Icon => OsuIcon.ModSynesthesia;
public override ModType Type => ModType.Fun;
}
}
+2 -1
View File
@@ -41,7 +41,8 @@ namespace osu.Game.Rulesets.Mods
{
get
{
yield return ("Speed change", $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x");
if (!InitialRate.IsDefault || !FinalRate.IsDefault)
yield return ("Speed change", $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x");
if (!AdjustPitch.IsDefault)
yield return ("Adjust pitch", AdjustPitch.Value ? "On" : "Off");
+1 -1
View File
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
{
public sealed override string Name => "Touch Device";
public sealed override string Acronym => "TD";
public sealed override IconUsage? Icon => OsuIcon.PlayStyleTouch;
public sealed override IconUsage? Icon => OsuIcon.ModTouchDevice;
public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen.";
public sealed override double ScoreMultiplier => 1;
public sealed override ModType Type => ModType.System;
+2 -1
View File
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Wind Down";
public override string Acronym => "WD";
public override LocalisableString Description => "Sloooow doooown...";
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown;
public override IconUsage? Icon => OsuIcon.ModWindDown;
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
{
+2 -1
View File
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Wind Up";
public override string Acronym => "WU";
public override LocalisableString Description => "Can you keep up?";
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp;
public override IconUsage? Icon => OsuIcon.ModWindUp;
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
{
@@ -160,8 +160,8 @@ namespace osu.Game.Rulesets.Objects.Pooling
if (!IsPresent)
return false;
bool aliveChanged = base.CheckChildrenLife();
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
bool aliveChanged = lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
aliveChanged |= base.CheckChildrenLife();
return aliveChanged;
}
}
+1 -5
View File
@@ -59,11 +59,7 @@ namespace osu.Game.Rulesets.Scoring
protected override void RevertResultInternal(JudgementResult result)
{
// TODO: this is rudimentary as to make rewinding failed replays work,
// but it also acts up (sometimes rewinding a replay several times around the fail boundary moves the point of fail forward).
// needs further investigation.
if (result.FailedAtJudgement)
HasFailed = false;
HasFailed = result.FailedAtJudgement;
if (HasFailed)
return;
+7 -1
View File
@@ -167,7 +167,13 @@ namespace osu.Game.Rulesets.UI
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(45),
RelativeSizeAxes = Axes.Both,
// the mod icon assets in `osu-resources` are sized such that they are flush with the hexagonal background with no shadow baked in.
// the `Icons/BeatmapDetails/mod-icon` asset (of size 135x100) has a shadow and some extra transparent pixels baked in.
// the hexagonal background on that asset, excluding its shadow and the transparent pixels, is 131px wide and 92px high.
// height is divided by 135 rather than by 100, because this entire component is square-sized.
Width = 131 / 135f,
Height = 92 / 135f,
Icon = FontAwesome.Solid.Question
},
adjustmentMarker = new Container
+3 -2
View File
@@ -61,7 +61,6 @@ namespace osu.Game.Rulesets.UI
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(0, 4),
Direction = FillDirection.Vertical,
Child = tinySwitch = new ModSwitchTiny(mod)
{
@@ -79,7 +78,9 @@ namespace osu.Game.Rulesets.UI
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(21),
Size = new Vector2(37, 26),
// arbitrary adjustment for better vertical alignment
Margin = new MarginPadding { Top = -1 },
Icon = mod.Icon.Value
});
tinySwitch.Scale = new Vector2(0.3f);

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