1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 10:33:07 +08:00

Merge branch 'master' into beatmap-cancellation-token

This commit is contained in:
Bartłomiej Dach 2021-11-17 21:52:30 +01:00
commit 8b134914cf
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
35 changed files with 388 additions and 86 deletions

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

View File

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

View File

@ -1,12 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("approach_rate")]
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

@ -1,13 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }
[JsonProperty("score_multiplier")]
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

@ -1,21 +1,77 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("aim_strain")]
public double AimStrain { get; set; }
[JsonProperty("speed_strain")]
public double SpeedStrain { get; set; }
[JsonProperty("flashlight_rating")]
public double FlashlightRating { get; set; }
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
[JsonProperty("overall_difficulty")]
public double OverallDifficulty { get; set; }
public double DrainRate { get; set; }
public int HitCircleCount { get; set; }
public int SliderCount { 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

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

View File

@ -1,16 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("stamina_strain")]
public double StaminaStrain { get; set; }
[JsonProperty("rhythm_strain")]
public double RhythmStrain { get; set; }
[JsonProperty("colour_strain")]
public double ColourStrain { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
[JsonProperty("great_hit_window")]
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

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
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>();

View File

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

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Menus
public void TestMusicNavigationActions()
{
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.
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("bind to track change", () =>
{
trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>();
trackChangeQueue = new Queue<(IWorkingBeatmap, TrackChangeDirection)>();
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);
}
[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]
public void TestSelectable()
{

View File

@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
@ -139,7 +140,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
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);
}

View File

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

View File

@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Playlists
RoomManager.CreateRequested = 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);
}
[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]
public void TestCreationFailureDisplaysError()
{
bool fail;
const string error_message = "failed";
string failText = error_message;
AddStep("setup", () =>
{
SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
fail = true;
RoomManager.CreateRequested = _ => !fail;
RoomManager.CreateRequested = _ => failText;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
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", () =>
{
fail = false;
failText = string.Empty;
settings.ApplyButton.Action.Invoke();
});
@ -132,9 +162,7 @@ namespace osu.Game.Tests.Visual.Playlists
protected class TestRoomManager : IRoomManager
{
public const string FAILED_TEXT = "failed";
public Func<Room, bool> CreateRequested;
public Func<Room, string> CreateRequested;
public event Action RoomsUpdated
{
@ -157,8 +185,10 @@ namespace osu.Game.Tests.Visual.Playlists
if (CreateRequested == null)
return;
if (!CreateRequested.Invoke(room))
onError?.Invoke(FAILED_TEXT);
string error = CreateRequested.Invoke(room);
if (!string.IsNullOrEmpty(error))
onError?.Invoke(error);
else
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", () =>
{

View File

@ -17,6 +17,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
@ -360,7 +361,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target));
// 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]
@ -392,7 +393,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
// 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]
@ -507,13 +508,13 @@ namespace osu.Game.Tests.Visual.SongSelect
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("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineID == target.OnlineID);
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineID == target.OnlineID);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true);
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target));
}
[Test]
@ -544,8 +545,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target));
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);
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("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
@ -683,8 +684,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("selection changed", () => changeCount > 1);
AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.ID == previousSetID);
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3);
AddAssert("Selected beatmap still same set", () => Beatmap.Value.BeatmapSetInfo.OnlineID == previousSetID);
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3);
AddAssert("selection changed only fired twice", () => changeCount == 2);
@ -727,7 +728,7 @@ namespace osu.Game.Tests.Visual.SongSelect
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", () =>
{
@ -738,8 +739,8 @@ namespace osu.Game.Tests.Visual.SongSelect
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 is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3);
AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet.OnlineID == previousSetID);
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3);
}
[Test]
@ -784,7 +785,7 @@ namespace osu.Game.Tests.Visual.SongSelect
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]
@ -815,7 +816,7 @@ namespace osu.Game.Tests.Visual.SongSelect
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);
}
@ -847,7 +848,7 @@ namespace osu.Game.Tests.Visual.SongSelect
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);
}
@ -960,7 +961,7 @@ namespace osu.Game.Tests.Visual.SongSelect
public new FilterControl FilterControl => base.FilterControl;
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public new BeatmapCarousel Carousel => base.Carousel;
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ namespace osu.Game.Online.Chat
base.LoadComplete();
string verb;
BeatmapInfo beatmapInfo;
IBeatmapInfo beatmapInfo;
switch (api.Activity.Value)
{
@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat
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);
Expire();

View File

@ -30,6 +30,11 @@ namespace osu.Game.Online.Rooms
[JsonProperty("expired")]
public bool Expired { get; set; }
[JsonIgnore]
public IBindable<bool> Valid => valid;
private readonly Bindable<bool> valid = new BindableBool(true);
[JsonIgnore]
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
@ -69,6 +74,8 @@ namespace osu.Game.Online.Rooms
Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.OnlineID ?? 0);
}
public void MarkInvalid() => valid.Value = false;
public void MapObjects(RulesetStore rulesets)
{
Beatmap.Value ??= apiBeatmap;

View File

@ -1,28 +1,87 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
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
{
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; }
/// <summary>
/// The skills resulting from the difficulty calculation.
/// </summary>
public Skill[] Skills { get; set; }
/// <summary>
/// The combined star rating of all skill.
/// </summary>
[JsonProperty("star_rating", Order = -3)]
public double StarRating { get; set; }
/// <summary>
/// The maximum achievable combo.
/// </summary>
[JsonProperty("max_combo", Order = -2)]
public int MaxCombo { get; set; }
/// <summary>
/// Creates new <see cref="DifficultyAttributes"/>.
/// </summary>
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)
{
Mods = mods;
Skills = skills;
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)
{
}
}
}

View File

@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
@ -45,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay
private ExplicitContentBeatmapPill explicitContentPill;
private ModDisplay modDisplay;
private readonly IBindable<bool> valid = new Bindable<bool>();
private readonly Bindable<IBeatmapInfo> beatmap = new Bindable<IBeatmapInfo>();
private readonly Bindable<IRulesetInfo> ruleset = new Bindable<IRulesetInfo>();
private readonly BindableList<Mod> requiredMods = new BindableList<Mod>();
@ -66,14 +67,18 @@ namespace osu.Game.Screens.OnlinePlay
this.allowSelection = allowSelection;
beatmap.BindTo(item.Beatmap);
valid.BindTo(item.Valid);
ruleset.BindTo(item.Ruleset);
requiredMods.BindTo(item.RequiredMods);
ShowDragHandle.Value = allowEdit;
}
[Resolved]
private OsuColour colours { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load()
{
if (!allowEdit)
HandleColour = HandleColour.Opacity(0);
@ -85,27 +90,43 @@ namespace osu.Game.Screens.OnlinePlay
{
base.LoadComplete();
SelectedItem.BindValueChanged(selected => maskingContainer.BorderThickness = selected.NewValue == Model ? 5 : 0, true);
SelectedItem.BindValueChanged(selected =>
{
bool isCurrent = selected.NewValue == Model;
beatmap.BindValueChanged(_ => scheduleRefresh());
ruleset.BindValueChanged(_ => scheduleRefresh());
if (!valid.Value)
{
// Don't allow selection when not valid.
if (isCurrent)
{
SelectedItem.Value = selected.OldValue;
}
requiredMods.CollectionChanged += (_, __) => scheduleRefresh();
// Don't update border when not valid (the border is displaying this fact).
return;
}
maskingContainer.BorderThickness = isCurrent ? 5 : 0;
}, true);
beatmap.BindValueChanged(_ => Scheduler.AddOnce(refresh));
ruleset.BindValueChanged(_ => Scheduler.AddOnce(refresh));
valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));
requiredMods.CollectionChanged += (_, __) => Scheduler.AddOnce(refresh);
refresh();
}
private ScheduledDelegate scheduledRefresh;
private PanelBackground panelBackground;
private void scheduleRefresh()
{
scheduledRefresh?.Cancel();
scheduledRefresh = Schedule(refresh);
}
private void refresh()
{
if (!valid.Value)
{
maskingContainer.BorderThickness = 5;
maskingContainer.BorderColour = colours.Red;
}
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) };
panelBackground.Beatmap.Value = Item.Beatmap.Value;
@ -278,7 +299,7 @@ namespace osu.Game.Screens.OnlinePlay
protected override bool OnClick(ClickEvent e)
{
if (allowSelection)
if (allowSelection && valid.Value)
SelectedItem.Value = Model;
return true;
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -204,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
new Drawable[]
{
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
playlist = new DrawableRoomPlaylist(true, false) { RelativeSizeAxes = Axes.Both }
},
new Drawable[]
{
@ -339,9 +340,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Duration.Value = DurationField.Current.Value;
manager?.CreateRoom(room, onSuccess, onError);
loadingLayer.Show();
manager?.CreateRoom(room, onSuccess, onError);
}
private void hideError() => ErrorText.FadeOut(50);
@ -350,9 +350,31 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private void onError(string text)
{
ErrorText.Text = text;
ErrorText.FadeIn(50);
// see https://github.com/ppy/osu-web/blob/2c97aaeb64fb4ed97c747d8383a35b30f57428c7/app/Models/Multiplayer/PlaylistItem.php#L48.
const string not_found_prefix = "beatmaps not found:";
if (text.StartsWith(not_found_prefix, StringComparison.Ordinal))
{
ErrorText.Text = "One or more beatmaps were not available online. Please remove or replace the highlighted items.";
int[] invalidBeatmapIDs = text
.Substring(not_found_prefix.Length + 1)
.Split(", ")
.Select(int.Parse)
.ToArray();
foreach (var item in Playlist)
{
if (invalidBeatmapIDs.Contains(item.BeatmapID))
item.MarkInvalid();
}
}
else
{
ErrorText.Text = text;
}
ErrorText.FadeIn(50);
loadingLayer.Hide();
}
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play
/// </summary>
public class BeatmapMetadataDisplay : Container
{
private readonly WorkingBeatmap beatmap;
private readonly IWorkingBeatmap beatmap;
private readonly Bindable<IReadOnlyList<Mod>> mods;
private readonly Drawable logoFacade;
private LoadingSpinner loading;
@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play
}
}
public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable<IReadOnlyList<Mod>> mods, Drawable logoFacade)
public BeatmapMetadataDisplay(IWorkingBeatmap beatmap, Bindable<IReadOnlyList<Mod>> mods, Drawable logoFacade)
{
this.beatmap = beatmap;
this.logoFacade = logoFacade;

View File

@ -354,7 +354,7 @@ namespace osu.Game.Screens.Play
private Drawable createUnderlayComponents() =>
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };
private Drawable createGameplayComponents(WorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay)
private Drawable createGameplayComponents(IWorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay)
{
Children = new Drawable[]
{
@ -372,7 +372,7 @@ namespace osu.Game.Screens.Play
}
};
private Drawable createOverlayComponents(WorkingBeatmap working)
private Drawable createOverlayComponents(IWorkingBeatmap working)
{
var container = new Container
{

View File

@ -15,9 +15,9 @@ namespace osu.Game.Screens.Select
{
internal class BeatmapInfoWedgeBackground : CompositeDrawable
{
private readonly WorkingBeatmap beatmap;
private readonly IWorkingBeatmap beatmap;
public BeatmapInfoWedgeBackground(WorkingBeatmap beatmap)
public BeatmapInfoWedgeBackground(IWorkingBeatmap beatmap)
{
this.beatmap = beatmap;
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select.Carousel
{
public class SetPanelBackground : BufferedContainer
{
public SetPanelBackground(WorkingBeatmap working)
public SetPanelBackground(IWorkingBeatmap working)
: base(cachedFrameBuffer: true)
{
RedrawOnScale = false;

View File

@ -671,7 +671,7 @@ namespace osu.Game.Screens.Select
music.TrackChanged -= ensureTrackLooping;
}
private void ensureTrackLooping(WorkingBeatmap beatmap, TrackChangeDirection changeDirection)
private void ensureTrackLooping(IWorkingBeatmap beatmap, TrackChangeDirection changeDirection)
=> beatmap.PrepareTrackForPreviewLooping();
public override bool OnBackButton()

View File

@ -89,7 +89,7 @@ namespace osu.Game.Storyboards
}
}
public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) =>
public DrawableStoryboard CreateDrawable(IWorkingBeatmap working = null) =>
new DrawableStoryboard(this);
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)

View File

@ -27,11 +27,11 @@ namespace osu.Game.Users
public abstract class InGame : UserActivity
{
public BeatmapInfo BeatmapInfo { get; }
public IBeatmapInfo BeatmapInfo { get; }
public RulesetInfo Ruleset { get; }
protected InGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
protected InGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
{
BeatmapInfo = beatmapInfo;
Ruleset = ruleset;
@ -42,7 +42,7 @@ namespace osu.Game.Users
public class InMultiplayerGame : InGame
{
public InMultiplayerGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
public InMultiplayerGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
@ -52,7 +52,7 @@ namespace osu.Game.Users
public class InPlaylistGame : InGame
{
public InPlaylistGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
public InPlaylistGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
@ -60,7 +60,7 @@ namespace osu.Game.Users
public class InSoloGame : InGame
{
public InSoloGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
public InSoloGame(IBeatmapInfo beatmapInfo, RulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
@ -68,9 +68,9 @@ namespace osu.Game.Users
public class Editing : UserActivity
{
public BeatmapInfo BeatmapInfo { get; }
public IBeatmapInfo BeatmapInfo { get; }
public Editing(BeatmapInfo info)
public Editing(IBeatmapInfo info)
{
BeatmapInfo = info;
}