mirror of
https://github.com/ppy/osu.git
synced 2026-06-02 23:41:00 +08:00
Compare commits
159 Commits
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.930.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1021.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
private float halfCatcherWidth;
|
||||
|
||||
public override int Version => 20250306;
|
||||
public override int Version => 20251020;
|
||||
|
||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
string format(string acronym, DifficultyBindable bindable)
|
||||
=> $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -53,13 +54,25 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public override IEnumerable<string> LookupNames => lookup_names;
|
||||
|
||||
public BananaHitSampleInfo(int volume = 100)
|
||||
: base(string.Empty, volume: volume)
|
||||
public BananaHitSampleInfo()
|
||||
: this(string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
|
||||
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
|
||||
public BananaHitSampleInfo(HitSampleInfo info)
|
||||
: this(info.Name, info.Bank, info.Suffix, info.Volume, info.EditorAutoBank, info.UseBeatmapSamples)
|
||||
{
|
||||
}
|
||||
|
||||
private BananaHitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100, bool editorAutoBank = true, bool useBeatmapSamples = false)
|
||||
: base(name, bank, suffix, volume, editorAutoBank, useBeatmapSamples)
|
||||
{
|
||||
}
|
||||
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default,
|
||||
Optional<bool> newEditorAutoBank = default, Optional<bool> newUseBeatmapSamples = default)
|
||||
=> new BananaHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume),
|
||||
newEditorAutoBank.GetOr(EditorAutoBank), newUseBeatmapSamples.GetOr(UseBeatmapSamples));
|
||||
|
||||
public bool Equals(BananaHitSampleInfo? other)
|
||||
=> other != null;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
StartTime = time,
|
||||
BananaIndex = count,
|
||||
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(CreateHitSampleInfo().Volume) }
|
||||
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(CreateHitSampleInfo()) }
|
||||
});
|
||||
|
||||
count++;
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
|
||||
@@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
double mostCommonBeatLengthBefore = beatmap.GetMostCommonBeatLength();
|
||||
|
||||
var newObjects = new List<ManiaHitObject>();
|
||||
|
||||
foreach (var h in beatmap.HitObjects.OfType<HoldNote>())
|
||||
@@ -48,6 +51,17 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
}
|
||||
|
||||
maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType<Note>().Concat(newObjects).OrderBy(h => h.StartTime).ToList();
|
||||
|
||||
double mostCommonBeatLengthAfter = beatmap.GetMostCommonBeatLength();
|
||||
|
||||
// the process of removing hold notes can result in shortening the beatmap's play time,
|
||||
// and therefore, as a side effect, changing the most common BPM, which will change scroll speed.
|
||||
// to compensate for this, apply a multiplier to effect points in order to maintain the beatmap's original intended scroll speed.
|
||||
if (!Precision.AlmostEquals(mostCommonBeatLengthBefore, mostCommonBeatLengthAfter))
|
||||
{
|
||||
foreach (var effectPoint in beatmap.ControlPointInfo.EffectPoints)
|
||||
effectPoint.ScrollSpeed *= mostCommonBeatLengthBefore / mostCommonBeatLengthAfter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 644 KiB |
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
private const double star_rating_multiplier = 0.0265;
|
||||
|
||||
public override int Version => 20250306;
|
||||
public override int Version => 20251020;
|
||||
|
||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate);
|
||||
else if (score.Mods.Any(m => m is OsuModTraceable))
|
||||
{
|
||||
aimValue *= 1.0 + OsuRatingCalculator.CalculateVisibilityBonus(score.Mods, approachRate, attributes.SliderFactor);
|
||||
aimValue *= 1.0 + OsuRatingCalculator.CalculateVisibilityBonus(score.Mods, approachRate, sliderFactor: attributes.SliderFactor);
|
||||
}
|
||||
|
||||
aimValue *= accuracy;
|
||||
|
||||
@@ -187,7 +187,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
bool isAlwaysPartiallyVisible = mods.OfType<OsuModHidden>().Any(m => m.OnlyFadeApproachCircles.Value) || mods.OfType<OsuModTraceable>().Any();
|
||||
|
||||
// Start from normal curve, rewarding lower AR up to AR7
|
||||
double readingBonus = 0.04 * (12.0 - Math.Max(approachRate, 7));
|
||||
// TC forcefully requires a lower reading bonus for now as it's post-applied in PP which makes it multiplicative with the regular AR bonuses
|
||||
// This means it has an advantage over HD, so we decrease the multiplier to compensate
|
||||
// This should be removed once we're able to apply TC bonuses in SR (depends on real-time difficulty calculations being possible)
|
||||
double readingBonus = (isAlwaysPartiallyVisible ? 0.025 : 0.04) * (12.0 - Math.Max(approachRate, 7));
|
||||
|
||||
readingBonus *= visibilityFactor;
|
||||
|
||||
@@ -196,11 +199,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
// For AR up to 0 - reduce reward for very low ARs when object is visible
|
||||
if (approachRate < 7)
|
||||
readingBonus += (isAlwaysPartiallyVisible ? 0.03 : 0.045) * (7.0 - Math.Max(approachRate, 0)) * sliderVisibilityFactor;
|
||||
readingBonus += (isAlwaysPartiallyVisible ? 0.02 : 0.045) * (7.0 - Math.Max(approachRate, 0)) * sliderVisibilityFactor;
|
||||
|
||||
// Starting from AR0 - cap values so they won't grow to infinity
|
||||
if (approachRate < 0)
|
||||
readingBonus += (isAlwaysPartiallyVisible ? 0.075 : 0.1) * (1 - Math.Pow(1.5, approachRate)) * sliderVisibilityFactor;
|
||||
readingBonus += (isAlwaysPartiallyVisible ? 0.01 : 0.1) * (1 - Math.Pow(1.5, approachRate)) * sliderVisibilityFactor;
|
||||
|
||||
return readingBonus;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
string format(string acronym, DifficultyBindable bindable)
|
||||
=> $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,24 +62,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
spin = new Sprite
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-spin"),
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 335,
|
||||
},
|
||||
clear = new Sprite
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-clear"),
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 115,
|
||||
},
|
||||
bonusCounter = new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Alpha = 0,
|
||||
@@ -103,6 +85,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Scale = new Vector2(SPRITE_SCALE * 0.9f),
|
||||
Position = new Vector2(80, 448 + spm_hide_offset),
|
||||
},
|
||||
spin = new Sprite
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-spin"),
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 335,
|
||||
},
|
||||
clear = new Sprite
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-clear"),
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
Y = SPINNER_TOP_OFFSET + 115,
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
|
||||
@@ -13,19 +14,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
private readonly OsuRulesetConfigManager config;
|
||||
|
||||
[SettingSource("Show click markers", SettingControlType = typeof(PlayerCheckbox))]
|
||||
[SettingSource(typeof(PlayerSettingsOverlayStrings), nameof(PlayerSettingsOverlayStrings.ShowClickMarkers), SettingControlType = typeof(PlayerCheckbox))]
|
||||
public BindableBool ShowClickMarkers { get; } = new BindableBool();
|
||||
|
||||
[SettingSource("Show frame markers", SettingControlType = typeof(PlayerCheckbox))]
|
||||
[SettingSource(typeof(PlayerSettingsOverlayStrings), nameof(PlayerSettingsOverlayStrings.ShowFrameMarkers), SettingControlType = typeof(PlayerCheckbox))]
|
||||
public BindableBool ShowAimMarkers { get; } = new BindableBool();
|
||||
|
||||
[SettingSource("Show cursor path", SettingControlType = typeof(PlayerCheckbox))]
|
||||
[SettingSource(typeof(PlayerSettingsOverlayStrings), nameof(PlayerSettingsOverlayStrings.ShowCursorPath), SettingControlType = typeof(PlayerCheckbox))]
|
||||
public BindableBool ShowCursorPath { get; } = new BindableBool();
|
||||
|
||||
[SettingSource("Hide gameplay cursor", SettingControlType = typeof(PlayerCheckbox))]
|
||||
[SettingSource(typeof(PlayerSettingsOverlayStrings), nameof(PlayerSettingsOverlayStrings.HideGameplayCursor), SettingControlType = typeof(PlayerCheckbox))]
|
||||
public BindableBool HideSkinCursor { get; } = new BindableBool();
|
||||
|
||||
[SettingSource("Display length", SettingControlType = typeof(PlayerSliderBar<int>))]
|
||||
[SettingSource(typeof(PlayerSettingsOverlayStrings), nameof(PlayerSettingsOverlayStrings.DisplayLength), SettingControlType = typeof(PlayerSliderBar<int>))]
|
||||
public BindableInt DisplayLength { get; } = new BindableInt
|
||||
{
|
||||
MinValue = 200,
|
||||
@@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
};
|
||||
|
||||
public ReplayAnalysisSettings(OsuRulesetConfigManager config)
|
||||
: base("Analysis Settings")
|
||||
: base(PlayerSettingsOverlayStrings.AnalysisSettingsTitle)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
private bool isRelax;
|
||||
private bool isConvert;
|
||||
|
||||
public override int Version => 20250306;
|
||||
public override int Version => 20251020;
|
||||
|
||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
|
||||
@@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable, int digits) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(digits)}";
|
||||
string format(string acronym, DifficultyBindable bindable, int digits)
|
||||
=> $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(digits)}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
public const int SAMPLE_VOLUME_THRESHOLD_MEDIUM = 60;
|
||||
|
||||
public VolumeAwareHitSampleInfo(HitSampleInfo sampleInfo, bool isStrong = false)
|
||||
: base(sampleInfo.Name, isStrong ? BANK_STRONG : getBank(sampleInfo.Bank, sampleInfo.Name, sampleInfo.Volume), sampleInfo.Suffix, sampleInfo.Volume)
|
||||
: base(sampleInfo.Name, isStrong ? BANK_STRONG : getBank(sampleInfo.Bank, sampleInfo.Name, sampleInfo.Volume), sampleInfo.Suffix, sampleInfo.Volume,
|
||||
sampleInfo.EditorAutoBank, sampleInfo.UseBeatmapSamples)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
private class LegacyTaikoSampleInfo : HitSampleInfo
|
||||
{
|
||||
public LegacyTaikoSampleInfo(HitSampleInfo sampleInfo)
|
||||
: base(sampleInfo.Name, sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume)
|
||||
: base(sampleInfo.Name, sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume, sampleInfo.EditorAutoBank, sampleInfo.UseBeatmapSamples)
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@@ -236,6 +237,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Is.EquivalentTo(originalSlider.Path.ControlPoints.Select(p => p.Position)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEncodeCustomSampleBanks()
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 100, Samples = [new HitSampleInfo(HitSampleInfo.HIT_NORMAL)] },
|
||||
new HitCircle { StartTime = 200, Samples = [new HitSampleInfo(HitSampleInfo.HIT_NORMAL, useBeatmapSamples: true)] },
|
||||
new HitCircle { StartTime = 300, Samples = [new HitSampleInfo(HitSampleInfo.HIT_NORMAL, suffix: "3", useBeatmapSamples: true)] },
|
||||
}
|
||||
};
|
||||
|
||||
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty);
|
||||
|
||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[0].Samples[0].Suffix, Is.Null);
|
||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[0].Samples[0].UseBeatmapSamples, Is.False);
|
||||
|
||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[1].Samples[0].Suffix, Is.Null);
|
||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[1].Samples[0].UseBeatmapSamples, Is.True);
|
||||
|
||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[2].Samples[0].Suffix, Is.EqualTo("3"));
|
||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[2].Samples[0].UseBeatmapSamples, Is.True);
|
||||
}
|
||||
|
||||
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
|
||||
{
|
||||
// equal to null, no need to SequenceEqual
|
||||
|
||||
@@ -59,7 +59,15 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
// Ensure importer encoding is correct
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"fractional-coordinates.olz"));
|
||||
AddAssert("hit object has fractional position", () => ((IHasYPosition)beatmap.Beatmap.HitObjects[1]).Y, () => Is.EqualTo(383.99997).Within(0.00001));
|
||||
AddAssert("second slider has fractional position",
|
||||
() => ((IHasXPosition)beatmap.Beatmap.HitObjects[1]).X,
|
||||
() => Is.EqualTo(-3.0517578E-05).Within(0.00001));
|
||||
AddAssert("second slider path has fractional coordinates",
|
||||
() => ((IHasPath)beatmap.Beatmap.HitObjects[1]).Path.ControlPoints[1].Position.X,
|
||||
() => Is.EqualTo(191.999939).Within(0.00001));
|
||||
AddAssert("second hit circle has fractional position",
|
||||
() => ((IHasYPosition)beatmap.Beatmap.HitObjects[3]).Y,
|
||||
() => Is.EqualTo(383.99997).Within(0.00001));
|
||||
|
||||
// Ensure exporter legacy conversion is correct
|
||||
AddStep("export", () =>
|
||||
@@ -71,7 +79,15 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
});
|
||||
|
||||
AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream));
|
||||
AddAssert("hit object is snapped", () => ((IHasYPosition)beatmap.Beatmap.HitObjects[1]).Y, () => Is.EqualTo(384).Within(0.00001));
|
||||
AddAssert("second slider is snapped",
|
||||
() => ((IHasXPosition)beatmap.Beatmap.HitObjects[1]).X,
|
||||
() => Is.EqualTo(0).Within(0.00001));
|
||||
AddAssert("second slider path is snapped",
|
||||
() => ((IHasPath)beatmap.Beatmap.HitObjects[1]).Path.ControlPoints[1].Position.X,
|
||||
() => Is.EqualTo(192).Within(0.00001));
|
||||
AddAssert("second hit circle is snapped",
|
||||
() => ((IHasYPosition)beatmap.Beatmap.HitObjects[3]).Y,
|
||||
() => Is.EqualTo(384).Within(0.00001));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
var rulesets = new RealmRulesetStore(realm, storage);
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||
@@ -36,8 +36,8 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
var rulesets = new RealmRulesetStore(realm, storage);
|
||||
var rulesets2 = new RealmRulesetStore(realm, storage);
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
using var rulesets2 = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
var rulesets = new RealmRulesetStore(realm, storage);
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
|
||||
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Database
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
|
||||
|
||||
// Availability is updated on construction of a RealmRulesetStore
|
||||
_ = new RealmRulesetStore(realm, storage);
|
||||
using var _ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
|
||||
});
|
||||
@@ -104,13 +104,13 @@ namespace osu.Game.Tests.Database
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
|
||||
|
||||
// Availability is updated on construction of a RealmRulesetStore
|
||||
_ = new RealmRulesetStore(realm, storage);
|
||||
using var _ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
|
||||
|
||||
// Simulate the ruleset getting updated
|
||||
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
|
||||
_ = new RealmRulesetStore(realm, storage);
|
||||
using var __ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace osu.Game.Tests.Extensions
|
||||
[TestCase(0, true, 0, ExpectedResult = "0%")]
|
||||
[TestCase(1, true, 0, ExpectedResult = "1%")]
|
||||
[TestCase(50, true, 0, ExpectedResult = "50%")]
|
||||
[SetCulture("")] // invariant culture
|
||||
public string TestInteger(int input, bool percent, int decimalDigits)
|
||||
{
|
||||
return input.ToStandardFormattedString(decimalDigits, percent);
|
||||
@@ -39,6 +40,7 @@ namespace osu.Game.Tests.Extensions
|
||||
[TestCase(0.48333, true, 2, ExpectedResult = "48%")]
|
||||
[TestCase(0.48333, true, 4, ExpectedResult = "48.33%")]
|
||||
[TestCase(1, true, 0, ExpectedResult = "100%")]
|
||||
[SetCulture("")] // invariant culture
|
||||
public string TestDouble(double input, bool percent, int decimalDigits)
|
||||
{
|
||||
return input.ToStandardFormattedString(decimalDigits, percent);
|
||||
@@ -47,9 +49,9 @@ namespace osu.Game.Tests.Extensions
|
||||
[Test]
|
||||
[SetCulture("fr-FR")]
|
||||
[TestCase(0.4, true, 2, ExpectedResult = "40%")]
|
||||
[TestCase(1e-6, false, 6, ExpectedResult = "0.000001")]
|
||||
[TestCase(0.48333, true, 4, ExpectedResult = "48.33%")]
|
||||
public string TestCultureInsensitivity(double input, bool percent, int decimalDigits)
|
||||
[TestCase(1e-6, false, 6, ExpectedResult = "0,000001")]
|
||||
[TestCase(0.48333, true, 4, ExpectedResult = "48,33%")]
|
||||
public string TestCultureSensitivity(double input, bool percent, int decimalDigits)
|
||||
{
|
||||
return input.ToStandardFormattedString(decimalDigits, percent);
|
||||
}
|
||||
|
||||
@@ -299,6 +299,23 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("artist")]
|
||||
[TestCase("unicode")]
|
||||
public void TestCriteriaNotMatchingArtist(string excludedTerm)
|
||||
{
|
||||
var beatmap = getExampleBeatmap();
|
||||
var criteria = new FilterCriteria
|
||||
{
|
||||
Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = excludedTerm, ExcludeTerm = true }
|
||||
};
|
||||
|
||||
var carouselItem = new CarouselBeatmap(beatmap);
|
||||
carouselItem.Filter(criteria);
|
||||
|
||||
Assert.True(carouselItem.Filtered.Value);
|
||||
}
|
||||
|
||||
[TestCase("simple", false)]
|
||||
[TestCase("\"style/clean\"", false)]
|
||||
[TestCase("\"style/clean\"!", false)]
|
||||
@@ -350,6 +367,41 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Assert.AreEqual(true, carouselItem.Filtered.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCriteriaMatchingTagExcluded()
|
||||
{
|
||||
var beatmap = getExampleBeatmap();
|
||||
var criteria = new FilterCriteria
|
||||
{
|
||||
UserTags =
|
||||
[
|
||||
new FilterCriteria.OptionalTextFilter { SearchTerm = "\"song representation/simple\"!", ExcludeTerm = true },
|
||||
]
|
||||
};
|
||||
var carouselItem = new CarouselBeatmap(beatmap);
|
||||
carouselItem.Filter(criteria);
|
||||
|
||||
Assert.AreEqual(true, carouselItem.Filtered.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCriteriaOneTagIncludedAndOneTagExcluded()
|
||||
{
|
||||
var beatmap = getExampleBeatmap();
|
||||
var criteria = new FilterCriteria
|
||||
{
|
||||
UserTags =
|
||||
[
|
||||
new FilterCriteria.OptionalTextFilter { SearchTerm = "\"song representation/simple\"!" },
|
||||
new FilterCriteria.OptionalTextFilter { SearchTerm = "\"style/clean\"!", ExcludeTerm = true }
|
||||
]
|
||||
};
|
||||
var carouselItem = new CarouselBeatmap(beatmap);
|
||||
carouselItem.Filter(criteria);
|
||||
|
||||
Assert.AreEqual(true, carouselItem.Filtered.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapMustHaveAtLeastOneTagIfUserTagFilterActive()
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -149,6 +149,8 @@ namespace osu.Game.Tests.Rulesets
|
||||
public IBindable<double> AggregateFrequency => throw new NotImplementedException();
|
||||
public IBindable<double> AggregateTempo => throw new NotImplementedException();
|
||||
|
||||
public void Invalidate(string name) => throw new NotImplementedException();
|
||||
|
||||
public int PlaybackConcurrency { get; set; }
|
||||
|
||||
public void AddExtension(string extension) => throw new NotImplementedException();
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
};
|
||||
|
||||
for (int i = 1; i <= 100; i++)
|
||||
((DummyAPIAccess)API).Friends.Add(new APIRelation { TargetID = i, TargetUser = new APIUser { Username = $"Friend {i}" } });
|
||||
((DummyAPIAccess)API).LocalUserState.Friends.Add(new APIRelation { TargetID = i, TargetUser = new APIUser { Username = $"Friend {i}" } });
|
||||
});
|
||||
|
||||
[Test]
|
||||
@@ -75,7 +75,9 @@ namespace osu.Game.Tests.Visual.Components
|
||||
});
|
||||
|
||||
AddUntilStep("chat overlay opened", () => chatOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddUntilStep("user channel selected", () => channelManager.CurrentChannel.Value.Name, () => Is.EqualTo(((DummyAPIAccess)API).Friends[0].TargetUser!.Username));
|
||||
AddUntilStep("user channel selected",
|
||||
() => channelManager.CurrentChannel.Value.Name,
|
||||
() => Is.EqualTo(((DummyAPIAccess)API).LocalUserState.Friends[0].TargetUser!.Username));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -90,8 +90,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
var api = (DummyAPIAccess)API;
|
||||
|
||||
api.Friends.Clear();
|
||||
api.Friends.Add(new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.Add(new APIRelation
|
||||
{
|
||||
Mutual = true,
|
||||
RelationType = RelationType.Friend,
|
||||
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
new ScoreInfo { User = new APIUser { Username = "Top", Id = 2 }, TotalScore = 900_000, Accuracy = 0.99, MaxCombo = 999 },
|
||||
new ScoreInfo { User = new APIUser { Username = "Second", Id = 14 }, TotalScore = 800_000, Accuracy = 0.9, MaxCombo = 888 },
|
||||
new ScoreInfo { User = friend, TotalScore = 700_000, Accuracy = 0.88, MaxCombo = 777 },
|
||||
}, 3, null);
|
||||
}, scoresRequested: 50, totalScores: 3, null);
|
||||
});
|
||||
|
||||
createLeaderboard();
|
||||
@@ -129,8 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
var api = (DummyAPIAccess)API;
|
||||
|
||||
api.Friends.Clear();
|
||||
api.Friends.Add(new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.Add(new APIRelation
|
||||
{
|
||||
Mutual = true,
|
||||
RelationType = RelationType.Friend,
|
||||
@@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
new ScoreInfo { User = new APIUser { Username = "Top", Id = 2 }, TotalScore = 900_000_000, Accuracy = 0.99, MaxCombo = 999999 },
|
||||
new ScoreInfo { User = new APIUser { Username = "Second", Id = 14 }, TotalScore = 800_000_000, Accuracy = 0.9, MaxCombo = 888888 },
|
||||
new ScoreInfo { User = friend, TotalScore = 700_000_000, Accuracy = 0.88, MaxCombo = 777777 },
|
||||
}, 3, null);
|
||||
}, scoresRequested: 50, totalScores: 3, null);
|
||||
});
|
||||
|
||||
createLeaderboard();
|
||||
@@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
scores.Add(new ScoreInfo { User = new APIUser { Username = $"Player {i + 1}" }, TotalScore = RNG.Next(700_000, 1_000_000) });
|
||||
|
||||
// this is dodgy but anything less dodgy is a lot of work
|
||||
((Bindable<LeaderboardScores?>)leaderboardManager.Scores).Value = LeaderboardScores.Success(scores, scores.Count, null);
|
||||
((Bindable<LeaderboardScores?>)leaderboardManager.Scores).Value = LeaderboardScores.Success(scores, scoresRequested: 50, scores.Count, null);
|
||||
gameplayState.ScoreProcessor.TotalScore.Value = 0;
|
||||
});
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
new ScoreInfo { User = new APIUser { Username = "smoogipoo", Id = 1040328 }, TotalScore = 800_000, Accuracy = 0.9, MaxCombo = 888 },
|
||||
new ScoreInfo { User = new APIUser { Username = "flyte", Id = 3103765 }, TotalScore = 700_000, Accuracy = 0.9, MaxCombo = 888 },
|
||||
new ScoreInfo { User = new APIUser { Username = "frenzibyte", Id = 14210502 }, TotalScore = 600_000, Accuracy = 0.9, MaxCombo = 777 },
|
||||
}, 4, null);
|
||||
}, scoresRequested: 50, totalScores: 4, null);
|
||||
});
|
||||
|
||||
createLeaderboard();
|
||||
@@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
((Bindable<LeaderboardScores?>)leaderboardManager.Scores).Value = LeaderboardScores.Success(new[]
|
||||
{
|
||||
new ScoreInfo { User = new APIUser { Username = "Quit", Id = 3 }, TotalScore = 100_000, Accuracy = 0.99, MaxCombo = 999 },
|
||||
}, 1, null);
|
||||
}, scoresRequested: 50, totalScores: 1, null);
|
||||
});
|
||||
|
||||
createLeaderboard();
|
||||
|
||||
@@ -257,6 +257,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private class CustomRuleset : OsuRuleset, ILegacyRuleset
|
||||
{
|
||||
public override string Description => "custom";
|
||||
|
||||
@@ -37,7 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TotalScore = 10_000 * (100 - i),
|
||||
Position = i,
|
||||
}).ToArray(),
|
||||
1337,
|
||||
scoresRequested: 100,
|
||||
totalScores: 100,
|
||||
null
|
||||
);
|
||||
});
|
||||
@@ -84,7 +85,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TotalScore = 600_000 + 10_000 * (40 - i),
|
||||
Position = i,
|
||||
}).ToArray(),
|
||||
1337,
|
||||
scoresRequested: 50,
|
||||
totalScores: 40,
|
||||
null
|
||||
);
|
||||
});
|
||||
@@ -131,7 +133,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TotalScore = 500_000 + 10_000 * (50 - i),
|
||||
Position = i
|
||||
}).ToArray(),
|
||||
1337,
|
||||
scoresRequested: 50,
|
||||
totalScores: 1337,
|
||||
new ScoreInfo { TotalScore = 200_000 }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
@@ -86,9 +87,9 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddStep("add selection 1", () => grid.ChildrenOfType<BeatmapSelectPanel>().First().AddUser(new APIUser
|
||||
{
|
||||
Id = 6411631,
|
||||
Id = DummyAPIAccess.DUMMY_USER_ID,
|
||||
Username = "Maarvin",
|
||||
}, isOwnUser: true));
|
||||
}));
|
||||
AddStep("add selection 2", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(5).First().AddUser(new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
@@ -22,17 +24,24 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
BeatmapSelectPanel? panel = null;
|
||||
|
||||
AddStep("add panel", () => Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
||||
AddStep("add panel", () =>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("add maarvin", () => panel!.AddUser(new APIUser
|
||||
{
|
||||
Id = 6411631,
|
||||
Id = DummyAPIAccess.DUMMY_USER_ID,
|
||||
Username = "Maarvin",
|
||||
}, isOwnUser: true));
|
||||
}));
|
||||
AddStep("add peppy", () => panel!.AddUser(new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundWarmup;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneIdleScreen : MultiplayerTestScene
|
||||
{
|
||||
private const int user_count = 8;
|
||||
|
||||
private (MultiplayerRoomUser user, int score)[] userScores = null!;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add list", () =>
|
||||
{
|
||||
userScores = Enumerable.Range(1, user_count).Select(i =>
|
||||
{
|
||||
var user = new MultiplayerRoomUser(i)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Username = $"Player {i}"
|
||||
}
|
||||
};
|
||||
|
||||
return (user, 0);
|
||||
}).ToArray();
|
||||
|
||||
Child = new ScreenStack(new SubScreenRoundWarmup())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.8f)
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("join users", () =>
|
||||
{
|
||||
foreach (var (user, _) in userScores)
|
||||
MultiplayerClient.AddUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomChanges()
|
||||
{
|
||||
AddStep("apply random changes", () =>
|
||||
{
|
||||
int[] deltas = Enumerable.Range(1, userScores.Length).ToArray();
|
||||
new Random().Shuffle(deltas);
|
||||
|
||||
for (int i = 0; i < userScores.Length; i++)
|
||||
userScores[i] = (userScores[i].user, userScores[i].score + deltas[i]);
|
||||
userScores = userScores.OrderByDescending(u => u.score).ToArray();
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(new MatchmakingRoomState
|
||||
{
|
||||
Users =
|
||||
{
|
||||
UserDictionary = userScores.Select((tuple, i) => new MatchmakingUser
|
||||
{
|
||||
UserId = tuple.user.UserID,
|
||||
Points = tuple.score,
|
||||
Placement = i + 1
|
||||
}).ToDictionary(s => s.UserId)
|
||||
}
|
||||
}).WaitSafely();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneMatchmakingChatDisplay : ScreenTestScene
|
||||
{
|
||||
private MatchmakingChatDisplay? chat;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add chat", () =>
|
||||
{
|
||||
chat?.Expire();
|
||||
|
||||
ScreenFooter.Add(chat = new MatchmakingChatDisplay(new Room())
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Size = new Vector2(700, 130),
|
||||
Margin = new MarginPadding { Bottom = 10, Right = WaveOverlayContainer.WIDTH_PADDING - OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
|
||||
Alpha = 0
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("show footer", () => ScreenFooter.Show());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAppearDisappear()
|
||||
{
|
||||
AddStep("appear", () => chat!.Appear());
|
||||
AddWaitStep("wait for animation", 3);
|
||||
|
||||
AddStep("disappear", () => chat!.Disappear());
|
||||
AddWaitStep("wait for animation", 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
Value =
|
||||
[
|
||||
new MatchmakingPool { Id = 0, RulesetId = 0 },
|
||||
new MatchmakingPool { Id = 1, RulesetId = 1 },
|
||||
new MatchmakingPool { Id = 2, RulesetId = 2 },
|
||||
new MatchmakingPool { Id = 3, RulesetId = 3, Variant = 4 },
|
||||
new MatchmakingPool { Id = 4, RulesetId = 3, Variant = 7 },
|
||||
new MatchmakingPool { Id = 0, RulesetId = 0, Name = "osu!" },
|
||||
new MatchmakingPool { Id = 1, RulesetId = 1, Name = "osu!taiko" },
|
||||
new MatchmakingPool { Id = 2, RulesetId = 2, Name = "osu!catch" },
|
||||
new MatchmakingPool { Id = 3, RulesetId = 3, Variant = 4, Name = "osu!mania (4k)" },
|
||||
new MatchmakingPool { Id = 4, RulesetId = 3, Variant = 7, Name = "osu!mania (7k)" },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,12 +120,16 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
changeStage(MatchmakingStage.Ended, state =>
|
||||
{
|
||||
int localUserId = API.LocalUser.Value.OnlineID;
|
||||
int i = 1;
|
||||
|
||||
state.Users[localUserId].Placement = 1;
|
||||
state.Users[localUserId].Rounds[1].Placement = 1;
|
||||
state.Users[localUserId].Rounds[1].TotalScore = 1;
|
||||
state.Users[localUserId].Rounds[1].Statistics[HitResult.LargeBonus] = 1;
|
||||
foreach (var user in MultiplayerClient.ServerRoom!.Users.OrderBy(_ => RNG.Next()))
|
||||
{
|
||||
state.Users[user.UserID].Placement = i++;
|
||||
state.Users[user.UserID].Points = (8 - i) * 7;
|
||||
state.Users[user.UserID].Rounds[1].Placement = 1;
|
||||
state.Users[user.UserID].Rounds[1].TotalScore = 1;
|
||||
state.Users[user.UserID].Rounds[1].Statistics[HitResult.LargeBonus] = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add statistic", () => Child = new PanelRoomAward("Statistic description", 1)
|
||||
AddStep("add award", () => Child = new PanelRoomAward("Award name", "Description of what this award means", 1)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
|
||||
@@ -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 NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -34,7 +35,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
Id = 2,
|
||||
Colour = "99EB47",
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/baba245ef60834b769694178f8f6d4f6166c5188c740de084656ad2b80f1eea7.jpeg",
|
||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||
}
|
||||
})
|
||||
@@ -66,7 +67,10 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
}
|
||||
}).WaitSafely());
|
||||
|
||||
AddToggleStep("toggle horizontal", h => panel.Horizontal = h);
|
||||
foreach (var layout in Enum.GetValues<PlayerPanelDisplayMode>())
|
||||
{
|
||||
AddStep($"set layout to {layout}", () => panel.DisplayMode = layout);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
+3
-4
@@ -14,12 +14,11 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneUserPanelOverlay : MultiplayerTestScene
|
||||
public partial class TestScenePlayerPanelOverlay : MultiplayerTestScene
|
||||
{
|
||||
private PlayerPanelOverlay list = null!;
|
||||
|
||||
@@ -118,10 +117,10 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("two panels displayed", () => this.ChildrenOfType<UserPanel>().Count(), () => Is.EqualTo(2));
|
||||
AddUntilStep("two panels displayed", () => this.ChildrenOfType<PlayerPanel>().Count(), () => Is.EqualTo(2));
|
||||
|
||||
AddStep("remove a user", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
|
||||
AddUntilStep("one panel displayed", () => this.ChildrenOfType<UserPanel>().Count(), () => Is.EqualTo(1));
|
||||
AddUntilStep("one panel displayed", () => this.ChildrenOfType<PlayerPanel>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -18,8 +18,6 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneResultsScreen : MultiplayerTestScene
|
||||
{
|
||||
private const int invalid_user_id = 1;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
@@ -27,6 +25,43 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("set initial results", () =>
|
||||
{
|
||||
var state = new MatchmakingRoomState
|
||||
{
|
||||
CurrentRound = 6,
|
||||
Stage = MatchmakingStage.Ended
|
||||
};
|
||||
|
||||
int localUserId = API.LocalUser.Value.OnlineID;
|
||||
|
||||
// Overall state.
|
||||
state.Users[localUserId].Placement = 1;
|
||||
state.Users[localUserId].Points = 8;
|
||||
for (int round = 1; round <= state.CurrentRound; round++)
|
||||
state.Users[localUserId].Rounds[round].Placement = round;
|
||||
|
||||
// Highest score.
|
||||
state.Users[localUserId].Rounds[1].TotalScore = 1000;
|
||||
|
||||
// Highest accuracy.
|
||||
state.Users[localUserId].Rounds[2].Accuracy = 0.9995;
|
||||
|
||||
// Highest combo.
|
||||
state.Users[localUserId].Rounds[3].MaxCombo = 100;
|
||||
|
||||
// Most bonus score.
|
||||
state.Users[localUserId].Rounds[4].Statistics[HitResult.LargeBonus] = 50;
|
||||
|
||||
// Smallest score difference.
|
||||
state.Users[localUserId].Rounds[5].TotalScore = 1000;
|
||||
|
||||
// Largest score difference.
|
||||
state.Users[localUserId].Rounds[6].TotalScore = 1000;
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||
});
|
||||
|
||||
AddStep("add results screen", () =>
|
||||
{
|
||||
Child = new ScreenStack(new SubScreenResults())
|
||||
@@ -36,7 +71,18 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
Size = new Vector2(0.8f)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("do nothing", () => { });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInvalidUser()
|
||||
{
|
||||
const int invalid_user_id = 1;
|
||||
AddStep("join another user", () => MultiplayerClient.AddUser(new MultiplayerRoomUser(invalid_user_id)
|
||||
{
|
||||
User = new APIUser
|
||||
@@ -45,11 +91,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
Username = "Invalid user"
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResults()
|
||||
{
|
||||
AddStep("set results stage", () =>
|
||||
{
|
||||
var state = new MatchmakingRoomState
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.Matchmaking;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneStageSegment : MultiplayerTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add bubble", () => Child = new StageDisplay.StageSegment(null, MatchmakingStage.RoundWarmupTime, "Next Round")
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStartStopCountdown()
|
||||
{
|
||||
MultiplayerCountdown countdown = null!;
|
||||
|
||||
AddStep("start countdown", () => MultiplayerClient.StartCountdown(countdown = new MatchmakingStageCountdown
|
||||
{
|
||||
Stage = MatchmakingStage.RoundWarmupTime,
|
||||
TimeRemaining = TimeSpan.FromSeconds(5)
|
||||
}).WaitSafely());
|
||||
|
||||
AddWaitStep("wait a bit", 10);
|
||||
|
||||
AddStep("stop countdown", () => MultiplayerClient.StopCountdown(countdown).WaitSafely());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneStatusText : MultiplayerTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("create display", () => Child = new StageDisplay.StatusText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeStage()
|
||||
{
|
||||
foreach (var stage in Enum.GetValues<MatchmakingStage>())
|
||||
{
|
||||
AddStep($"{stage}", () => MultiplayerClient.MatchmakingChangeStage(stage).WaitSafely());
|
||||
AddWaitStep("wait a bit", 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@@ -36,6 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
|
||||
private TestMultiplayerComponents multiplayerComponents = null!;
|
||||
|
||||
@@ -46,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapStore beatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
@@ -115,5 +117,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded);
|
||||
AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@@ -37,13 +38,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneDrawableRoomPlaylist : MultiplayerTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private TestPlaylist playlist = null!;
|
||||
private BeatmapManager manager = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
@@ -436,6 +438,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestPlaylist : DrawableRoomPlaylist
|
||||
{
|
||||
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayer : ScreenTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapSetInfo importedSet2 = null!;
|
||||
@@ -67,7 +68,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapStore beatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
@@ -1247,5 +1248,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => multiplayerClient.RoomJoined);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
@@ -170,6 +171,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
.All(b => b.Mod.GetType() != type));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
|
||||
{
|
||||
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
@@ -44,6 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public partial class TestSceneMultiplayerMatchSubScreen : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerMatchSubScreen screen = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private Room room = null!;
|
||||
@@ -51,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
Dependencies.CacheAs<BeatmapStore>(new RealmDetachedBeatmapStore());
|
||||
@@ -462,6 +464,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("settings still open", () => this.ChildrenOfType<MultiplayerMatchSettingsOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
|
||||
{
|
||||
[Resolved(canBeNull: true)]
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@@ -28,6 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerPlaylist list = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapInfo importedBeatmap = null!;
|
||||
@@ -35,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
@@ -290,5 +292,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
.Single()
|
||||
.Items.Any(i => i.ID == playlistItemId);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
@@ -26,6 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public partial class TestSceneMultiplayerQueueList : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerQueueList playlist = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapInfo importedBeatmap = null!;
|
||||
@@ -34,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
@@ -168,5 +170,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
var button = playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAtOrDefault(index);
|
||||
return (button?.Alpha > 0) == visible;
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@@ -32,12 +33,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private Room room = null!;
|
||||
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -162,5 +164,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void assertReadyButtonEnablement(bool shouldBeEnabled)
|
||||
=> AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => startControl.ChildrenOfType<MultiplayerReadyButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Platform;
|
||||
@@ -31,6 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager manager = null!;
|
||||
private TestPlaylistsSongSelect songSelect = null!;
|
||||
private Room room = null!;
|
||||
@@ -40,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapStore beatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
@@ -189,6 +191,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("mod select visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestPlaylistsSongSelect : PlaylistsSongSelect
|
||||
{
|
||||
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -27,6 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneTeamVersus : ScreenTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
|
||||
@@ -37,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
@@ -182,5 +184,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => multiplayerClient.RoomJoined);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
@@ -54,6 +57,91 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests pushing and exiting subscreens that have footers.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPushAndExitSubScreens()
|
||||
{
|
||||
TestScreenWithSubScreen screen = null!;
|
||||
|
||||
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenTwo());
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
|
||||
AddStep("exit sub screen", () => screen.ExitSubScreen());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
AddStep("exit sub screen", () => screen.ExitSubScreen());
|
||||
AddAssert("footer hidden", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests pushing a new parenting screen while the footer is displayed from a subscreen.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPushParentScreenDuringSubScreen()
|
||||
{
|
||||
TestScreenWithSubScreen screen = null!;
|
||||
|
||||
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
PushAndConfirm(() => new TestScreenTwo());
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
|
||||
AddStep("exit parent screen", () => Game.ScreenStack.Exit());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests pushing a new subscreen after a new parenting screen has been pushed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPushSubScreenWhileNotCurrent()
|
||||
{
|
||||
TestScreenWithSubScreen screen = null!;
|
||||
|
||||
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
|
||||
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
PushAndConfirm(() => new TestScreenOne());
|
||||
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
// Can't use the helper method because the screen never loads
|
||||
AddStep("Push new sub screen", () => screen.PushSubScreen(new TestScreenTwo()));
|
||||
AddWaitStep("wait for potential screen load", 5);
|
||||
AddUntilStep("button one still shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
|
||||
|
||||
AddStep("exit parent screen", () => Game.ScreenStack.Exit());
|
||||
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
|
||||
}
|
||||
|
||||
private void pushSubScreenAndConfirm(Func<TestScreenWithSubScreen> target, Func<Screen> newScreen)
|
||||
{
|
||||
Screen screen = null!;
|
||||
IScreen? previousScreen = null;
|
||||
|
||||
AddStep("Push new sub screen", () =>
|
||||
{
|
||||
previousScreen = target().CurrentSubScreen;
|
||||
target().PushSubScreen(screen = newScreen());
|
||||
});
|
||||
|
||||
AddUntilStep("Wait for new screen", () => screen.IsLoaded
|
||||
&& target().CurrentSubScreen != previousScreen
|
||||
&& (previousScreen == null || previousScreen.GetChildScreen() == screen));
|
||||
}
|
||||
|
||||
private partial class TestScreenOne : OsuScreen
|
||||
{
|
||||
public override bool ShowFooter => true;
|
||||
@@ -89,5 +177,24 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
ShowFooter = footer;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestScreenWithSubScreen : OsuScreen, IHasSubScreenStack
|
||||
{
|
||||
public ScreenStack SubScreenStack { get; }
|
||||
|
||||
public TestScreenWithSubScreen()
|
||||
{
|
||||
InternalChild = SubScreenStack = new ScreenStack
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
|
||||
public IScreen? CurrentSubScreen => SubScreenStack.CurrentScreen;
|
||||
|
||||
public void PushSubScreen(IScreen screen) => SubScreenStack.Push(screen);
|
||||
|
||||
public void ExitSubScreen() => SubScreenStack.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens;
|
||||
@@ -24,6 +26,7 @@ using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
@@ -271,6 +274,33 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("selected beatmap is still osu! ruleset", () => Game.Beatmap.Value.BeatmapInfo, () => Is.EqualTo(selectedBeatmap));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: This test was written to demonstrate the failure described at https://github.com/ppy/osu/issues/35023,
|
||||
/// but because the failure scenario there entailed a race condition, it was possible for the test to pass regardless
|
||||
/// unless <see cref="osu.Game.Screens.SelectV2.SongSelect.SELECTION_DEBOUNCE"/> was increased.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPresentFromResults()
|
||||
{
|
||||
BeatmapSetInfo beatmapToPresent = null!;
|
||||
BeatmapSetInfo beatmapToPlay = null!;
|
||||
AddStep("manually insert beatmap to be presented", () =>
|
||||
{
|
||||
Game.Realm.Write(r =>
|
||||
{
|
||||
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(3, [r.Find<RulesetInfo>("osu")]);
|
||||
r.Add(beatmapSet);
|
||||
beatmapToPresent = beatmapSet.Detach();
|
||||
});
|
||||
});
|
||||
AddStep("import beatmap", () => beatmapToPlay = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
AddStep("set global beatmap", () => Game.Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(beatmapToPlay.Beatmaps.First()));
|
||||
playToResults();
|
||||
AddStep("present beatmap from results", () => Game.PresentBeatmap(beatmapToPresent));
|
||||
AddUntilStep("back at song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
AddUntilStep("presented beatmap is current", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapToPresent));
|
||||
}
|
||||
|
||||
private Func<Player> playToResults()
|
||||
{
|
||||
var player = playToCompletion();
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
if (supportLevel > 3)
|
||||
supportLevel = 0;
|
||||
|
||||
((DummyAPIAccess)API).Friends.Add(new APIRelation
|
||||
((DummyAPIAccess)API).LocalUserState.Friends.Add(new APIRelation
|
||||
{
|
||||
TargetID = 2,
|
||||
RelationType = RelationType.Friend,
|
||||
|
||||
@@ -59,8 +59,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("set friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = u.OnlineID,
|
||||
@@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("remove one friend", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.RemoveAt(0);
|
||||
api.LocalUserState.Friends.RemoveAt(0);
|
||||
});
|
||||
|
||||
waitForLoad();
|
||||
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("add one friend", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.AddRange(getUsers().Take(1).Select(u => new APIRelation
|
||||
api.LocalUserState.Friends.AddRange(getUsers().Take(1).Select(u => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = u.OnlineID,
|
||||
@@ -101,8 +101,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("set friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = u.OnlineID,
|
||||
@@ -130,8 +130,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("set friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = u.OnlineID,
|
||||
@@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("bring a friend online", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
metadataClient.FriendPresenceUpdated(api.Friends[0].TargetID, new UserPresence { Status = UserStatus.Online });
|
||||
metadataClient.FriendPresenceUpdated(api.LocalUserState.Friends[0].TargetID, new UserPresence { Status = UserStatus.Online });
|
||||
});
|
||||
|
||||
assertVisiblePanelCount<UserPanel>(1);
|
||||
@@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("bring a friend online", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
metadataClient.FriendPresenceUpdated(api.Friends[1].TargetID, new UserPresence { Status = UserStatus.Online });
|
||||
metadataClient.FriendPresenceUpdated(api.LocalUserState.Friends[1].TargetID, new UserPresence { Status = UserStatus.Online });
|
||||
});
|
||||
|
||||
assertVisiblePanelCount<UserPanel>(1);
|
||||
@@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("take friend offline", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
metadataClient.FriendPresenceUpdated(api.Friends[1].TargetID, null);
|
||||
metadataClient.FriendPresenceUpdated(api.LocalUserState.Friends[1].TargetID, null);
|
||||
});
|
||||
assertVisiblePanelCount<UserPanel>(1);
|
||||
|
||||
@@ -184,8 +184,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("set friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(getUsers().Select(u => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = u.OnlineID,
|
||||
|
||||
@@ -471,7 +471,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
public DrawableChannel DrawableChannel => InternalChildren.OfType<DrawableChannel>().First();
|
||||
|
||||
public ChannelScrollContainer ScrollContainer => (ChannelScrollContainer)((Container)DrawableChannel.Child).Child;
|
||||
public ChannelScrollContainer ScrollContainer => DrawableChannel.ChildrenOfType<ChannelScrollContainer>().Single();
|
||||
|
||||
public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child;
|
||||
|
||||
|
||||
@@ -443,7 +443,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Task.Run(() =>
|
||||
{
|
||||
requestLock.Wait(3000);
|
||||
dummyAPI.Friends.Add(apiRelation);
|
||||
dummyAPI.LocalUserState.Friends.Add(apiRelation);
|
||||
req.TriggerSuccess(new AddFriendResponse
|
||||
{
|
||||
UserRelation = apiRelation
|
||||
@@ -453,11 +453,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
return true;
|
||||
};
|
||||
});
|
||||
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
|
||||
AddStep("clear friend list", () => dummyAPI.LocalUserState.Friends.Clear());
|
||||
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
|
||||
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
|
||||
AddStep("Complete request", () => requestLock.Set());
|
||||
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
|
||||
AddUntilStep("Friend added", () => API.LocalUserState.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -486,7 +486,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Task.Run(() =>
|
||||
{
|
||||
requestLock.Wait(3000);
|
||||
dummyAPI.Friends.Add(apiRelation);
|
||||
dummyAPI.LocalUserState.Friends.Add(apiRelation);
|
||||
req.TriggerSuccess(new AddFriendResponse
|
||||
{
|
||||
UserRelation = apiRelation
|
||||
@@ -496,11 +496,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
return true;
|
||||
};
|
||||
});
|
||||
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
|
||||
AddStep("clear friend list", () => dummyAPI.LocalUserState.Friends.Clear());
|
||||
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
|
||||
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
|
||||
AddStep("Complete request", () => requestLock.Set());
|
||||
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
|
||||
AddUntilStep("Friend added", () => API.LocalUserState.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@@ -25,6 +26,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestSceneAddPlaylistToCollectionButton : OsuManualInputManagerTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager manager = null!;
|
||||
private BeatmapSetInfo importedBeatmap = null!;
|
||||
private Room room = null!;
|
||||
@@ -33,7 +35,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -112,5 +114,13 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@@ -32,6 +33,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestScenePlaylistsRoomCreation : OnlinePlayTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager manager = null!;
|
||||
private TestPlaylistsRoomSubScreen match = null!;
|
||||
private BeatmapSetInfo importedBeatmap = null!;
|
||||
@@ -40,7 +42,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
@@ -220,6 +222,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
});
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
|
||||
{
|
||||
public new Bindable<PlaylistItem?> SelectedItem => base.SelectedItem;
|
||||
|
||||
@@ -11,6 +11,7 @@ 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;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
@@ -38,6 +39,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
|
||||
@@ -46,7 +48,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
BeatmapStore beatmapStore;
|
||||
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
|
||||
Dependencies.Cache(Realm);
|
||||
@@ -579,6 +581,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("mods set", () => SelectedMods.Value.Count == 1 && SelectedMods.Value.OfType<OsuModDoubleTime>().Any());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestPlaylistsScreen : OsuScreen
|
||||
{
|
||||
public TestPlaylistsScreen(PlaylistsRoomSubScreen screen)
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@@ -351,7 +352,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
Score = userBest,
|
||||
Position = 133_337,
|
||||
}
|
||||
},
|
||||
ScoresCount = 200_000,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -405,7 +407,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
Score = userBest,
|
||||
Position = 133_337,
|
||||
}
|
||||
},
|
||||
ScoresCount = 200_000,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -510,7 +513,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
Score = userBest,
|
||||
Position = 133_337,
|
||||
}
|
||||
},
|
||||
ScoresCount = 200_000,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -531,5 +535,13 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddAssert("only one score with ID 12345", () => this.ChildrenOfType<ScorePanel>().Count(s => s.Score.OnlineID == 12345), () => Is.EqualTo(1));
|
||||
AddUntilStep("user best position preserved", () => this.ChildrenOfType<ScorePanel>().Any(p => p.ScorePosition.Value == 133_337));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesetStore.IsNotNull())
|
||||
rulesetStore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@@ -219,8 +220,15 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Tags =
|
||||
[
|
||||
new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", },
|
||||
new APITag { Id = 2, Name = "style/clean", Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", },
|
||||
new APITag { Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", },
|
||||
new APITag
|
||||
{
|
||||
Id = 2, Name = "style/clean",
|
||||
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.",
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.",
|
||||
},
|
||||
new APITag { Id = 4, Name = "tap/bursts", Description = "Patterns requiring continuous movement and alternating, typically 9 notes or less.", },
|
||||
]
|
||||
}), 500);
|
||||
@@ -403,6 +411,14 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
return hitEvents;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesetStore.IsNotNull())
|
||||
rulesetStore?.Dispose();
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@@ -28,6 +29,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneCollectionDropdown : OsuManualInputManagerTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private CollectionDropdown dropdown = null!;
|
||||
|
||||
@@ -37,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -269,5 +271,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
CollectionFilterMenuItem item = dropdown.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
|
||||
return dropdown.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@@ -27,13 +28,14 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
private DialogOverlay dialogOverlay = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private ManageCollectionsDialog dialog = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -379,5 +381,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void assertCollectionName(int index, string name)
|
||||
=> AddUntilStep($"item {index + 1} has correct name",
|
||||
() => dialog.ChildrenOfType<DrawableCollectionList>().Single().OrderedItems.ElementAtOrDefault(index)?.ChildrenOfType<TextBox>().First().Text == name);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@@ -211,5 +212,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank, () => Is.Null);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,12 +271,19 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(applyStars(2), beatmapSets, out var beatmap2);
|
||||
addBeatmapSet(applyStars(2.1), beatmapSets, out var beatmapAbove2);
|
||||
addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7);
|
||||
addBeatmapSet(applyStars(13), beatmapSets, out var beatmap13);
|
||||
addBeatmapSet(applyStars(14.996), beatmapSets, out var beatmapAlmost15);
|
||||
addBeatmapSet(applyStars(15), beatmapSets, out var beatmap15);
|
||||
addBeatmapSet(applyStars(22), beatmapSets, out var beatmap22);
|
||||
|
||||
var results = await runGrouping(GroupMode.Difficulty, beatmapSets);
|
||||
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);
|
||||
assertGroup(results, 4, "13 Stars", beatmap13.Beatmaps, ref total);
|
||||
assertGroup(results, 5, "14 Stars", beatmapAlmost15.Beatmaps, ref total);
|
||||
assertGroup(results, 6, "Over 15 Stars", beatmap15.Beatmaps.Concat(beatmap22.Beatmaps), ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -366,12 +373,40 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
#endregion
|
||||
|
||||
private static async Task<List<CarouselItem>> runGrouping(GroupMode group, List<BeatmapSetInfo> beatmapSets)
|
||||
#region Favourites grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestFavouritesGrouping()
|
||||
{
|
||||
var groupingFilter = new BeatmapCarouselFilterGrouping(
|
||||
() => new FilterCriteria { Group = group },
|
||||
() => new List<BeatmapCollection>(),
|
||||
_ => new Dictionary<Guid, ScoreRank>());
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(s => s.OnlineID = 1, beatmapSets, out _);
|
||||
addBeatmapSet(s => s.OnlineID = 21, beatmapSets, out var firstFavourite);
|
||||
addBeatmapSet(s => s.OnlineID = 321, beatmapSets, out _);
|
||||
addBeatmapSet(s => s.OnlineID = 4321, beatmapSets, out _);
|
||||
addBeatmapSet(s => s.OnlineID = 54321, beatmapSets, out var secondFavourite);
|
||||
|
||||
favouriteBeatmapSets = [21, 54321];
|
||||
|
||||
var results = await runGrouping(GroupMode.Favourites, beatmapSets);
|
||||
assertGroup(results, 0, "Favourites", firstFavourite.Beatmaps.Concat(secondFavourite.Beatmaps), ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private HashSet<int> favouriteBeatmapSets = [];
|
||||
|
||||
private async Task<List<CarouselItem>> runGrouping(GroupMode group, List<BeatmapSetInfo> beatmapSets)
|
||||
{
|
||||
var groupingFilter = new BeatmapCarouselFilterGrouping
|
||||
{
|
||||
GetCriteria = () => new FilterCriteria { Group = group },
|
||||
GetCollections = () => new List<BeatmapCollection>(),
|
||||
GetLocalUserTopRanks = _ => new Dictionary<Guid, ScoreRank>(),
|
||||
GetFavouriteBeatmapSets = () => favouriteBeatmapSets,
|
||||
};
|
||||
|
||||
return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -190,5 +190,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
|
||||
protected void WaitForSuspension() => AddUntilStep("wait for not current", () => !SongSelect.AsNonNull().IsCurrentScreen());
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (Rulesets.IsNotNull())
|
||||
Rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,5 +283,44 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
CheckHasSelection();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetDoesExpandAgainWhenGroupingTurnedOff()
|
||||
{
|
||||
ApplyToFilterAndWaitForFilter("filter", c => c.SearchText = BeatmapSets[2].Metadata.Title);
|
||||
|
||||
CheckDisplayedGroupsCount(1);
|
||||
CheckDisplayedBeatmapSetsCount(1);
|
||||
CheckDisplayedBeatmapsCount(3);
|
||||
|
||||
CheckHasSelection();
|
||||
|
||||
ApplyToFilterAndWaitForFilter("remove filter", c => c.SearchText = string.Empty);
|
||||
CheckDisplayedGroupsCount(5);
|
||||
CheckDisplayedBeatmapSetsCount(10);
|
||||
CheckDisplayedBeatmapsCount(30);
|
||||
|
||||
ToggleGroupCollapse();
|
||||
|
||||
ApplyToFilterAndWaitForFilter("apply no-op filter", c => c.AllowConvertedBeatmaps = !c.AllowConvertedBeatmaps);
|
||||
AddAssert("group didn't re-expand", () => Carousel.ExpandedGroup, () => Is.Null);
|
||||
AddAssert("beatmap set didn't re-expand", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.Zero);
|
||||
|
||||
SortAndGroupBy(SortMode.Title, GroupMode.None);
|
||||
AddAssert("beatmap set did re-expand", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.Not.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManuallyCollapsingCurrentGroupAndOpeningAnother()
|
||||
{
|
||||
SelectNextSet();
|
||||
ToggleGroupCollapse();
|
||||
SelectNextGroup();
|
||||
AddUntilStep("no beatmap panels visible", () => GetVisiblePanels<PanelBeatmap>().Count(), () => Is.Zero);
|
||||
|
||||
SelectNextSet();
|
||||
SelectNextSet();
|
||||
AddUntilStep("no beatmap panels visible", () => GetVisiblePanels<PanelBeatmap>().Count(), () => Is.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,5 +151,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesetStore.IsNotNull())
|
||||
rulesetStore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,5 +578,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesetStore.IsNotNull())
|
||||
rulesetStore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@@ -29,6 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneCollectionDropdown : OsuManualInputManagerTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private CollectionDropdown dropdown = null!;
|
||||
|
||||
@@ -38,7 +40,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -260,5 +262,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
CollectionFilterMenuItem item = dropdown.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
|
||||
return dropdown.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -145,6 +146,62 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatuses()
|
||||
{
|
||||
foreach (var status in Enum.GetValues<BeatmapOnlineStatus>().Where(s => s != BeatmapOnlineStatus.Approved))
|
||||
{
|
||||
AddStep($"display {status} status", () =>
|
||||
{
|
||||
ContentContainer.Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine))
|
||||
},
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new[]
|
||||
{
|
||||
new PanelGroupRankedStatus
|
||||
{
|
||||
Item = new CarouselItem(new RankedStatusGroupDefinition(0, status))
|
||||
},
|
||||
new PanelGroupRankedStatus
|
||||
{
|
||||
Item = new CarouselItem(new RankedStatusGroupDefinition(1, status)),
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
new PanelGroupRankedStatus
|
||||
{
|
||||
Item = new CarouselItem(new RankedStatusGroupDefinition(2, status)),
|
||||
Expanded = { Value = true },
|
||||
},
|
||||
new PanelGroupRankedStatus
|
||||
{
|
||||
Item = new CarouselItem(new RankedStatusGroupDefinition(3, status)),
|
||||
Expanded = { Value = true },
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new OsuContextMenuContainer
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("mods selected", () => SelectedMods.Value, () => Has.Count.EqualTo(1));
|
||||
AddStep("right click mod button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Footer.ChildrenOfType<FooterButtonMods>().Single());
|
||||
InputManager.MoveMouseTo(ScreenFooter.ChildrenOfType<FooterButtonMods>().Single());
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("not mods selected", () => SelectedMods.Value, () => Has.Count.EqualTo(0));
|
||||
@@ -620,7 +620,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||
}
|
||||
|
||||
private FooterButtonRandom randomButton => Footer.ChildrenOfType<FooterButtonRandom>().Single();
|
||||
private FooterButtonRandom randomButton => ScreenFooter.ChildrenOfType<FooterButtonRandom>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestFooterOptions()
|
||||
|
||||
@@ -9,6 +9,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Platform;
|
||||
@@ -37,6 +38,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private readonly ContextMenuContainer contextMenuContainer;
|
||||
private readonly BeatmapLeaderboard leaderboard;
|
||||
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmapManager;
|
||||
private ScoreManager scoreManager;
|
||||
|
||||
@@ -71,7 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get<RulesetStore>(), () => beatmapManager, LocalStorage, Realm, API));
|
||||
Dependencies.Cache(Realm);
|
||||
@@ -151,7 +153,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("click delete option", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase)));
|
||||
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>()
|
||||
.First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase)));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
@@ -178,5 +181,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddUntilStep("wait for fetch", () => leaderboard.Scores.Any());
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set 10 friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = i,
|
||||
@@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set 20 friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(Enumerable.Range(1, 20).Select(i => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(Enumerable.Range(1, 20).Select(i => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = i,
|
||||
@@ -78,8 +78,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set 10 friends", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
api.Friends.Clear();
|
||||
api.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
|
||||
api.LocalUserState.Friends.Clear();
|
||||
api.LocalUserState.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
|
||||
{
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = i,
|
||||
|
||||
@@ -469,5 +469,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Ruleset = rulesets.GetRuleset(3).AsNonNull()
|
||||
}
|
||||
};
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
@@ -1057,6 +1058,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private ModPanel getPanelForMod(Type modType)
|
||||
=> modSelectOverlay.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.GetType() == modType);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesetStore.IsNotNull())
|
||||
rulesetStore.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||
{
|
||||
public TestModSelectOverlay()
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
@@ -21,6 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
protected override bool UseFreshStoragePerRun => true;
|
||||
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
|
||||
private const int item_count = 20;
|
||||
@@ -30,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
@@ -62,5 +64,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
// Ensure all the initial imports are present before running any tests.
|
||||
Realm.Run(r => r.Refresh());
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesets.IsNotNull())
|
||||
rulesets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,19 @@ namespace osu.Game.Audio
|
||||
/// </summary>
|
||||
public bool EditorAutoBank { get; }
|
||||
|
||||
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100, bool editorAutoBank = true)
|
||||
/// <summary>
|
||||
/// Whether the sample can be looked up from the beatmap's skin.
|
||||
/// </summary>
|
||||
public bool UseBeatmapSamples { get; }
|
||||
|
||||
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100, bool editorAutoBank = true, bool useBeatmapSamples = false)
|
||||
{
|
||||
Name = name;
|
||||
Bank = bank;
|
||||
Suffix = suffix;
|
||||
Volume = volume;
|
||||
EditorAutoBank = editorAutoBank;
|
||||
UseBeatmapSamples = useBeatmapSamples;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -99,16 +105,19 @@ namespace osu.Game.Audio
|
||||
/// <param name="newSuffix">An optional new lookup suffix.</param>
|
||||
/// <param name="newVolume">An optional new volume.</param>
|
||||
/// <param name="newEditorAutoBank">An optional new editor auto bank flag.</param>
|
||||
/// <param name="newUseBeatmapSamples">An optional use beatmap samples flag.</param>
|
||||
/// <returns>The new <see cref="HitSampleInfo"/>.</returns>
|
||||
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
|
||||
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank));
|
||||
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default,
|
||||
Optional<bool> newEditorAutoBank = default, Optional<bool> newUseBeatmapSamples = default)
|
||||
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume),
|
||||
newEditorAutoBank.GetOr(EditorAutoBank), newUseBeatmapSamples.GetOr(UseBeatmapSamples));
|
||||
|
||||
public virtual bool Equals(HitSampleInfo? other)
|
||||
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;
|
||||
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix && UseBeatmapSamples == other.UseBeatmapSamples;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is HitSampleInfo other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Name, Bank, Suffix);
|
||||
public override int GetHashCode() => HashCode.Combine(Name, Bank, Suffix, UseBeatmapSamples);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +284,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
||||
/// IMPORTANT: This should not be used outside of tests. Consider using <see cref="RealmDetachedBeatmapStore"/> instead.
|
||||
/// </summary>
|
||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets()
|
||||
@@ -567,6 +568,16 @@ namespace osu.Game.Beatmaps
|
||||
transaction.Commit();
|
||||
});
|
||||
|
||||
public void MarkNotPlayed(BeatmapInfo beatmapSetInfo) => Realm.Run(r =>
|
||||
{
|
||||
using var transaction = r.BeginWrite();
|
||||
|
||||
var beatmap = r.Find<BeatmapInfo>(beatmapSetInfo.ID)!;
|
||||
beatmap.LastPlayed = null;
|
||||
|
||||
transaction.Commit();
|
||||
});
|
||||
|
||||
#region Implementation of ICanAcceptFiles
|
||||
|
||||
public Task Import(params string[] paths) => beatmapImporter.Import(paths);
|
||||
@@ -633,6 +644,14 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID && !s.DeletePending));
|
||||
|
||||
public bool IsAvailableLocally(IBeatmapInfo model)
|
||||
{
|
||||
return Realm.Run(r => r.All<BeatmapInfo>()
|
||||
.Filter($@"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false")
|
||||
.Filter($@"{nameof(BeatmapInfo.OnlineID)} == $0 AND {nameof(BeatmapInfo.MD5Hash)} == {nameof(BeatmapInfo.OnlineMD5Hash)}", model.OnlineID)
|
||||
.Any());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPostImports<out BeatmapSetInfo>
|
||||
|
||||
@@ -187,6 +187,11 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
@"1841885 cYsmix - triangles.osz",
|
||||
// winner of https://osu.ppy.sh/home/news/2023-02-01-twin-trials-contest-beatmapping-phase
|
||||
@"1971987 James Landino - Aresene's Bazaar.osz",
|
||||
// locus 2025 https://osu.ppy.sh/home/news/2025-08-21-locus-2025-results
|
||||
"2412244 Kry.exe - Rift Walker.osz",
|
||||
"2412260 Koto Spirit - Locus of Hexagram.osz",
|
||||
"2412232 Will Stetson - Of Our Time.osz",
|
||||
"2412292 ArXe - Locus Amoenus (feat. Megurine Luka).osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_osu =
|
||||
|
||||
@@ -280,8 +280,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
}
|
||||
|
||||
createStatistics();
|
||||
|
||||
Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID);
|
||||
}
|
||||
|
||||
private LocalisableString createArtistText()
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1));
|
||||
|
||||
SetLoading(false);
|
||||
api.LocalUserState.UpdateFavouriteBeatmapSets();
|
||||
};
|
||||
favouriteRequest.Failure += e =>
|
||||
{
|
||||
|
||||
@@ -321,9 +321,21 @@ namespace osu.Game.Beatmaps.Formats
|
||||
int volume = samples.Max(o => o.Volume);
|
||||
string bank = samples.Where(s => s.Name == HitSampleInfo.HIT_NORMAL).Select(s => s.Bank).FirstOrDefault()
|
||||
?? samples.Select(s => s.Bank).First();
|
||||
int customIndex = samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo)
|
||||
? samples.OfType<ConvertHitObjectParser.LegacyHitSampleInfo>().Max(o => o.CustomSampleBank)
|
||||
: -1;
|
||||
|
||||
int customIndex = samples.Max(s =>
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case ConvertHitObjectParser.LegacyHitSampleInfo legacy:
|
||||
return legacy.CustomSampleBank;
|
||||
|
||||
default:
|
||||
if (int.TryParse(s.Suffix, out int index))
|
||||
return index;
|
||||
|
||||
return s.UseBeatmapSamples ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = time, SampleVolume = volume, SampleBank = bank, CustomSampleBank = customIndex };
|
||||
}
|
||||
|
||||
@@ -132,7 +132,20 @@ namespace osu.Game.Database
|
||||
hasPath.Path.ControlPoints[^1].Type = null;
|
||||
|
||||
if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1
|
||||
&& hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) continue;
|
||||
&& hasPath.Path.ControlPoints[0].Type!.Value.Degree == null)
|
||||
{
|
||||
// Round every control point to integer positions before skipping to the next hit object
|
||||
for (int i = 0; i < hasPath.Path.ControlPoints.Count; i++)
|
||||
{
|
||||
var position = new Vector2(
|
||||
MathF.Round(hasPath.Path.ControlPoints[i].Position.X),
|
||||
MathF.Round(hasPath.Path.ControlPoints[i].Position.Y));
|
||||
|
||||
hasPath.Path.ControlPoints[i].Position = position;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var convertedToBezier = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints);
|
||||
|
||||
@@ -142,10 +155,10 @@ namespace osu.Game.Database
|
||||
{
|
||||
var convertedPoint = convertedToBezier[i];
|
||||
|
||||
// Truncate control points to integer positions
|
||||
// Round control points to integer positions
|
||||
var position = new Vector2(
|
||||
(float)Math.Floor(convertedPoint.Position.X),
|
||||
(float)Math.Floor(convertedPoint.Position.Y));
|
||||
MathF.Round(convertedPoint.Position.X),
|
||||
MathF.Round(convertedPoint.Position.Y));
|
||||
|
||||
// stable only supports a single curve type specification per slider.
|
||||
// we exploit the fact that the converted-to-Bézier path only has Bézier segments,
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1);
|
||||
|
||||
private readonly CountdownEvent pendingAsyncOperations = new CountdownEvent(0);
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> when the current thread has already entered the <see cref="realmRetrievalLock"/>.
|
||||
/// </summary>
|
||||
@@ -467,6 +469,30 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run work on realm on a TPL thread, in a way that ensures that the realm isn't disposed before the work is done.
|
||||
/// </summary>
|
||||
public Task<T> RunAsync<T>(Func<Realm, T> action, CancellationToken token = default)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(isDisposed, this);
|
||||
|
||||
// Required to ensure the read is tracked and accounted for before disposal.
|
||||
// Can potentially be avoided if we have a need to do so in the future.
|
||||
if (!ThreadSafety.IsUpdateThread)
|
||||
throw new InvalidOperationException($@"{nameof(RunAsync)} must be called from the update thread.");
|
||||
|
||||
// CountdownEvent will fail if already at zero.
|
||||
if (!pendingAsyncOperations.TryAddCount())
|
||||
pendingAsyncOperations.Reset(1);
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var result = Run(action);
|
||||
pendingAsyncOperations.Signal();
|
||||
return result;
|
||||
}, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write changes to realm.
|
||||
/// </summary>
|
||||
@@ -507,8 +533,6 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CountdownEvent pendingAsyncWrites = new CountdownEvent(0);
|
||||
|
||||
/// <summary>
|
||||
/// Write changes to realm asynchronously, guaranteeing order of execution.
|
||||
/// </summary>
|
||||
@@ -523,8 +547,8 @@ namespace osu.Game.Database
|
||||
throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread.");
|
||||
|
||||
// CountdownEvent will fail if already at zero.
|
||||
if (!pendingAsyncWrites.TryAddCount())
|
||||
pendingAsyncWrites.Reset(1);
|
||||
if (!pendingAsyncOperations.TryAddCount())
|
||||
pendingAsyncOperations.Reset(1);
|
||||
|
||||
// Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval.
|
||||
// Adding a forced Task.Run resolves this.
|
||||
@@ -539,7 +563,7 @@ namespace osu.Game.Database
|
||||
// ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]).
|
||||
await realm.WriteAsync(() => action(realm)).ConfigureAwait(false);
|
||||
|
||||
pendingAsyncWrites.Signal();
|
||||
pendingAsyncOperations.Signal();
|
||||
});
|
||||
|
||||
return writeTask;
|
||||
@@ -559,8 +583,8 @@ namespace osu.Game.Database
|
||||
throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread.");
|
||||
|
||||
// CountdownEvent will fail if already at zero.
|
||||
if (!pendingAsyncWrites.TryAddCount())
|
||||
pendingAsyncWrites.Reset(1);
|
||||
if (!pendingAsyncOperations.TryAddCount())
|
||||
pendingAsyncOperations.Reset(1);
|
||||
|
||||
// Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval.
|
||||
// Adding a forced Task.Run resolves this.
|
||||
@@ -576,7 +600,7 @@ namespace osu.Game.Database
|
||||
// ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]).
|
||||
result = await realm.WriteAsync(() => action(realm)).ConfigureAwait(false);
|
||||
|
||||
pendingAsyncWrites.Signal();
|
||||
pendingAsyncOperations.Signal();
|
||||
return result;
|
||||
});
|
||||
|
||||
@@ -1494,7 +1518,7 @@ namespace osu.Game.Database
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!pendingAsyncWrites.Wait(10000))
|
||||
if (!pendingAsyncOperations.Wait(10000))
|
||||
Logger.Log("Realm took too long waiting on pending async writes", level: LogLevel.Error);
|
||||
|
||||
updateRealm?.Dispose();
|
||||
|
||||
@@ -82,6 +82,34 @@ namespace osu.Game.Database
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.InsertedIndices.Length == 1 && changes.DeletedIndices.Length == 1)
|
||||
{
|
||||
lock (detachedBeatmapSets)
|
||||
{
|
||||
var deletedSet = detachedBeatmapSets[changes.DeletedIndices[0]];
|
||||
var insertedSet = sender[changes.InsertedIndices[0]];
|
||||
|
||||
// this handles beatmap updates using a heuristic that a beatmap update will preserve the online ID.
|
||||
// it relies on the fact that updates are performed by removing the old set and adding a new one, in a single transaction.
|
||||
// instead of removing the old set and adding a new one to the collection too, which would trigger consumers' logic related to set removals,
|
||||
// move the deleted set to the index occupied by the new one and then replace it in-place.
|
||||
// due to this, the operation can be presented to consumer in a manner that permits them to actually handle this as a replace operation
|
||||
// and not trigger any set removal logic that may result in selections changing or similar undesirable side effects.
|
||||
if (deletedSet.OnlineID == insertedSet.OnlineID)
|
||||
{
|
||||
pendingOperations.Enqueue(new OperationArgs
|
||||
{
|
||||
Type = OperationType.MoveAndReplace,
|
||||
BeatmapSet = insertedSet.Detach(),
|
||||
Index = changes.DeletedIndices[0],
|
||||
NewIndex = changes.InsertedIndices[0],
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int i in changes.DeletedIndices.OrderDescending())
|
||||
{
|
||||
pendingOperations.Enqueue(new OperationArgs
|
||||
@@ -138,6 +166,11 @@ namespace osu.Game.Database
|
||||
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||
break;
|
||||
|
||||
case OperationType.MoveAndReplace:
|
||||
detachedBeatmapSets.Move(op.Index, op.NewIndex!.Value);
|
||||
detachedBeatmapSets.ReplaceRange(op.NewIndex!.Value, 1, [op.BeatmapSet!]);
|
||||
break;
|
||||
|
||||
case OperationType.Remove:
|
||||
detachedBeatmapSets.RemoveAt(op.Index);
|
||||
break;
|
||||
@@ -160,13 +193,15 @@ namespace osu.Game.Database
|
||||
public OperationType Type;
|
||||
public BeatmapSetInfo? BeatmapSet;
|
||||
public int Index;
|
||||
public int? NewIndex;
|
||||
}
|
||||
|
||||
private enum OperationType
|
||||
{
|
||||
Insert,
|
||||
Update,
|
||||
Remove
|
||||
Remove,
|
||||
MoveAndReplace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Logging;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@@ -29,6 +30,7 @@ namespace osu.Game.Database
|
||||
// It may be that we access this from the update thread before a refresh has taken place.
|
||||
// To ensure that behaviour matches what we'd expect (the object generally *should be* available), force
|
||||
// a refresh to bring in any off-thread changes immediately.
|
||||
Logger.Log($"{nameof(FindWithRefresh)} triggered a realm refresh because it couldn't find the requested guid {id}");
|
||||
realm.Refresh();
|
||||
found = realm.Find<T>(id);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace osu.Game.Extensions
|
||||
/// <summary>
|
||||
/// For a given numeric type, return a formatted string in the standard format we use for display everywhere.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Number formatting will abide by <see cref="CultureInfo.CurrentCulture"/>.
|
||||
/// </remarks>
|
||||
/// <param name="value">The numeric value.</param>
|
||||
/// <param name="maxDecimalDigits">The maximum number of decimals to be considered in the original value.</param>
|
||||
/// <param name="asPercentage">Whether the output should be a percentage. For integer types, 0-100 is mapped to 0-100%; for other types 0-1 is mapped to 0-100%.</param>
|
||||
@@ -31,12 +34,12 @@ namespace osu.Game.Extensions
|
||||
if (value is int)
|
||||
floatValue /= 100;
|
||||
|
||||
return floatValue.ToString($@"0.{new string('0', Math.Max(0, significantDigits - 2))}%", CultureInfo.InvariantCulture);
|
||||
return floatValue.ToString($@"0.{new string('0', Math.Max(0, significantDigits - 2))}%", CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
string negativeSign = Math.Round(floatValue, significantDigits) < 0 ? "-" : string.Empty;
|
||||
|
||||
return $"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}", CultureInfo.InvariantCulture)}";
|
||||
return $"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}", CultureInfo.CurrentCulture)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -160,7 +160,18 @@ namespace osu.Game.Graphics.Carousel
|
||||
/// <summary>
|
||||
/// Scroll carousel to the selected item if available.
|
||||
/// </summary>
|
||||
public void ScrollToSelection() => scrollToSelection.Invalidate();
|
||||
/// <param name="immediate">
|
||||
/// Whether the scroll position should immediately be shifted to the target, delegating animation to visible panels.
|
||||
/// This should be true for operations like filtering - where panels are changing visibility state - to avoid large jumps in animation.
|
||||
/// </param>
|
||||
public void ScrollToSelection(bool immediate = false)
|
||||
{
|
||||
// if an immediate scroll is already requested, don't override it with a slower scroll
|
||||
if (scrollToSelection == PendingScrollOperation.Immediate)
|
||||
return;
|
||||
|
||||
scrollToSelection = immediate ? PendingScrollOperation.Immediate : PendingScrollOperation.Standard;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the vertical spacing between two given carousel items. Negative value can be used to create an overlapping effect.
|
||||
@@ -400,7 +411,7 @@ namespace osu.Game.Graphics.Carousel
|
||||
|
||||
refreshAfterSelection();
|
||||
if (!Scroll.UserScrolling)
|
||||
ScrollToSelection();
|
||||
ScrollToSelection(immediate: true);
|
||||
|
||||
NewItemsPresented?.Invoke(carouselItems);
|
||||
});
|
||||
@@ -681,6 +692,23 @@ namespace osu.Game.Graphics.Carousel
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scrolling
|
||||
|
||||
/// <summary>
|
||||
/// Scrolling to selection relies on <see cref="currentKeyboardSelection"/> being fully populated.
|
||||
/// This flag ensures it runs after <see cref="refreshAfterSelection"/> validates this.
|
||||
/// </summary>
|
||||
private PendingScrollOperation scrollToSelection = PendingScrollOperation.None;
|
||||
|
||||
private enum PendingScrollOperation
|
||||
{
|
||||
None,
|
||||
Standard,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audio
|
||||
|
||||
private Sample? sampleKeyboardTraversal;
|
||||
@@ -761,13 +789,26 @@ namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
var item = carouselItems[i];
|
||||
|
||||
bool isKeyboardSelection = CheckModelEquality(item.Model, currentKeyboardSelection.Model!);
|
||||
bool isSelection = CheckModelEquality(item.Model, currentSelection.Model!);
|
||||
|
||||
// while we don't know the Y position of the item yet, as it's about to be updated,
|
||||
// consumers (specifically `BeatmapCarousel.GetSpacingBetweenPanels()`) benefit from `CurrentSelectionItem` already pointing
|
||||
// at the correct item to avoid redundant local equality checks.
|
||||
// the Y positions will be filled in after they're computed.
|
||||
if (isKeyboardSelection)
|
||||
currentKeyboardSelection = new Selection(currentKeyboardSelection.Model, item, null, i);
|
||||
|
||||
if (isSelection)
|
||||
currentSelection = new Selection(currentSelection.Model, item, null, i);
|
||||
|
||||
updateItemYPosition(item, ref lastVisible, ref yPos);
|
||||
|
||||
if (CheckModelEquality(item.Model, currentKeyboardSelection.Model!))
|
||||
currentKeyboardSelection = new Selection(currentKeyboardSelection.Model, item, item.CarouselYPosition + item.DrawHeight / 2, i);
|
||||
if (isKeyboardSelection)
|
||||
currentKeyboardSelection = currentKeyboardSelection with { YPosition = item.CarouselYPosition + item.DrawHeight / 2 };
|
||||
|
||||
if (CheckModelEquality(item.Model, currentSelection.Model!))
|
||||
currentSelection = new Selection(currentSelection.Model, item, item.CarouselYPosition + item.DrawHeight / 2, i);
|
||||
if (isSelection)
|
||||
currentSelection = currentSelection with { YPosition = item.CarouselYPosition + item.DrawHeight / 2 };
|
||||
}
|
||||
|
||||
// Update the total height of all items (to make the scroll container scrollable through the full height even though
|
||||
@@ -808,12 +849,6 @@ namespace osu.Game.Graphics.Carousel
|
||||
/// </summary>
|
||||
private readonly Cached filterReusesPanels = new Cached();
|
||||
|
||||
/// <summary>
|
||||
/// Scrolling to selection relies on <see cref="currentKeyboardSelection"/> being fully populated.
|
||||
/// This flag ensures it runs after <see cref="refreshAfterSelection"/> validates this.
|
||||
/// </summary>
|
||||
private readonly Cached scrollToSelection = new Cached();
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@@ -874,12 +909,12 @@ namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (!scrollToSelection.IsValid)
|
||||
if (scrollToSelection != PendingScrollOperation.None)
|
||||
{
|
||||
if (GetScrollTarget() is double scrollTarget)
|
||||
Scroll.ScrollTo(scrollTarget - visibleHalfHeight + BleedTop);
|
||||
Scroll.ScrollTo(scrollTarget - visibleHalfHeight + BleedTop, animated: scrollToSelection == PendingScrollOperation.Standard);
|
||||
|
||||
scrollToSelection.Validate();
|
||||
scrollToSelection = PendingScrollOperation.None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace osu.Game.Graphics
|
||||
/// Retrieves colour for a <see cref="RankingTier"/>.
|
||||
/// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours
|
||||
/// </summary>
|
||||
public ColourInfo ForRankingTier(RankingTier tier)
|
||||
public static ColourInfo ForRankingTier(RankingTier tier)
|
||||
{
|
||||
switch (tier)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public class BreakInfoStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.BreakInfo";
|
||||
|
||||
/// <summary>
|
||||
/// "Current Progress"
|
||||
/// </summary>
|
||||
public static LocalisableString CurrentProgressTitle => new TranslatableString(getKey(@"current_progress_title"), @"Current Progress");
|
||||
|
||||
/// <summary>
|
||||
/// "Grade"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowInfoGrade => new TranslatableString(getKey(@"show_info_grade"), @"Grade");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Details => new TranslatableString(getKey(@"details"), @"Details...");
|
||||
|
||||
/// <summary>
|
||||
/// "Mapper"
|
||||
/// </summary>
|
||||
public static LocalisableString Mapper => new TranslatableString(getKey(@"mapper"), @"Mapper");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,61 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString SeekForwardSeconds(double arg0) => new TranslatableString(getKey(@"seek_forward_seconds"), @"Seek forward {0} seconds", arg0);
|
||||
|
||||
/// <summary>
|
||||
/// "Playback speed"
|
||||
/// </summary>
|
||||
public static LocalisableString PlaybackSpeed => new TranslatableString(getKey(@"playback_speed"), @"Playback speed");
|
||||
|
||||
/// <summary>
|
||||
/// "Show click markers"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowClickMarkers => new TranslatableString(getKey(@"show_click_markers"), @"Show click markers");
|
||||
|
||||
/// <summary>
|
||||
/// "Show frame markers"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowFrameMarkers => new TranslatableString(getKey(@"show_frame_markers"), @"Show frame markers");
|
||||
|
||||
/// <summary>
|
||||
/// "Show cursor path"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowCursorPath => new TranslatableString(getKey(@"show_cursor_path"), @"Show cursor path");
|
||||
|
||||
/// <summary>
|
||||
/// "Hide gameplay cursor"
|
||||
/// </summary>
|
||||
public static LocalisableString HideGameplayCursor => new TranslatableString(getKey(@"hide_gameplay_cursor"), @"Hide gameplay cursor");
|
||||
|
||||
/// <summary>
|
||||
/// "Display length"
|
||||
/// </summary>
|
||||
public static LocalisableString DisplayLength => new TranslatableString(getKey(@"display_length"), @"Display length");
|
||||
|
||||
/// <summary>
|
||||
/// "Playback"
|
||||
/// </summary>
|
||||
public static LocalisableString PlaybackTitle => new TranslatableString(getKey(@"playback_title"), @"Playback");
|
||||
|
||||
/// <summary>
|
||||
/// "Visual Settings"
|
||||
/// </summary>
|
||||
public static LocalisableString VisualSettingsTitle => new TranslatableString(getKey(@"visual_settings_title"), @"Visual Settings");
|
||||
|
||||
/// <summary>
|
||||
/// "Audio Settings"
|
||||
/// </summary>
|
||||
public static LocalisableString AudioSettingsTitle => new TranslatableString(getKey(@"audio_settings_title"), @"Audio Settings");
|
||||
|
||||
/// <summary>
|
||||
/// "Input Settings"
|
||||
/// </summary>
|
||||
public static LocalisableString InputSettingsTitle => new TranslatableString(getKey(@"input_settings_title"), @"Input Settings");
|
||||
|
||||
/// <summary>
|
||||
/// "Analysis Settings"
|
||||
/// </summary>
|
||||
public static LocalisableString AnalysisSettingsTitle => new TranslatableString(getKey(@"analysis_settings_title"), @"Analysis Settings");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString MarkAsPlayed => new TranslatableString(getKey(@"mark_as_played"), @"Mark as played");
|
||||
|
||||
/// <summary>
|
||||
/// "Remove from played"
|
||||
/// </summary>
|
||||
public static LocalisableString RemoveFromPlayed => new TranslatableString(getKey(@"remove_from_played"), @"Remove from played");
|
||||
|
||||
/// <summary>
|
||||
/// "Clear all local scores"
|
||||
/// </summary>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
@@ -18,7 +17,7 @@ using osu.Framework.Development;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ExceptionExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
@@ -26,11 +25,10 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Notifications.WebSocket;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public partial class APIAccess : Component, IAPIProvider
|
||||
public partial class APIAccess : CompositeComponent, IAPIProvider
|
||||
{
|
||||
private readonly OsuGameBase game;
|
||||
private readonly OsuConfigManager config;
|
||||
@@ -53,30 +51,23 @@ namespace osu.Game.Online.API
|
||||
|
||||
public string ProvidedUsername { get; private set; }
|
||||
|
||||
public SessionVerificationMethod? SessionVerificationMethod { get; set; }
|
||||
public SessionVerificationMethod? SessionVerificationMethod { get; private set; }
|
||||
|
||||
public string SecondFactorCode { get; private set; }
|
||||
|
||||
private string password;
|
||||
|
||||
public IBindable<APIUser> LocalUser => localUser;
|
||||
public IBindableList<APIRelation> Friends => friends;
|
||||
public IBindableList<APIRelation> Blocks => blocks;
|
||||
public IBindable<APIUser> LocalUser => localUserState.User;
|
||||
|
||||
public ILocalUserState LocalUserState => localUserState;
|
||||
private readonly LocalUserState localUserState;
|
||||
|
||||
public INotificationsClient NotificationsClient { get; }
|
||||
|
||||
public Language Language => game.CurrentLanguage.Value;
|
||||
|
||||
private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());
|
||||
|
||||
private BindableList<APIRelation> friends { get; } = new BindableList<APIRelation>();
|
||||
private BindableList<APIRelation> blocks { get; } = new BindableList<APIRelation>();
|
||||
|
||||
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
|
||||
|
||||
private readonly Bindable<UserStatus> configStatus = new Bindable<UserStatus>();
|
||||
private readonly Bindable<bool> configSupporter = new Bindable<bool>();
|
||||
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
private readonly Logger log;
|
||||
|
||||
@@ -108,13 +99,12 @@ namespace osu.Game.Online.API
|
||||
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
||||
authentication.Token.ValueChanged += onTokenChanged;
|
||||
|
||||
config.BindWith(OsuSetting.UserOnlineStatus, configStatus);
|
||||
config.BindWith(OsuSetting.WasSupporter, configSupporter);
|
||||
AddInternal(localUserState = new LocalUserState(this, config));
|
||||
|
||||
if (HasLogin)
|
||||
{
|
||||
// Early call to ensure the local user / "logged in" state is correct immediately.
|
||||
setPlaceholderLocalUser();
|
||||
localUserState.SetPlaceholderLocalUser(ProvidedUsername);
|
||||
|
||||
// This is required so that Queue() requests during startup sequence don't fail due to "not logged in".
|
||||
state.Value = APIState.Connecting;
|
||||
@@ -249,8 +239,8 @@ namespace osu.Game.Online.API
|
||||
/// <returns>Whether the connection attempt was successful.</returns>
|
||||
private void attemptConnect()
|
||||
{
|
||||
if (localUser.IsDefault)
|
||||
Scheduler.Add(setPlaceholderLocalUser, false);
|
||||
if (LocalUser.IsDefault)
|
||||
Scheduler.Add(localUserState.SetPlaceholderLocalUser, ProvidedUsername, false);
|
||||
|
||||
// save the username at this point, if the user requested for it to be.
|
||||
config.SetValue(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||
@@ -348,8 +338,7 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||
|
||||
localUser.Value = me;
|
||||
configSupporter.Value = me.IsSupporter;
|
||||
localUserState.SetLocalUser(me);
|
||||
SessionVerificationMethod = me.SessionVerificationMethod;
|
||||
state.Value = SessionVerificationMethod == null ? APIState.Online : APIState.RequiresSecondFactorAuth;
|
||||
failureCount = 0;
|
||||
@@ -365,8 +354,6 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLocalFriends();
|
||||
|
||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||
// before actually going online.
|
||||
@@ -374,23 +361,6 @@ namespace osu.Game.Online.API
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a placeholder user if saved credentials are available.
|
||||
/// This is useful for storing local scores and showing a placeholder username after starting the game,
|
||||
/// until a valid connection has been established.
|
||||
/// </summary>
|
||||
private void setPlaceholderLocalUser()
|
||||
{
|
||||
if (!localUser.IsDefault)
|
||||
return;
|
||||
|
||||
localUser.Value = new APIUser
|
||||
{
|
||||
Username = ProvidedUsername,
|
||||
IsSupporter = configSupporter.Value,
|
||||
};
|
||||
}
|
||||
|
||||
public void Perform(APIRequest request)
|
||||
{
|
||||
try
|
||||
@@ -619,78 +589,12 @@ namespace osu.Game.Online.API
|
||||
SecondFactorCode = null;
|
||||
authentication.Clear();
|
||||
|
||||
// Reset the status to be broadcast on the next login, in case multiple players share the same system.
|
||||
configStatus.Value = UserStatus.Online;
|
||||
|
||||
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present
|
||||
Schedule(() =>
|
||||
{
|
||||
localUser.Value = createGuestUser();
|
||||
configSupporter.Value = false;
|
||||
friends.Clear();
|
||||
});
|
||||
localUserState.ClearLocalUser();
|
||||
|
||||
state.Value = APIState.Offline;
|
||||
flushQueue();
|
||||
}
|
||||
|
||||
public void UpdateLocalFriends()
|
||||
{
|
||||
if (!IsLoggedIn)
|
||||
return;
|
||||
|
||||
var friendsReq = new GetFriendsRequest();
|
||||
friendsReq.Failure += ex =>
|
||||
{
|
||||
if (ex is not WebRequestFlushedException)
|
||||
state.Value = APIState.Failing;
|
||||
};
|
||||
friendsReq.Success += res =>
|
||||
{
|
||||
var existingFriends = friends.Select(f => f.TargetID).ToHashSet();
|
||||
var updatedFriends = res.Select(f => f.TargetID).ToHashSet();
|
||||
|
||||
// Add new friends into local list.
|
||||
friends.AddRange(res.Where(r => !existingFriends.Contains(r.TargetID)));
|
||||
|
||||
// Remove non-friends from local list.
|
||||
friends.RemoveAll(f => !updatedFriends.Contains(f.TargetID));
|
||||
};
|
||||
|
||||
Queue(friendsReq);
|
||||
}
|
||||
|
||||
public void UpdateLocalBlocks()
|
||||
{
|
||||
if (!IsLoggedIn)
|
||||
return;
|
||||
|
||||
var blocksReq = new GetBlocksRequest();
|
||||
blocksReq.Failure += ex =>
|
||||
{
|
||||
if (ex is not WebRequestFlushedException)
|
||||
state.Value = APIState.Failing;
|
||||
};
|
||||
blocksReq.Success += res =>
|
||||
{
|
||||
var existingBlocks = blocks.Select(f => f.TargetID).ToHashSet();
|
||||
var updatedBlocks = res.Select(f => f.TargetID).ToHashSet();
|
||||
|
||||
// Add new blocked users to local list.
|
||||
blocks.AddRange(res.Where(r => !existingBlocks.Contains(r.TargetID)));
|
||||
|
||||
// Remove non-blocked users from local list.
|
||||
blocks.RemoveAll(b => !updatedBlocks.Contains(b.TargetID));
|
||||
|
||||
// Remove friends who got blocked since last check.
|
||||
friends.RemoveAll(f => updatedBlocks.Contains(f.TargetID));
|
||||
};
|
||||
|
||||
Queue(blocksReq);
|
||||
}
|
||||
|
||||
private static APIUser createGuestUser() => new GuestUser();
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
@@ -20,14 +20,11 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
public const int DUMMY_USER_ID = 1001;
|
||||
|
||||
public Bindable<APIUser> LocalUser { get; } = new Bindable<APIUser>(new APIUser
|
||||
{
|
||||
Username = @"Local user",
|
||||
Id = DUMMY_USER_ID,
|
||||
});
|
||||
public DummyLocalUserState LocalUserState { get; } = new DummyLocalUserState();
|
||||
public Bindable<APIUser> LocalUser => LocalUserState.User;
|
||||
|
||||
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();
|
||||
public BindableList<APIRelation> Blocks { get; } = new BindableList<APIRelation>();
|
||||
ILocalUserState IAPIProvider.LocalUserState => LocalUserState;
|
||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||
|
||||
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
|
||||
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
|
||||
@@ -208,10 +205,6 @@ namespace osu.Game.Online.API
|
||||
|
||||
public void SetState(APIState newState) => state.Value = newState;
|
||||
|
||||
IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
|
||||
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
|
||||
IBindableList<APIRelation> IAPIProvider.Blocks => Blocks;
|
||||
|
||||
/// <summary>
|
||||
/// Skip 2FA requirement for next login.
|
||||
/// </summary>
|
||||
@@ -234,5 +227,35 @@ namespace osu.Game.Online.API
|
||||
// Ensure (as much as we can) that any pending tasks are run.
|
||||
Scheduler.Update();
|
||||
}
|
||||
|
||||
public class DummyLocalUserState : ILocalUserState
|
||||
{
|
||||
public Bindable<APIUser> User { get; } = new Bindable<APIUser>(new APIUser
|
||||
{
|
||||
Username = @"Local user",
|
||||
Id = DUMMY_USER_ID,
|
||||
});
|
||||
|
||||
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();
|
||||
public BindableList<APIRelation> Blocks { get; } = new BindableList<APIRelation>();
|
||||
public BindableList<int> FavouriteBeatmapSets { get; } = new BindableList<int>();
|
||||
|
||||
IBindable<APIUser> ILocalUserState.User => User;
|
||||
IBindableList<APIRelation> ILocalUserState.Friends => Friends;
|
||||
IBindableList<APIRelation> ILocalUserState.Blocks => Blocks;
|
||||
IBindableList<int> ILocalUserState.FavouriteBeatmapSets => FavouriteBeatmapSets;
|
||||
|
||||
public void UpdateFriends()
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateBlocks()
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateFavouriteBeatmapSets()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,11 @@ namespace osu.Game.Online.API
|
||||
IBindable<APIUser> LocalUser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's friends.
|
||||
/// The local user's current state.
|
||||
/// Contains auxiliary information such as the user's friends, blocks, and favourites,
|
||||
/// as well as methods to manage those in a way that keeps this state consistent throughout the game.
|
||||
/// </summary>
|
||||
IBindableList<APIRelation> Friends { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The users blocked by the local user.
|
||||
/// </summary>
|
||||
IBindableList<APIRelation> Blocks { get; }
|
||||
ILocalUserState LocalUserState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The language supplied by this provider to API requests.
|
||||
@@ -123,16 +120,6 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
void Logout();
|
||||
|
||||
/// <summary>
|
||||
/// Update the friends status of the current user.
|
||||
/// </summary>
|
||||
void UpdateLocalFriends();
|
||||
|
||||
/// <summary>
|
||||
/// Update the list of users blocked by the current user.
|
||||
/// </summary>
|
||||
void UpdateLocalBlocks();
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a callback to run on the update thread.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user