1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 09:02:58 +08:00

Merge branch 'master' into beatmap-card/download-button

This commit is contained in:
Bartłomiej Dach 2021-11-18 22:37:35 +01:00
commit 8a1d6757af
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
152 changed files with 1797 additions and 495 deletions

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform
{ {
public class EmptyFreeformDifficultyCalculator : DifficultyCalculator public class EmptyFreeformDifficultyCalculator : DifficultyCalculator
{ {
public EmptyFreeformDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public EmptyFreeformDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.EmptyFreeform
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
new EmptyFreeformBeatmapConverter(beatmap, this); new EmptyFreeformBeatmapConverter(beatmap, this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) =>
new EmptyFreeformDifficultyCalculator(this, beatmap); new EmptyFreeformDifficultyCalculator(RulesetInfo, beatmap);
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon
{ {
public class PippidonDifficultyCalculator : DifficultyCalculator public class PippidonDifficultyCalculator : DifficultyCalculator
{ {
public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Pippidon
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
new PippidonBeatmapConverter(beatmap, this); new PippidonBeatmapConverter(beatmap, this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) =>
new PippidonDifficultyCalculator(this, beatmap); new PippidonDifficultyCalculator(RulesetInfo, beatmap);
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling
{ {
public class EmptyScrollingDifficultyCalculator : DifficultyCalculator public class EmptyScrollingDifficultyCalculator : DifficultyCalculator
{ {
public EmptyScrollingDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public EmptyScrollingDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyScrolling
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new EmptyScrollingBeatmapConverter(beatmap, this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new EmptyScrollingBeatmapConverter(beatmap, this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new EmptyScrollingDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new EmptyScrollingDifficultyCalculator(RulesetInfo, beatmap);
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon
{ {
public class PippidonDifficultyCalculator : DifficultyCalculator public class PippidonDifficultyCalculator : DifficultyCalculator
{ {
public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Pippidon
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PippidonBeatmapConverter(beatmap, this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PippidonBeatmapConverter(beatmap, this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new PippidonDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new PippidonDifficultyCalculator(RulesetInfo, beatmap);
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1108.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.1118.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -93,6 +93,11 @@ namespace osu.Desktop
protected override UpdateManager CreateUpdateManager() protected override UpdateManager CreateUpdateManager()
{ {
string packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
if (!string.IsNullOrEmpty(packageManaged))
return new NoActionUpdateManager();
switch (RuntimeInfo.OS) switch (RuntimeInfo.OS)
{ {
case RuntimeInfo.Platform.Windows: case RuntimeInfo.Platform.Windows:

View File

@ -183,6 +183,19 @@ namespace osu.Desktop.Updater
} }
}); });
} }
public override void Close()
{
// cancelling updates is not currently supported by the underlying updater.
// only allow dismissing for now.
switch (State)
{
case ProgressNotificationState.Cancelled:
base.Close();
break;
}
}
} }
private class SquirrelLogger : Splat.ILogger, IDisposable private class SquirrelLogger : Splat.ILogger, IDisposable

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new CatchModDoubleTime()); => Test(expected, name, new CatchModDoubleTime());
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset().RulesetInfo, beatmap);
protected override Ruleset CreateRuleset() => new CatchRuleset(); protected override Ruleset CreateRuleset() => new CatchRuleset();
} }

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
var controlPointInfo = new ControlPointInfo(); var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint()); controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{ {
HitObjects = new List<HitObject> { new Fruit() }, HitObjects = new List<HitObject> { new Fruit() },
BeatmapInfo = new BeatmapInfo BeatmapInfo = new BeatmapInfo

View File

@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Catch
return base.GetDisplayNameForHitResult(result); return base.GetDisplayNameForHitResult(result);
} }
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(RulesetInfo, beatmap);
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin); public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin);

View File

@ -1,12 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchDifficultyAttributes : DifficultyAttributes public class CatchDifficultyAttributes : DifficultyAttributes
{ {
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; } public double ApproachRate { get; set; }
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;
// Todo: osu!catch should not output star rating in the 'aim' attribute.
yield return (ATTRIB_ID_AIM, StarRating);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
{
base.FromDatabaseAttributes(values);
StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
}
} }
} }

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
private float halfCatcherWidth; private float halfCatcherWidth;
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new ManiaModDoubleTime()); => Test(expected, name, new ManiaModDoubleTime());
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, beatmap);
protected override Ruleset CreateRuleset() => new ManiaRuleset(); protected override Ruleset CreateRuleset() => new ManiaRuleset();
} }

View File

@ -1,13 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Mania.Difficulty namespace osu.Game.Rulesets.Mania.Difficulty
{ {
public class ManiaDifficultyAttributes : DifficultyAttributes public class ManiaDifficultyAttributes : DifficultyAttributes
{ {
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; } public double GreatHitWindow { get; set; }
[JsonProperty("score_multiplier")]
public double ScoreMultiplier { get; set; } public double ScoreMultiplier { get; set; }
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
{
base.FromDatabaseAttributes(values);
StarRating = values[ATTRIB_ID_STRAIN];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
}
} }
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
@ -28,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private readonly bool isForCurrentRuleset; private readonly bool isForCurrentRuleset;
private readonly double originalOverallDifficulty; private readonly double originalOverallDifficulty;
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
originalOverallDifficulty = beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
} }
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)

View File

@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new ManiaDifficultyCalculator(RulesetInfo, beatmap);
public int LegacyID => 3; public int LegacyID => 3;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime()); => Test(expected, name, new OsuModDoubleTime());
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap);
protected override Ruleset CreateRuleset() => new OsuRuleset(); protected override Ruleset CreateRuleset() => new OsuRuleset();
} }

View File

@ -1,21 +1,77 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Difficulty namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public class OsuDifficultyAttributes : DifficultyAttributes public class OsuDifficultyAttributes : DifficultyAttributes
{ {
[JsonProperty("aim_strain")]
public double AimStrain { get; set; } public double AimStrain { get; set; }
[JsonProperty("speed_strain")]
public double SpeedStrain { get; set; } public double SpeedStrain { get; set; }
[JsonProperty("flashlight_rating")]
public double FlashlightRating { get; set; } public double FlashlightRating { get; set; }
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; } public double SliderFactor { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; } public double ApproachRate { get; set; }
[JsonProperty("overall_difficulty")]
public double OverallDifficulty { get; set; } public double OverallDifficulty { get; set; }
public double DrainRate { get; set; } public double DrainRate { get; set; }
public int HitCircleCount { get; set; } public int HitCircleCount { get; set; }
public int SliderCount { get; set; } public int SliderCount { get; set; }
public int SpinnerCount { get; set; } public int SpinnerCount { get; set; }
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;
yield return (ATTRIB_ID_AIM, AimStrain);
yield return (ATTRIB_ID_SPEED, SpeedStrain);
yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_STRAIN, StarRating);
if (ShouldSerializeFlashlightRating())
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightRating);
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
{
base.FromDatabaseAttributes(values);
AimStrain = values[ATTRIB_ID_AIM];
SpeedStrain = values[ATTRIB_ID_SPEED];
OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_STRAIN];
FlashlightRating = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
}
// Used implicitly by Newtonsoft.Json to not serialize flashlight property in some cases.
[UsedImplicitly]
public bool ShouldSerializeFlashlightRating() => Mods.Any(m => m is ModFlashlight);
} }
} }

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private const double difficulty_multiplier = 0.0675; private const double difficulty_multiplier = 0.0675;
private double hitWindowGreat; private double hitWindowGreat;
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score);

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
var controlPointInfo = new ControlPointInfo(); var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint()); controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{ {
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } }, HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo BeatmapInfo = new BeatmapInfo

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new TaikoModDoubleTime()); => Test(expected, name, new TaikoModDoubleTime());
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset().RulesetInfo, beatmap);
protected override Ruleset CreateRuleset() => new TaikoRuleset(); protected override Ruleset CreateRuleset() => new TaikoRuleset();
} }

View File

@ -1,16 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Taiko.Difficulty namespace osu.Game.Rulesets.Taiko.Difficulty
{ {
public class TaikoDifficultyAttributes : DifficultyAttributes public class TaikoDifficultyAttributes : DifficultyAttributes
{ {
[JsonProperty("stamina_strain")]
public double StaminaStrain { get; set; } public double StaminaStrain { get; set; }
[JsonProperty("rhythm_strain")]
public double RhythmStrain { get; set; } public double RhythmStrain { get; set; }
[JsonProperty("colour_strain")]
public double ColourStrain { get; set; } public double ColourStrain { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; } public double ApproachRate { get; set; }
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; } public double GreatHitWindow { get; set; }
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
{
base.FromDatabaseAttributes(values);
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_STRAIN];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
} }
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private const double colour_skill_multiplier = 0.01; private const double colour_skill_multiplier = 0.01;
private const double stamina_skill_multiplier = 0.02; private const double stamina_skill_multiplier = 0.02;
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }

View File

@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Taiko
public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score); public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score);

View File

@ -156,7 +156,7 @@ namespace osu.Game.Tests.Mods
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
namespace osu.Game.Tests.NonVisual namespace osu.Game.Tests.NonVisual
{ {
@ -15,7 +16,8 @@ namespace osu.Game.Tests.NonVisual
var ourInfo = new BeatmapSetInfo { OnlineID = 123 }; var ourInfo = new BeatmapSetInfo { OnlineID = 123 };
var otherInfo = new BeatmapSetInfo { OnlineID = 123 }; var otherInfo = new BeatmapSetInfo { OnlineID = 123 };
Assert.AreEqual(ourInfo, otherInfo); Assert.AreNotEqual(ourInfo, otherInfo);
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
} }
[Test] [Test]
@ -33,7 +35,8 @@ namespace osu.Game.Tests.NonVisual
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 }; var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineID = 12 }; var otherInfo = new BeatmapSetInfo { OnlineID = 12 };
Assert.AreEqual(ourInfo, otherInfo); Assert.AreNotEqual(ourInfo, otherInfo);
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
} }
[Test] [Test]

View File

@ -128,7 +128,7 @@ namespace osu.Game.Tests.Online
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty; public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty; public override string ShortName { get; } = string.Empty;

View File

@ -90,7 +90,7 @@ namespace osu.Game.Tests.Online
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new System.NotImplementedException();
public override string Description { get; } = string.Empty; public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty; public override string ShortName { get; } = string.Empty;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Skins
[Resolved] [Resolved]
private BeatmapManager beatmaps { get; set; } private BeatmapManager beatmaps { get; set; }
private WorkingBeatmap beatmap; private IWorkingBeatmap beatmap;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -79,7 +79,7 @@ namespace osu.Game.Tests.Testing
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>(); public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null; public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null;
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null; public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null; public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
} }
private class TestRulesetConfigManager : IRulesetConfigManager private class TestRulesetConfigManager : IRulesetConfigManager

View File

@ -10,9 +10,13 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.GameplayTest;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
@ -58,6 +62,42 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
AddStep("exit player", () => editorPlayer.Exit()); AddStep("exit player", () => editorPlayer.Exit());
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () =>
{
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});
}
[Test]
public void TestGameplayTestWhenTrackRunning()
{
AddStep("start track", () => EditorClock.Start());
AddAssert("sample playback enabled", () => !Editor.SamplePlaybackDisabled.Value);
AddStep("click test gameplay button", () =>
{
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
EditorPlayer editorPlayer = null;
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
AddAssert("editor track stopped", () => !EditorClock.IsRunning);
AddAssert("sample playback disabled", () => Editor.SamplePlaybackDisabled.Value);
AddStep("exit player", () => editorPlayer.Exit());
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("background has correct params", () =>
{
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
});
AddStep("start track", () => EditorClock.Start());
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
} }
[Test] [Test]
@ -111,6 +151,35 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning);
} }
[Test]
public void TestSharedClockState()
{
AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000));
AddStep("click test gameplay button", () =>
{
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
EditorPlayer editorPlayer = null;
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
GameplayClockContainer gameplayClockContainer = null;
AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First());
AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning);
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
double timeAtPlayerExit = 0;
AddWaitStep("wait some", 5);
AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime);
AddStep("exit player", () => editorPlayer.Exit());
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddAssert("time is past player exit", () => EditorClock.CurrentTime >= timeAtPlayerExit);
}
public override void TearDownSteps() public override void TearDownSteps()
{ {
base.TearDownSteps(); base.TearDownSteps();

View File

@ -81,11 +81,11 @@ namespace osu.Game.Tests.Visual.Editing
public class EditorBeatmapContainer : Container public class EditorBeatmapContainer : Container
{ {
private readonly WorkingBeatmap working; private readonly IWorkingBeatmap working;
public EditorBeatmap EditorBeatmap { get; private set; } public EditorBeatmap EditorBeatmap { get; private set; }
public EditorBeatmapContainer(WorkingBeatmap working) public EditorBeatmapContainer(IWorkingBeatmap working)
{ {
this.working = working; this.working = working;

View File

@ -0,0 +1,171 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneHitObjectDifficultyPointAdjustments : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("add test objects", () =>
{
EditorBeatmap.Add(new Slider
{
StartTime = 0,
Position = (OsuPlayfield.BASE_SIZE - new Vector2(0, 100)) / 2,
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(new Vector2(0, 0)),
new PathControlPoint(new Vector2(0, 100))
}
}
});
EditorBeatmap.Add(new Slider
{
StartTime = 500,
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(new Vector2(0, 0)),
new PathControlPoint(new Vector2(100, 0))
}
},
DifficultyControlPoint = new DifficultyControlPoint
{
SliderVelocity = 2
}
});
});
}
[Test]
public void TestSingleSelection()
{
clickDifficultyPiece(0);
velocityPopoverHasSingleValue(1);
dismissPopover();
// select first object to ensure that difficulty pieces for unselected objects
// work independently from selection state.
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
clickDifficultyPiece(1);
velocityPopoverHasSingleValue(2);
setVelocityViaPopover(5);
hitObjectHasVelocity(1, 5);
}
[Test]
public void TestMultipleSelectionWithSameSliderVelocity()
{
AddStep("unify slider velocity", () =>
{
foreach (var h in EditorBeatmap.HitObjects)
h.DifficultyControlPoint.SliderVelocity = 1.5;
});
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
clickDifficultyPiece(0);
velocityPopoverHasSingleValue(1.5);
dismissPopover();
clickDifficultyPiece(1);
velocityPopoverHasSingleValue(1.5);
setVelocityViaPopover(5);
hitObjectHasVelocity(0, 5);
hitObjectHasVelocity(1, 5);
}
[Test]
public void TestMultipleSelectionWithDifferentSliderVelocity()
{
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
clickDifficultyPiece(0);
velocityPopoverHasIndeterminateValue();
dismissPopover();
clickDifficultyPiece(1);
velocityPopoverHasIndeterminateValue();
setVelocityViaPopover(3);
hitObjectHasVelocity(0, 3);
hitObjectHasVelocity(1, 3);
}
private void clickDifficultyPiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () =>
{
var difficultyPiece = this.ChildrenOfType<DifficultyPointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
InputManager.MoveMouseTo(difficultyPiece);
InputManager.Click(MouseButton.Left);
});
private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () =>
{
var popover = this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().SingleOrDefault();
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().Single();
return slider?.Current.Value == velocity;
});
private void velocityPopoverHasIndeterminateValue() => AddUntilStep("velocity popover has indeterminate value", () =>
{
var popover = this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().SingleOrDefault();
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().Single();
return slider != null && slider.Current.Value == null;
});
private void dismissPopover()
{
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
AddUntilStep("wait for dismiss", () => !this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().Any(popover => popover.IsPresent));
}
private void setVelocityViaPopover(double velocity) => AddStep($"set {velocity} via popover", () =>
{
var popover = this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().Single();
var slider = popover.ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().Single();
slider.Current.Value = velocity;
});
private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.DifficultyControlPoint.SliderVelocity == velocity;
});
}
}

View File

@ -0,0 +1,252 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneHitObjectSamplePointAdjustments : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("add test objects", () =>
{
EditorBeatmap.Add(new HitCircle
{
StartTime = 0,
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
SampleControlPoint = new SampleControlPoint
{
SampleBank = "normal",
SampleVolume = 80
}
});
EditorBeatmap.Add(new HitCircle
{
StartTime = 500,
Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2,
SampleControlPoint = new SampleControlPoint
{
SampleBank = "soft",
SampleVolume = 60
}
});
});
}
[Test]
public void TestSingleSelection()
{
clickSamplePiece(0);
samplePopoverHasSingleBank("normal");
samplePopoverHasSingleVolume(80);
dismissPopover();
// select first object to ensure that sample pieces for unselected objects
// work independently from selection state.
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
clickSamplePiece(1);
samplePopoverHasSingleBank("soft");
samplePopoverHasSingleVolume(60);
setVolumeViaPopover(90);
hitObjectHasSampleVolume(1, 90);
setBankViaPopover("drum");
hitObjectHasSampleBank(1, "drum");
}
[Test]
public void TestMultipleSelectionWithSameSampleVolume()
{
AddStep("unify sample volume", () =>
{
foreach (var h in EditorBeatmap.HitObjects)
h.SampleControlPoint.SampleVolume = 50;
});
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
clickSamplePiece(0);
samplePopoverHasSingleVolume(50);
dismissPopover();
clickSamplePiece(1);
samplePopoverHasSingleVolume(50);
setVolumeViaPopover(75);
hitObjectHasSampleVolume(0, 75);
hitObjectHasSampleVolume(1, 75);
}
[Test]
public void TestMultipleSelectionWithDifferentSampleVolume()
{
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
clickSamplePiece(0);
samplePopoverHasIndeterminateVolume();
dismissPopover();
clickSamplePiece(1);
samplePopoverHasIndeterminateVolume();
setVolumeViaPopover(30);
hitObjectHasSampleVolume(0, 30);
hitObjectHasSampleVolume(1, 30);
}
[Test]
public void TestMultipleSelectionWithSameSampleBank()
{
AddStep("unify sample bank", () =>
{
foreach (var h in EditorBeatmap.HitObjects)
h.SampleControlPoint.SampleBank = "soft";
});
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
clickSamplePiece(0);
samplePopoverHasSingleBank("soft");
dismissPopover();
clickSamplePiece(1);
samplePopoverHasSingleBank("soft");
setBankViaPopover(string.Empty);
hitObjectHasSampleBank(0, "soft");
hitObjectHasSampleBank(1, "soft");
samplePopoverHasSingleBank("soft");
setBankViaPopover("drum");
hitObjectHasSampleBank(0, "drum");
hitObjectHasSampleBank(1, "drum");
samplePopoverHasSingleBank("drum");
}
[Test]
public void TestMultipleSelectionWithDifferentSampleBank()
{
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
clickSamplePiece(0);
samplePopoverHasIndeterminateBank();
dismissPopover();
clickSamplePiece(1);
samplePopoverHasIndeterminateBank();
setBankViaPopover(string.Empty);
hitObjectHasSampleBank(0, "normal");
hitObjectHasSampleBank(1, "soft");
samplePopoverHasIndeterminateBank();
setBankViaPopover("normal");
hitObjectHasSampleBank(0, "normal");
hitObjectHasSampleBank(1, "normal");
samplePopoverHasSingleBank("normal");
}
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () =>
{
var difficultyPiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
InputManager.MoveMouseTo(difficultyPiece);
InputManager.Click(MouseButton.Left);
});
private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
return slider?.Current.Value == volume;
});
private void samplePopoverHasIndeterminateVolume() => AddUntilStep("sample popover has indeterminate volume", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
return slider != null && slider.Current.Value == null;
});
private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var textBox = popover?.ChildrenOfType<OsuTextBox>().First();
return textBox?.Current.Value == bank && string.IsNullOrEmpty(textBox?.PlaceholderText.ToString());
});
private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var textBox = popover?.ChildrenOfType<OsuTextBox>().First();
return textBox != null && string.IsNullOrEmpty(textBox.Current.Value) && !string.IsNullOrEmpty(textBox.PlaceholderText.ToString());
});
private void dismissPopover()
{
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
AddUntilStep("wait for dismiss", () => !this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().Any(popover => popover.IsPresent));
}
private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
var slider = popover.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
slider.Current.Value = volume;
});
private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.SampleControlPoint.SampleVolume == volume;
});
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
var textBox = popover.ChildrenOfType<LabelledTextBox>().First();
textBox.Current.Value = bank;
// force a commit via keyboard.
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
InputManager.ChangeFocus(textBox);
InputManager.Key(Key.Enter);
});
private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.SampleControlPoint.SampleBank == bank;
});
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture] [TestFixture]
public class TestSceneWaveform : OsuTestScene public class TestSceneWaveform : OsuTestScene
{ {
private WorkingBeatmap waveformBeatmap; private IWorkingBeatmap waveformBeatmap;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)

View File

@ -293,7 +293,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, null); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, null);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty; public override string Description { get; } = string.Empty;

View File

@ -31,12 +31,18 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
// The pause screen and fail animation both ramp frequency.
// This tests to ensure that it doesn't reset during that handoff.
AddAssert("frequency only ever decreased", () => !((FailPlayer)Player).FrequencyIncreased);
} }
private class FailPlayer : TestPlayer private class FailPlayer : TestPlayer
{ {
public new FailOverlay FailOverlay => base.FailOverlay; public new FailOverlay FailOverlay => base.FailOverlay;
public bool FrequencyIncreased { get; private set; }
public FailPlayer() public FailPlayer()
: base(false, false) : base(false, false)
{ {
@ -47,6 +53,19 @@ namespace osu.Game.Tests.Visual.Gameplay
base.LoadComplete(); base.LoadComplete();
HealthProcessor.FailConditions += (_, __) => true; HealthProcessor.FailConditions += (_, __) => true;
} }
private double lastFrequency = double.MaxValue;
protected override void Update()
{
base.Update();
double freq = Beatmap.Value.Track.AggregateFrequency.Value;
FrequencyIncreased |= freq > lastFrequency;
lastFrequency = freq;
}
} }
} }
} }

View File

@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
throw new System.NotImplementedException(); throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) =>
throw new System.NotImplementedException(); throw new System.NotImplementedException();
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
@ -83,6 +83,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool OnPressed(KeyBindingPressEvent<TestAction> e) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
if (e.Repeat)
return false;
ReceivedAction = e.Action == TestAction.Down; ReceivedAction = e.Action == TestAction.Down;
return true; return true;
} }

View File

@ -95,8 +95,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private void prepareBeatmap() private void prepareBeatmap()
{ {
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning; workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
Beatmap.Value = workingBeatmap;
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>()) foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(Beatmap.Value.Track); mod.ApplyToTrack(Beatmap.Value.Track);

View File

@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestScenePlayerReferenceLeaking : TestSceneAllRulesetPlayers public class TestScenePlayerReferenceLeaking : TestSceneAllRulesetPlayers
{ {
private readonly WeakList<WorkingBeatmap> workingWeakReferences = new WeakList<WorkingBeatmap>(); private readonly WeakList<IWorkingBeatmap> workingWeakReferences = new WeakList<IWorkingBeatmap>();
private readonly WeakList<Player> playerWeakReferences = new WeakList<Player>(); private readonly WeakList<Player> playerWeakReferences = new WeakList<Player>();

View File

@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty; public override string Description { get; } = string.Empty;

View File

@ -231,6 +231,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool OnPressed(KeyBindingPressEvent<TestAction> e) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
if (e.Repeat)
return false;
box.Colour = Color4.White; box.Colour = Color4.White;
return true; return true;
} }

View File

@ -164,6 +164,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool OnPressed(KeyBindingPressEvent<TestAction> e) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
if (e.Repeat)
return false;
box.Colour = Color4.White; box.Colour = Color4.White;
return true; return true;
} }

View File

@ -283,6 +283,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool OnPressed(KeyBindingPressEvent<TestAction> e) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
if (e.Repeat)
return false;
box.Colour = Color4.White; box.Colour = Color4.White;
return true; return true;
} }

View File

@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
track.Start(); track.Start();
} }
private void loadStoryboard(WorkingBeatmap working) private void loadStoryboard(IWorkingBeatmap working)
{ {
if (storyboard != null) if (storyboard != null)
storyboardContainer.Remove(storyboard); storyboardContainer.Remove(storyboard);

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Menus
public void TestMusicNavigationActions() public void TestMusicNavigationActions()
{ {
int importId = 0; int importId = 0;
Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to. // ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("bind to track change", () => AddStep("bind to track change", () =>
{ {
trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>(); trackChangeQueue = new Queue<(IWorkingBeatmap, TrackChangeDirection)>();
Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection)); Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection));
}); });

View File

@ -67,6 +67,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("no item selected", () => playlist.SelectedItem.Value == null); AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
} }
[Test]
public void TestMarkInvalid()
{
createPlaylist(true, true);
AddStep("mark item 0 as invalid", () => playlist.Items[0].MarkInvalid());
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
}
[Test] [Test]
public void TestSelectable() public void TestSelectable()
{ {

View File

@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -139,7 +140,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.ID == getImport().ID); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.MatchesOnlineID(getImport()));
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID);
} }

View File

@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Navigation
Player player = null; Player player = null;
ResultsScreen results = null; ResultsScreen results = null;
WorkingBeatmap beatmap() => Game.Beatmap.Value; IWorkingBeatmap beatmap() => Game.Beatmap.Value;
PushAndConfirm(() => new TestPlaySongSelect()); PushAndConfirm(() => new TestPlaySongSelect());
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
Player player = null; Player player = null;
WorkingBeatmap beatmap() => Game.Beatmap.Value; IWorkingBeatmap beatmap() => Game.Beatmap.Value;
PushAndConfirm(() => new TestPlaySongSelect()); PushAndConfirm(() => new TestPlaySongSelect());

View File

@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Playlists
RoomManager.CreateRequested = r => RoomManager.CreateRequested = r =>
{ {
createdRoom = r; createdRoom = r;
return true; return string.Empty;
}; };
}); });
@ -82,28 +82,58 @@ namespace osu.Game.Tests.Visual.Playlists
AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration); AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration);
} }
[Test]
public void TestInvalidBeatmapError()
{
const string not_found_prefix = "beatmaps not found:";
string errorMesage = null;
AddStep("setup", () =>
{
var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo;
SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmap } });
errorMesage = $"{not_found_prefix} {beatmap.OnlineID}";
RoomManager.CreateRequested = _ => errorMesage;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
AddAssert("playlist item valid", () => SelectedRoom.Value.Playlist[0].Valid.Value);
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("error displayed", () => settings.ErrorText.IsPresent);
AddAssert("error has custom text", () => settings.ErrorText.Text != errorMesage);
AddAssert("playlist item marked invalid", () => !SelectedRoom.Value.Playlist[0].Valid.Value);
}
[Test] [Test]
public void TestCreationFailureDisplaysError() public void TestCreationFailureDisplaysError()
{ {
bool fail; const string error_message = "failed";
string failText = error_message;
AddStep("setup", () => AddStep("setup", () =>
{ {
SelectedRoom.Value.Name.Value = "Test Room"; SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }); SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
fail = true; RoomManager.CreateRequested = _ => failText;
RoomManager.CreateRequested = _ => !fail;
}); });
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
AddStep("create room", () => settings.ApplyButton.Action.Invoke()); AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("error displayed", () => settings.ErrorText.IsPresent); AddAssert("error displayed", () => settings.ErrorText.IsPresent);
AddAssert("error has correct text", () => settings.ErrorText.Text == TestRoomManager.FAILED_TEXT); AddAssert("error has correct text", () => settings.ErrorText.Text == error_message);
AddStep("create room no fail", () => AddStep("create room no fail", () =>
{ {
fail = false; failText = string.Empty;
settings.ApplyButton.Action.Invoke(); settings.ApplyButton.Action.Invoke();
}); });
@ -132,9 +162,7 @@ namespace osu.Game.Tests.Visual.Playlists
protected class TestRoomManager : IRoomManager protected class TestRoomManager : IRoomManager
{ {
public const string FAILED_TEXT = "failed"; public Func<Room, string> CreateRequested;
public Func<Room, bool> CreateRequested;
public event Action RoomsUpdated public event Action RoomsUpdated
{ {
@ -157,8 +185,10 @@ namespace osu.Game.Tests.Visual.Playlists
if (CreateRequested == null) if (CreateRequested == null)
return; return;
if (!CreateRequested.Invoke(room)) string error = CreateRequested.Invoke(room);
onError?.Invoke(FAILED_TEXT);
if (!string.IsNullOrEmpty(error))
onError?.Invoke(error);
else else
onSuccess?.Invoke(room); onSuccess?.Invoke(room);
} }

View File

@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.SongSelect
}); });
} }
private void showMetadataForBeatmap(Func<WorkingBeatmap> getBeatmap) private void showMetadataForBeatmap(Func<IWorkingBeatmap> getBeatmap)
{ {
AddStep("setup display", () => AddStep("setup display", () =>
{ {

View File

@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -102,8 +103,8 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
BeatmapSetInfo catchSet = null, mixedSet = null; BeatmapSetInfo catchSet = null, mixedSet = null;
AddStep("create catch beatmapset", () => catchSet = importBeatmapSet(0, new[] { new CatchRuleset().RulesetInfo })); AddStep("create catch beatmapset", () => catchSet = importBeatmapSet(1, new[] { new CatchRuleset().RulesetInfo }));
AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(1, AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(2,
new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new ManiaRuleset().RulesetInfo })); new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new ManiaRuleset().RulesetInfo }));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { catchSet, mixedSet })); AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { catchSet, mixedSet }));
@ -120,8 +121,8 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
BeatmapSetInfo osuSet = null, mixedSet = null; BeatmapSetInfo osuSet = null, mixedSet = null;
AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(0, new[] { new OsuRuleset().RulesetInfo })); AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(1, new[] { new OsuRuleset().RulesetInfo }));
AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(1, AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(2,
new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new ManiaRuleset().RulesetInfo })); new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new ManiaRuleset().RulesetInfo }));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, mixedSet })); AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, mixedSet }));
@ -138,8 +139,8 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
BeatmapSetInfo osuSet = null, mixedSet = null; BeatmapSetInfo osuSet = null, mixedSet = null;
AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(0, new[] { new OsuRuleset().RulesetInfo })); AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(1, new[] { new OsuRuleset().RulesetInfo }));
AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(1, AddStep("create mixed beatmapset", () => mixedSet = importBeatmapSet(2,
new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new TaikoRuleset().RulesetInfo })); new[] { new TaikoRuleset().RulesetInfo, new CatchRuleset().RulesetInfo, new TaikoRuleset().RulesetInfo }));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, mixedSet })); AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, mixedSet }));
@ -156,8 +157,8 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
BeatmapSetInfo osuSet = null, maniaSet = null; BeatmapSetInfo osuSet = null, maniaSet = null;
AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(0, new[] { new OsuRuleset().RulesetInfo })); AddStep("create osu! beatmapset", () => osuSet = importBeatmapSet(1, new[] { new OsuRuleset().RulesetInfo }));
AddStep("create mania beatmapset", () => maniaSet = importBeatmapSet(1, Enumerable.Repeat(new ManiaRuleset().RulesetInfo, 10))); AddStep("create mania beatmapset", () => maniaSet = importBeatmapSet(2, Enumerable.Repeat(new ManiaRuleset().RulesetInfo, 10)));
AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, maniaSet })); AddAssert("all sets imported", () => ensureAllBeatmapSetsImported(new[] { osuSet, maniaSet }));
@ -203,11 +204,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("recommended beatmap displayed", () => AddUntilStep("recommended beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.MatchesOnlineID(getImport().Beatmaps[expectedDiff - 1]));
{
int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineID;
return Game.Beatmap.Value.BeatmapInfo.OnlineID == expectedID;
});
} }
} }
} }

View File

@ -17,6 +17,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -360,7 +361,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target));
// this is an important check, to make sure updateComponentFromBeatmap() was actually run // this is an important check, to make sure updateComponentFromBeatmap() was actually run
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target)); AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target));
} }
[Test] [Test]
@ -392,7 +393,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0); AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
// this is an important check, to make sure updateComponentFromBeatmap() was actually run // this is an important check, to make sure updateComponentFromBeatmap() was actually run
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target)); AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target));
} }
[Test] [Test]
@ -507,13 +508,13 @@ namespace osu.Game.Tests.Visual.SongSelect
i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0); i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0);
}); });
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID); AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty); AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineID == target.OnlineID); AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true);
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineID == target.OnlineID); AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target));
} }
[Test] [Test]
@ -544,8 +545,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID); AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo"); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo");
@ -672,7 +673,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for selection", () => !Beatmap.IsDefault); AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
AddStep("record set ID", () => previousSetID = Beatmap.Value.BeatmapSetInfo.ID); AddStep("record set ID", () => previousSetID = ((IBeatmapSetInfo)Beatmap.Value.BeatmapSetInfo).OnlineID);
AddAssert("selection changed once", () => changeCount == 1); AddAssert("selection changed once", () => changeCount == 1);
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0); AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
@ -683,8 +684,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("selection changed", () => changeCount > 1); AddUntilStep("selection changed", () => changeCount > 1);
AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.ID == previousSetID); AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.OnlineID == previousSetID);
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3); AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3);
AddAssert("selection changed only fired twice", () => changeCount == 2); AddAssert("selection changed only fired twice", () => changeCount == 2);
@ -727,7 +728,7 @@ namespace osu.Game.Tests.Visual.SongSelect
int previousSetID = 0; int previousSetID = 0;
AddStep("record set ID", () => previousSetID = Beatmap.Value.BeatmapSetInfo.ID); AddStep("record set ID", () => previousSetID = ((IBeatmapSetInfo)Beatmap.Value.BeatmapSetInfo).OnlineID);
AddStep("Click on a difficulty", () => AddStep("Click on a difficulty", () =>
{ {
@ -738,8 +739,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet.ID == previousSetID); AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet.OnlineID == previousSetID);
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3); AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3);
} }
[Test] [Test]
@ -784,7 +785,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().BeatmapInfo)); AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(groupIcon.Items.First().BeatmapInfo));
} }
[Test] [Test]
@ -815,7 +816,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen());
AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(getPresentBeatmap())); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap()));
AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0); AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0);
} }
@ -847,7 +848,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen());
AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.Equals(getPresentBeatmap())); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap()));
AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0); AddAssert("check ruleset is correct for score", () => Ruleset.Value.ID == 0);
} }
@ -960,7 +961,7 @@ namespace osu.Game.Tests.Visual.SongSelect
public new FilterControl FilterControl => base.FilterControl; public new FilterControl FilterControl => base.FilterControl;
public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;
public new void PresentScore(ScoreInfo score) => base.PresentScore(score); public new void PresentScore(ScoreInfo score) => base.PresentScore(score);

View File

@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = "test"; public override string Description { get; } = "test";
public override string ShortName { get; } = "tst"; public override string ShortName { get; } = "tst";

View File

@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps
}, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); }, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
} }
public Task<List<TimedDifficultyAttributes>> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default) public Task<List<TimedDifficultyAttributes>> GetTimedDifficultyAttributesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default)
{ {
return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods), return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods),
token, token,

View File

@ -154,14 +154,17 @@ namespace osu.Game.Beatmaps
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)
{ {
if (ID == 0 || other?.ID == 0) if (ReferenceEquals(this, other)) return true;
// one of the two BeatmapInfos we are comparing isn't sourced from a database. if (other == null) return false;
// fall back to reference equality.
return ReferenceEquals(this, other);
return ID == other?.ID; if (ID != 0 && other.ID != 0)
return ID == other.ID;
return false;
} }
public bool Equals(IBeatmapInfo other) => other is BeatmapInfo b && Equals(b);
public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Hash == other.BeatmapSet.Hash && BeatmapSet.Hash == other.BeatmapSet.Hash &&
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;

View File

@ -179,7 +179,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available. /// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary> /// </summary>
public WorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; public IWorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap;
/// <summary> /// <summary>
/// Fired when a notification should be presented to the user. /// Fired when a notification should be presented to the user.

View File

@ -69,21 +69,17 @@ namespace osu.Game.Beatmaps
public bool Equals(BeatmapSetInfo other) public bool Equals(BeatmapSetInfo other)
{ {
if (other == null) if (ReferenceEquals(this, other)) return true;
return false; if (other == null) return false;
if (ID != 0 && other.ID != 0) if (ID != 0 && other.ID != 0)
return ID == other.ID; return ID == other.ID;
if (OnlineID.HasValue && other.OnlineID.HasValue) return false;
return OnlineID == other.OnlineID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash;
return ReferenceEquals(this, other);
} }
public bool Equals(IBeatmapSetInfo other) => other is BeatmapSetInfo b && Equals(b);
#region Implementation of IHasOnlineID #region Implementation of IHasOnlineID
int IHasOnlineID<int>.OnlineID => OnlineID ?? -1; int IHasOnlineID<int>.OnlineID => OnlineID ?? -1;

View File

@ -9,9 +9,9 @@ namespace osu.Game.Beatmaps.Drawables
{ {
public class BeatmapBackgroundSprite : Sprite public class BeatmapBackgroundSprite : Sprite
{ {
private readonly WorkingBeatmap working; private readonly IWorkingBeatmap working;
public BeatmapBackgroundSprite(WorkingBeatmap working) public BeatmapBackgroundSprite(IWorkingBeatmap working)
{ {
if (working == null) if (working == null)
throw new ArgumentNullException(nameof(working)); throw new ArgumentNullException(nameof(working));

View File

@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap }; public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null; public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
public override string Description => "dummy"; public override string Description => "dummy";

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -11,7 +12,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// A single beatmap difficulty. /// A single beatmap difficulty.
/// </summary> /// </summary>
public interface IBeatmapInfo : IHasOnlineID<int> public interface IBeatmapInfo : IHasOnlineID<int>, IEquatable<IBeatmapInfo>
{ {
/// <summary> /// <summary>
/// The user-specified name given to this beatmap. /// The user-specified name given to this beatmap.

View File

@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive. /// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
/// </summary> /// </summary>
public interface IBeatmapSetInfo : IHasOnlineID<int> public interface IBeatmapSetInfo : IHasOnlineID<int>, IEquatable<IBeatmapSetInfo>
{ {
/// <summary> /// <summary>
/// The date when this beatmap was imported. /// The date when this beatmap was imported.

View File

@ -17,33 +17,69 @@ namespace osu.Game.Beatmaps
{ {
public interface IWorkingBeatmap public interface IWorkingBeatmap
{ {
IBeatmapInfo BeatmapInfo { get; }
IBeatmapSetInfo BeatmapSetInfo { get; }
IBeatmapMetadataInfo Metadata { get; }
/// <summary> /// <summary>
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="WorkingBeatmap"/> represents. /// Whether the Beatmap has finished loading.
///</summary>
public bool BeatmapLoaded { get; }
/// <summary>
/// Whether the Background has finished loading.
///</summary>
public bool BackgroundLoaded { get; }
/// <summary>
/// Whether the Waveform has finished loading.
///</summary>
public bool WaveformLoaded { get; }
/// <summary>
/// Whether the Storyboard has finished loading.
///</summary>
public bool StoryboardLoaded { get; }
/// <summary>
/// Whether the Skin has finished loading.
///</summary>
public bool SkinLoaded { get; }
/// <summary>
/// Whether the Track has finished loading.
///</summary>
public bool TrackLoaded { get; }
/// <summary>
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="IWorkingBeatmap"/> represents.
/// </summary> /// </summary>
IBeatmap Beatmap { get; } IBeatmap Beatmap { get; }
/// <summary> /// <summary>
/// Retrieves the background for this <see cref="WorkingBeatmap"/>. /// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
/// </summary> /// </summary>
Texture Background { get; } Texture Background { get; }
/// <summary> /// <summary>
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="WorkingBeatmap"/>. /// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.
/// </summary> /// </summary>
Waveform Waveform { get; } Waveform Waveform { get; }
/// <summary> /// <summary>
/// Retrieves the <see cref="Storyboard"/> which this <see cref="WorkingBeatmap"/> provides. /// Retrieves the <see cref="Storyboard"/> which this <see cref="IWorkingBeatmap"/> provides.
/// </summary> /// </summary>
Storyboard Storyboard { get; } Storyboard Storyboard { get; }
/// <summary> /// <summary>
/// Retrieves the <see cref="Skin"/> which this <see cref="WorkingBeatmap"/> provides. /// Retrieves the <see cref="Skin"/> which this <see cref="IWorkingBeatmap"/> provides.
/// </summary> /// </summary>
ISkin Skin { get; } ISkin Skin { get; }
/// <summary> /// <summary>
/// Retrieves the <see cref="Track"/> which this <see cref="WorkingBeatmap"/> has loaded. /// Retrieves the <see cref="Track"/> which this <see cref="IWorkingBeatmap"/> has loaded.
/// </summary> /// </summary>
Track Track { get; } Track Track { get; }
@ -59,7 +95,7 @@ namespace osu.Game.Beatmaps
/// <param name="timeout">The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms.</param> /// <param name="timeout">The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms.</param>
/// <returns>The converted <see cref="IBeatmap"/>.</returns> /// <returns>The converted <see cref="IBeatmap"/>.</returns>
/// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception> /// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null); IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null);
/// <summary> /// <summary>
/// Load a new audio track instance for this beatmap. This should be called once before accessing <see cref="Track"/>. /// Load a new audio track instance for this beatmap. This should be called once before accessing <see cref="Track"/>.
@ -67,7 +103,7 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// In a standard game context, the loading of the track is managed solely by MusicController, which will /// In a standard game context, the loading of the track is managed solely by MusicController, which will
/// automatically load the track of the current global IBindable WorkingBeatmap. /// automatically load the track of the current global IBindable IWorkingBeatmap.
/// As such, this method should only be called in very special scenarios, such as external tests or apps which are /// As such, this method should only be called in very special scenarios, such as external tests or apps which are
/// outside of the game context. /// outside of the game context.
/// </remarks> /// </remarks>
@ -79,5 +115,20 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <param name="storagePath">The storage path to the file.</param> /// <param name="storagePath">The storage path to the file.</param>
Stream GetStream(string storagePath); Stream GetStream(string storagePath);
/// <summary>
/// Beings loading the contents of this <see cref="IWorkingBeatmap"/> asynchronously.
/// </summary>
public void BeginAsyncLoad();
/// <summary>
/// Cancels the asynchronous loading of the contents of this <see cref="IWorkingBeatmap"/>.
/// </summary>
public void CancelAsyncLoad();
/// <summary>
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
/// </summary>
void PrepareTrackForPreviewLooping();
} }
} }

View File

@ -27,9 +27,7 @@ namespace osu.Game.Beatmaps
public abstract class WorkingBeatmap : IWorkingBeatmap public abstract class WorkingBeatmap : IWorkingBeatmap
{ {
public readonly BeatmapInfo BeatmapInfo; public readonly BeatmapInfo BeatmapInfo;
public readonly BeatmapSetInfo BeatmapSetInfo; public readonly BeatmapSetInfo BeatmapSetInfo;
public readonly BeatmapMetadata Metadata; public readonly BeatmapMetadata Metadata;
protected AudioManager AudioManager { get; } protected AudioManager AudioManager { get; }
@ -81,7 +79,7 @@ namespace osu.Game.Beatmaps
/// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns> /// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns>
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null) public virtual IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null)
{ {
using (var cancellationSource = createCancellationTokenSource(timeout)) using (var cancellationSource = createCancellationTokenSource(timeout))
{ {
@ -89,6 +87,9 @@ namespace osu.Game.Beatmaps
var rulesetInstance = ruleset.CreateInstance(); var rulesetInstance = ruleset.CreateInstance();
if (rulesetInstance == null)
throw new RulesetLoadException("Creating ruleset instance failed when attempting to create playable beatmap.");
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
// Check if the beatmap can be converted // Check if the beatmap can be converted
@ -176,17 +177,8 @@ namespace osu.Game.Beatmaps
private CancellationTokenSource loadCancellation = new CancellationTokenSource(); private CancellationTokenSource loadCancellation = new CancellationTokenSource();
/// <summary> public void BeginAsyncLoad() => loadBeatmapAsync();
/// Beings loading the contents of this <see cref="WorkingBeatmap"/> asynchronously.
/// </summary>
public void BeginAsyncLoad()
{
loadBeatmapAsync();
}
/// <summary>
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
/// </summary>
public void CancelAsyncLoad() public void CancelAsyncLoad()
{ {
lock (beatmapFetchLock) lock (beatmapFetchLock)
@ -234,6 +226,10 @@ namespace osu.Game.Beatmaps
public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo;
IBeatmapMetadataInfo IWorkingBeatmap.Metadata => Metadata;
IBeatmapSetInfo IWorkingBeatmap.BeatmapSetInfo => BeatmapSetInfo;
public IBeatmap Beatmap public IBeatmap Beatmap
{ {
get get
@ -273,9 +269,6 @@ namespace osu.Game.Beatmaps
[NotNull] [NotNull]
public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000);
/// <summary>
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
/// </summary>
public void PrepareTrackForPreviewLooping() public void PrepareTrackForPreviewLooping()
{ {
Track.Looping = true; Track.Looping = true;

View File

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

View File

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

View File

@ -1,11 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
#nullable enable #nullable enable
namespace osu.Game.Database namespace osu.Game.Database
{ {
public interface IHasOnlineID<out T> public interface IHasOnlineID<out T>
where T : IEquatable<T>
{ {
/// <summary> /// <summary>
/// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID (except in special cases where autoincrement is not used, like rulesets). /// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID (except in special cases where autoincrement is not used, like rulesets).

View File

@ -1,11 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Threading;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -14,30 +11,6 @@ namespace osu.Game.Extensions
{ {
public static class DrawableExtensions public static class DrawableExtensions
{ {
public const double REPEAT_INTERVAL = 70;
public const double INITIAL_DELAY = 250;
/// <summary>
/// Helper method that is used while <see cref="IKeyBindingHandler"/> doesn't support repetitions of <see cref="IKeyBindingHandler{T}.OnPressed"/>.
/// Simulates repetitions by continually invoking a delegate according to the default key repeat rate.
/// </summary>
/// <remarks>
/// The returned delegate can be cancelled to stop repeat events from firing (usually in <see cref="IKeyBindingHandler{T}.OnReleased"/>).
/// </remarks>
/// <param name="handler">The <see cref="IKeyBindingHandler{T}"/> which is handling the repeat.</param>
/// <param name="scheduler">The <see cref="Scheduler"/> to schedule repetitions on.</param>
/// <param name="action">The <see cref="Action"/> to be invoked once immediately and with every repetition.</param>
/// <param name="initialRepeatDelay">The delay imposed on the first repeat. Defaults to <see cref="INITIAL_DELAY"/>.</param>
/// <returns>A <see cref="ScheduledDelegate"/> which can be cancelled to stop the repeat events from firing.</returns>
public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action, double initialRepeatDelay = INITIAL_DELAY)
{
action();
ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + initialRepeatDelay, REPEAT_INTERVAL);
scheduler.Add(repeatDelegate);
return repeatDelegate;
}
/// <summary> /// <summary>
/// Shakes this drawable. /// Shakes this drawable.
/// </summary> /// </summary>

View File

@ -2,10 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Users; using osu.Game.Users;
#nullable enable
namespace osu.Game.Extensions namespace osu.Game.Extensions
{ {
public static class ModelExtensions public static class ModelExtensions
@ -22,9 +26,9 @@ namespace osu.Game.Extensions
/// extension method type inference rules cause this method to call itself and cause a stack overflow. /// extension method type inference rules cause this method to call itself and cause a stack overflow.
/// </para> /// </para>
/// </remarks> /// </remarks>
public static string GetDisplayString(this object model) public static string GetDisplayString(this object? model)
{ {
string result = null; string? result = null;
switch (model) switch (model)
{ {
@ -57,5 +61,59 @@ namespace osu.Game.Extensions
result ??= model?.ToString() ?? @"null"; result ??= model?.ToString() ?? @"null";
return result; return result;
} }
/// <summary>
/// Check whether the online ID of two <see cref="IBeatmapSetInfo"/>s match.
/// </summary>
/// <param name="instance">The instance to compare.</param>
/// <param name="other">The other instance to compare against.</param>
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
public static bool MatchesOnlineID(this IBeatmapSetInfo? instance, IBeatmapSetInfo? other) => matchesOnlineID(instance, other);
/// <summary>
/// Check whether the online ID of two <see cref="IBeatmapInfo"/>s match.
/// </summary>
/// <param name="instance">The instance to compare.</param>
/// <param name="other">The other instance to compare against.</param>
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
public static bool MatchesOnlineID(this IBeatmapInfo? instance, IBeatmapInfo? other) => matchesOnlineID(instance, other);
/// <summary>
/// Check whether the online ID of two <see cref="IRulesetInfo"/>s match.
/// </summary>
/// <param name="instance">The instance to compare.</param>
/// <param name="other">The other instance to compare against.</param>
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
public static bool MatchesOnlineID(this IRulesetInfo? instance, IRulesetInfo? other) => matchesOnlineID(instance, other);
/// <summary>
/// Check whether the online ID of two <see cref="APIUser"/>s match.
/// </summary>
/// <param name="instance">The instance to compare.</param>
/// <param name="other">The other instance to compare against.</param>
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other);
private static bool matchesOnlineID(this IHasOnlineID<long>? instance, IHasOnlineID<long>? other)
{
if (instance == null || other == null)
return false;
if (instance.OnlineID < 0 || other.OnlineID < 0)
return false;
return instance.OnlineID.Equals(other.OnlineID);
}
private static bool matchesOnlineID(this IHasOnlineID<int>? instance, IHasOnlineID<int>? other)
{
if (instance == null || other == null)
return false;
if (instance.OnlineID < 0 || other.OnlineID < 0)
return false;
return instance.OnlineID.Equals(other.OnlineID);
}
} }
} }

View File

@ -90,6 +90,9 @@ namespace osu.Game.Graphics.Containers
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Back: case GlobalAction.Back:

View File

@ -60,6 +60,9 @@ namespace osu.Game.Graphics
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.TakeScreenshot: case GlobalAction.TakeScreenshot:

View File

@ -64,6 +64,9 @@ namespace osu.Game.Graphics.UserInterface
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Back: case GlobalAction.Back:

View File

@ -75,6 +75,9 @@ namespace osu.Game.Graphics.UserInterface
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
if (!HasFocus) return false; if (!HasFocus) return false;
if (e.Action == GlobalAction.Back) if (e.Action == GlobalAction.Back)

View File

@ -19,6 +19,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Utils;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -219,7 +220,7 @@ namespace osu.Game.Graphics.UserInterface
decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);
// Find the number of significant digits (we could have less than 5 after normalize()) // Find the number of significant digits (we could have less than 5 after normalize())
int significantDigits = findPrecision(decimalPrecision); int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
TooltipText = floatValue.ToString($"N{significantDigits}"); TooltipText = floatValue.ToString($"N{significantDigits}");
} }
@ -248,23 +249,5 @@ namespace osu.Game.Graphics.UserInterface
/// <returns>The normalised decimal.</returns> /// <returns>The normalised decimal.</returns>
private decimal normalise(decimal d, int sd) private decimal normalise(decimal d, int sd)
=> decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); => decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
/// <summary>
/// Finds the number of digits after the decimal.
/// </summary>
/// <param name="d">The value to find the number of decimal digits for.</param>
/// <returns>The number decimal digits.</returns>
private int findPrecision(decimal d)
{
int precision = 0;
while (d != Math.Round(d))
{
d *= 10;
precision++;
}
return precision;
}
} }
} }

View File

@ -58,6 +58,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Hidden)
return false; return false;

View File

@ -20,7 +20,7 @@ namespace osu.Game.Models
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
[Serializable] [Serializable]
[MapTo("Beatmap")] [MapTo("Beatmap")]
public class RealmBeatmap : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo public class RealmBeatmap : RealmObject, IHasGuidPrimaryKey, IBeatmapInfo, IEquatable<RealmBeatmap>
{ {
[PrimaryKey] [PrimaryKey]
public Guid ID { get; set; } = Guid.NewGuid(); public Guid ID { get; set; } = Guid.NewGuid();
@ -98,6 +98,16 @@ namespace osu.Game.Models
#endregion #endregion
public bool Equals(RealmBeatmap? other)
{
if (ReferenceEquals(this, other)) return true;
if (other == null) return false;
return ID == other.ID;
}
public bool Equals(IBeatmapInfo? other) => other is RealmBeatmap b && Equals(b);
public bool AudioEquals(RealmBeatmap? other) => other != null public bool AudioEquals(RealmBeatmap? other) => other != null
&& BeatmapSet != null && BeatmapSet != null
&& other.BeatmapSet != null && other.BeatmapSet != null

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using Realms; using Realms;
#nullable enable #nullable enable
@ -53,25 +54,18 @@ namespace osu.Game.Models
/// <param name="filename">The name of the file to get the storage path of.</param> /// <param name="filename">The name of the file to get the storage path of.</param>
public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.StoragePath; public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.StoragePath;
public override string ToString() => Metadata?.ToString() ?? base.ToString();
public bool Equals(RealmBeatmapSet? other) public bool Equals(RealmBeatmapSet? other)
{ {
if (other == null) if (ReferenceEquals(this, other)) return true;
return false; if (other == null) return false;
if (IsManaged && other.IsManaged) return ID == other.ID;
return ID == other.ID;
if (OnlineID > 0 && other.OnlineID > 0)
return OnlineID == other.OnlineID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash;
return ReferenceEquals(this, other);
} }
public override string ToString() => Metadata?.GetDisplayString() ?? base.ToString();
public bool Equals(IBeatmapSetInfo? other) => other is RealmBeatmapSet b && Equals(b);
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps; IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files; IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;

View File

@ -4,6 +4,7 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Rulesets; using osu.Game.Rulesets;
#nullable enable #nullable enable
@ -103,5 +104,7 @@ namespace osu.Game.Online.API.Requests.Responses
public string Hash => throw new NotImplementedException(); public string Hash => throw new NotImplementedException();
#endregion #endregion
public bool Equals(IBeatmapInfo? other) => other is APIBeatmap b && this.MatchesOnlineID(b);
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
#nullable enable #nullable enable
@ -141,5 +142,7 @@ namespace osu.Game.Online.API.Requests.Responses
double IBeatmapSetInfo.MaxBPM => BPM; double IBeatmapSetInfo.MaxBPM => BPM;
#endregion #endregion
public bool Equals(IBeatmapSetInfo? other) => other is APIBeatmapSet b && this.MatchesOnlineID(b);
} }
} }

View File

@ -7,6 +7,7 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Extensions;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses namespace osu.Game.Online.API.Requests.Responses
@ -240,6 +241,6 @@ namespace osu.Game.Online.API.Requests.Responses
public int OnlineID => Id; public int OnlineID => Id;
public bool Equals(APIUser other) => OnlineID == other?.OnlineID; public bool Equals(APIUser other) => this.MatchesOnlineID(other);
} }
} }

View File

@ -37,7 +37,7 @@ namespace osu.Game.Online.Chat
base.LoadComplete(); base.LoadComplete();
string verb; string verb;
BeatmapInfo beatmapInfo; IBeatmapInfo beatmapInfo;
switch (api.Activity.Value) switch (api.Activity.Value)
{ {
@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat
break; break;
} }
string beatmapString = beatmapInfo.OnlineID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString(); string beatmapString = beatmapInfo.OnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString();
channelManager.PostMessage($"is {verb} {beatmapString}", true, target); channelManager.PostMessage($"is {verb} {beatmapString}", true, target);
Expire(); Expire();

View File

@ -41,8 +41,10 @@ namespace osu.Game.Online.Chat
/// <param name="postingTextbox">Whether a textbox for posting new messages should be displayed.</param> /// <param name="postingTextbox">Whether a textbox for posting new messages should be displayed.</param>
public StandAloneChatDisplay(bool postingTextbox = false) public StandAloneChatDisplay(bool postingTextbox = false)
{ {
const float corner_radius = 10;
this.postingTextbox = postingTextbox; this.postingTextbox = postingTextbox;
CornerRadius = 10; CornerRadius = corner_radius;
Masking = true; Masking = true;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -62,6 +64,7 @@ namespace osu.Game.Online.Chat
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = textbox_height, Height = textbox_height,
PlaceholderText = "type your message", PlaceholderText = "type your message",
CornerRadius = corner_radius,
ReleaseFocusOnCommit = false, ReleaseFocusOnCommit = false,
HoldFocus = true, HoldFocus = true,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,

View File

@ -10,6 +10,7 @@ using Newtonsoft.Json.Converters;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -62,7 +63,7 @@ namespace osu.Game.Online.Rooms
[CanBeNull] [CanBeNull]
public MultiplayerScoresAround ScoresAround { get; set; } public MultiplayerScoresAround ScoresAround { get; set; }
public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap) public ScoreInfo CreateScoreInfo(RulesetStore rulesets, PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap)
{ {
var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance(); var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance();
@ -73,7 +74,7 @@ namespace osu.Game.Online.Rooms
MaxCombo = MaxCombo, MaxCombo = MaxCombo,
BeatmapInfo = beatmap, BeatmapInfo = beatmap,
BeatmapInfoID = playlistItem.BeatmapID, BeatmapInfoID = playlistItem.BeatmapID,
Ruleset = playlistItem.Ruleset.Value, Ruleset = rulesets.GetRuleset(playlistItem.RulesetID),
RulesetID = playlistItem.RulesetID, RulesetID = playlistItem.RulesetID,
Statistics = Statistics, Statistics = Statistics,
User = User, User = User,

View File

@ -30,11 +30,16 @@ namespace osu.Game.Online.Rooms
[JsonProperty("expired")] [JsonProperty("expired")]
public bool Expired { get; set; } public bool Expired { get; set; }
[JsonIgnore]
public IBindable<bool> Valid => valid;
private readonly Bindable<bool> valid = new BindableBool(true);
[JsonIgnore] [JsonIgnore]
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>(); public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
[JsonIgnore] [JsonIgnore]
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>(); public readonly Bindable<IRulesetInfo> Ruleset = new Bindable<IRulesetInfo>();
[JsonIgnore] [JsonIgnore]
public readonly BindableList<Mod> AllowedMods = new BindableList<Mod>(); public readonly BindableList<Mod> AllowedMods = new BindableList<Mod>();
@ -66,9 +71,11 @@ namespace osu.Game.Online.Rooms
public PlaylistItem() public PlaylistItem()
{ {
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1); Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1);
Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0); Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.OnlineID ?? 0);
} }
public void MarkInvalid() => valid.Value = false;
public void MapObjects(RulesetStore rulesets) public void MapObjects(RulesetStore rulesets)
{ {
Beatmap.Value ??= apiBeatmap; Beatmap.Value ??= apiBeatmap;

View File

@ -1007,6 +1007,9 @@ namespace osu.Game
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
if (introScreen == null) return false; if (introScreen == null) return false;
switch (e.Action) switch (e.Action)

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
@ -80,7 +81,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
case DownloadState.LocallyAvailable: case DownloadState.LocallyAvailable:
Predicate<BeatmapInfo> findPredicate = null; Predicate<BeatmapInfo> findPredicate = null;
if (SelectedBeatmap.Value != null) if (SelectedBeatmap.Value != null)
findPredicate = b => b.OnlineID == SelectedBeatmap.Value.OnlineID; findPredicate = b => b.MatchesOnlineID(SelectedBeatmap.Value);
game?.PresentBeatmap(beatmapSet, findPredicate); game?.PresentBeatmap(beatmapSet, findPredicate);
break; break;

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -166,7 +167,7 @@ namespace osu.Game.Overlays.BeatmapSet
if (BeatmapSet != null) if (BeatmapSet != null)
{ {
Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps
.Where(b => b.Ruleset.OnlineID == ruleset.Value?.OnlineID) .Where(b => b.Ruleset.MatchesOnlineID(ruleset.Value))
.OrderBy(b => b.StarRating) .OrderBy(b => b.StarRating)
.Select(b => new DifficultySelectorButton(b) .Select(b => new DifficultySelectorButton(b)
{ {

View File

@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -64,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapSet
BeatmapSet.BindValueChanged(setInfo => BeatmapSet.BindValueChanged(setInfo =>
{ {
int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.OnlineID == Value.OnlineID) ?? 0; int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.MatchesOnlineID(Value)) ?? 0;
count.Text = beatmapsCount.ToString(); count.Text = beatmapsCount.ToString();
countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0); countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0);

View File

@ -94,6 +94,9 @@ namespace osu.Game.Overlays
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Back: case GlobalAction.Back:

View File

@ -102,6 +102,9 @@ namespace osu.Game.Overlays
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Select: case GlobalAction.Select:

View File

@ -32,6 +32,9 @@ namespace osu.Game.Overlays.Music
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat)
return false;
if (beatmap.Disabled) if (beatmap.Disabled)
return false; return false;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Music
var items = (SearchContainer<RearrangeableListItem<BeatmapSetInfo>>)ListContainer; var items = (SearchContainer<RearrangeableListItem<BeatmapSetInfo>>)ListContainer;
foreach (var item in items.OfType<PlaylistItem>()) foreach (var item in items.OfType<PlaylistItem>())
item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => b.BeatmapSet.Equals(item.Model)) ?? true; item.InSelectedCollection = criteria.Collection?.Beatmaps.Any(b => item.Model.Equals(b.BeatmapSet)) ?? true;
items.SearchTerm = criteria.SearchText; items.SearchTerm = criteria.SearchText;
} }

View File

@ -5,22 +5,23 @@ using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
public class SupporterIcon : CompositeDrawable, IHasTooltip public class SupporterIcon : OsuClickableContainer
{ {
private readonly Box background; private readonly Box background;
private readonly FillFlowContainer iconContainer; private readonly FillFlowContainer iconContainer;
private readonly CircularContainer content; private readonly CircularContainer content;
public LocalisableString TooltipText => UsersStrings.ShowIsSupporter; public override LocalisableString TooltipText => UsersStrings.ShowIsSupporter;
public int SupportLevel public int SupportLevel
{ {
@ -56,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
{ {
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;
InternalChild = content = new CircularContainer Child = content = new CircularContainer
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
@ -78,10 +79,27 @@ namespace osu.Game.Overlays.Profile.Header.Components
}; };
} }
[BackgroundDependencyLoader] [Resolved]
private void load(OsuColour colours) private OsuColour colours { get; set; }
[BackgroundDependencyLoader(true)]
private void load(OsuGame game)
{ {
background.Colour = colours.Pink; background.Colour = colours.Pink;
Action = () => game?.OpenUrlExternally(@"/home/support");
}
protected override bool OnHover(HoverEvent e)
{
background.FadeColour(colours.PinkLight, 500, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
background.FadeColour(colours.Pink, 500, Easing.OutQuint);
base.OnHoverLost(e);
} }
} }
} }

View File

@ -6,8 +6,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Extensions;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Volume namespace osu.Game.Overlays.Volume
@ -17,18 +15,12 @@ namespace osu.Game.Overlays.Volume
public Func<GlobalAction, bool> ActionRequested; public Func<GlobalAction, bool> ActionRequested;
public Func<GlobalAction, float, bool, bool> ScrollActionRequested; public Func<GlobalAction, float, bool, bool> ScrollActionRequested;
private ScheduledDelegate keyRepeat;
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.DecreaseVolume: case GlobalAction.DecreaseVolume:
case GlobalAction.IncreaseVolume: case GlobalAction.IncreaseVolume:
keyRepeat?.Cancel();
keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(e.Action), 150);
return true;
case GlobalAction.ToggleMute: case GlobalAction.ToggleMute:
case GlobalAction.NextVolumeMeter: case GlobalAction.NextVolumeMeter:
case GlobalAction.PreviousVolumeMeter: case GlobalAction.PreviousVolumeMeter:
@ -41,7 +33,6 @@ namespace osu.Game.Overlays.Volume
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{ {
keyRepeat?.Cancel();
} }
protected override bool OnScroll(ScrollEvent e) protected override bool OnScroll(ScrollEvent e)

View File

@ -1,28 +1,87 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty namespace osu.Game.Rulesets.Difficulty
{ {
/// <summary>
/// Describes the difficulty of a beatmap, as output by a <see cref="DifficultyCalculator"/>.
/// </summary>
[JsonObject(MemberSerialization.OptIn)]
public class DifficultyAttributes public class DifficultyAttributes
{ {
protected const int ATTRIB_ID_AIM = 1;
protected const int ATTRIB_ID_SPEED = 3;
protected const int ATTRIB_ID_OVERALL_DIFFICULTY = 5;
protected const int ATTRIB_ID_APPROACH_RATE = 7;
protected const int ATTRIB_ID_MAX_COMBO = 9;
protected const int ATTRIB_ID_STRAIN = 11;
protected const int ATTRIB_ID_GREAT_HIT_WINDOW = 13;
protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15;
protected const int ATTRIB_ID_FLASHLIGHT = 17;
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
/// <summary>
/// The mods which were applied to the beatmap.
/// </summary>
public Mod[] Mods { get; set; } public Mod[] Mods { get; set; }
/// <summary>
/// The skills resulting from the difficulty calculation.
/// </summary>
public Skill[] Skills { get; set; } public Skill[] Skills { get; set; }
/// <summary>
/// The combined star rating of all skill.
/// </summary>
[JsonProperty("star_rating", Order = -3)]
public double StarRating { get; set; } public double StarRating { get; set; }
/// <summary>
/// The maximum achievable combo.
/// </summary>
[JsonProperty("max_combo", Order = -2)]
public int MaxCombo { get; set; } public int MaxCombo { get; set; }
/// <summary>
/// Creates new <see cref="DifficultyAttributes"/>.
/// </summary>
public DifficultyAttributes() public DifficultyAttributes()
{ {
} }
/// <summary>
/// Creates new <see cref="DifficultyAttributes"/>.
/// </summary>
/// <param name="mods">The mods which were applied to the beatmap.</param>
/// <param name="skills">The skills resulting from the difficulty calculation.</param>
/// <param name="starRating">The combined star rating of all skills.</param>
public DifficultyAttributes(Mod[] mods, Skill[] skills, double starRating) public DifficultyAttributes(Mod[] mods, Skill[] skills, double starRating)
{ {
Mods = mods; Mods = mods;
Skills = skills; Skills = skills;
StarRating = starRating; StarRating = starRating;
} }
/// <summary>
/// Converts this <see cref="DifficultyAttributes"/> to osu-web compatible database attribute mappings.
/// </summary>
/// <remarks>
/// See: osu_difficulty_attribs table.
/// </remarks>
public virtual IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() => Enumerable.Empty<(int, object)>();
/// <summary>
/// Reads osu-web database attribute mappings into this <see cref="DifficultyAttributes"/> object.
/// </summary>
/// <param name="values">The attribute mappings.</param>
public virtual void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
{
}
} }
} }

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