Merge branch 'master' into singletap-mod

This commit is contained in:
Dean Herbert 2022-07-14 01:43:25 +09:00
commit 732d86bad7
19 changed files with 154 additions and 20 deletions

View File

@ -4,9 +4,12 @@
#nullable disable
using System;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.NonVisual
{
@ -23,6 +26,47 @@ namespace osu.Game.Tests.NonVisual
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
}
[Test]
public void TestAudioEqualityNoFile()
{
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
}
[Test]
public void TestAudioEqualitySameHash()
{
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
addAudioFile(beatmapSetA, "abc");
addAudioFile(beatmapSetB, "abc");
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
}
[Test]
public void TestAudioEqualityDifferentHash()
{
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
addAudioFile(beatmapSetA);
addAudioFile(beatmapSetB);
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
}
private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null)
{
beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3"));
}
[Test]
public void TestDatabasedWithDatabased()
{

View File

@ -134,6 +134,7 @@ namespace osu.Game.Tests.Resources
DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
StarRating = diff,
Length = length,
BeatmapSet = beatmapSet,
BPM = bpm,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo,

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Screens;
@ -103,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing
*/
public void TestAddAudioTrack()
{
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
AddAssert("switch track to real track", () =>
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();
@ -131,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
}
});
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -59,6 +60,20 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool AllowFail => false;
[Test]
public void TestLastPlayedUpdated()
{
DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed);
AddStep("set no custom ruleset", () => customRuleset = null);
AddAssert("last played is null", () => getLastPlayed() == null);
CreateTest();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
}
[Test]
public void TestScoreStoredLocally()
{

View File

@ -12,7 +12,8 @@ namespace osu.Game.Tournament.Components
{
public class TournamentSpriteTextWithBackground : CompositeDrawable
{
protected readonly TournamentSpriteText Text;
public readonly TournamentSpriteText Text;
protected readonly Box Background;
public TournamentSpriteTextWithBackground(string text = "")

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using Newtonsoft.Json;
using osu.Framework.Bindables;
@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Models
public int ID;
[JsonProperty("BeatmapInfo")]
public TournamentBeatmap Beatmap;
public TournamentBeatmap? Beatmap;
public long Score;

View File

@ -16,6 +16,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
{
private readonly TeamScore score;
private readonly TournamentSpriteTextWithBackground teamText;
private readonly Bindable<string> teamName = new Bindable<string>("???");
private bool showScore;
public bool ShowScore
@ -93,7 +97,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
}
}
},
new TournamentSpriteTextWithBackground(team?.FullName.Value ?? "???")
teamText = new TournamentSpriteTextWithBackground
{
Scale = new Vector2(0.5f),
Origin = anchor,
@ -113,6 +117,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
updateDisplay();
FinishTransforms(true);
if (Team != null)
teamName.BindTo(Team.FullName);
teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true);
}
private void updateDisplay()

View File

@ -42,6 +42,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
currentMatch.BindTo(ladder.CurrentMatch);
currentMatch.BindValueChanged(matchChanged);
currentTeam.BindValueChanged(teamChanged);
updateMatch();
}
@ -67,7 +69,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
// team may change to same team, which means score is not in a good state.
// thus we handle this manually.
teamChanged(currentTeam.Value);
currentTeam.TriggerChange();
}
protected override bool OnMouseDown(MouseDownEvent e)
@ -88,11 +90,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
return base.OnMouseDown(e);
}
private void teamChanged(TournamentTeam team)
private void teamChanged(ValueChangedEvent<TournamentTeam> team)
{
InternalChildren = new Drawable[]
{
teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
};
}
}

View File

@ -53,6 +53,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
editorInfo.Selected.ValueChanged += selection =>
{
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
GetContainingInputManager().TriggerFocusContention(null);
roundDropdown.Current = selection.NewValue?.Round;
losersCheckbox.Current = selection.NewValue?.Losers;
dateTimeBox.Current = selection.NewValue?.Date;

View File

@ -3,6 +3,7 @@
#nullable disable
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -69,7 +70,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
currentTeam.BindValueChanged(teamChanged, true);
}
private void teamChanged(ValueChangedEvent<TournamentTeam> team)
private void teamChanged(ValueChangedEvent<TournamentTeam> team) => Scheduler.AddOnce(() =>
{
if (team.NewValue == null)
{
@ -78,7 +79,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
}
showTeam(team.NewValue);
}
});
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
{
@ -120,8 +121,14 @@ namespace osu.Game.Tournament.Screens.TeamIntro
foreach (var seeding in team.SeedingResults)
{
fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value));
foreach (var beatmap in seeding.Beatmaps)
{
if (beatmap.Beatmap == null)
continue;
fill.Add(new BeatmapScoreRow(beatmap));
}
}
}
@ -129,6 +136,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro
{
public BeatmapScoreRow(SeedingBeatmap beatmap)
{
Debug.Assert(beatmap.Beatmap != null);
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@ -157,7 +166,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro
Children = new Drawable[]
{
new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 },
new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
new TournamentSpriteText
{ Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
}
},
};

View File

@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens.TeamWin
private bool firstDisplay = true;
private void update() => Schedule(() =>
private void update() => Scheduler.AddOnce(() =>
{
var match = CurrentMatch.Value;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
@ -110,6 +111,11 @@ namespace osu.Game.Beatmaps
public bool SamplesMatchPlaybackRate { get; set; } = true;
/// <summary>
/// The time at which this beatmap was last played by the local user.
/// </summary>
public DateTimeOffset? LastPlayed { get; set; }
/// <summary>
/// The ratio of distance travelled per time unit.
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
@ -151,14 +157,23 @@ namespace osu.Game.Beatmaps
public bool AudioEquals(BeatmapInfo? other) => other != null
&& BeatmapSet != null
&& other.BeatmapSet != null
&& BeatmapSet.Hash == other.BeatmapSet.Hash
&& Metadata.AudioFile == other.Metadata.AudioFile;
&& compareFiles(this, other, m => m.AudioFile);
public bool BackgroundEquals(BeatmapInfo? other) => other != null
&& BeatmapSet != null
&& other.BeatmapSet != null
&& BeatmapSet.Hash == other.BeatmapSet.Hash
&& Metadata.BackgroundFile == other.Metadata.BackgroundFile;
&& compareFiles(this, other, m => m.BackgroundFile);
private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func<IBeatmapMetadataInfo, string> getFilename)
{
Debug.Assert(x.BeatmapSet != null);
Debug.Assert(y.BeatmapSet != null);
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash;
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash;
return fileHashX == fileHashY;
}
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;

View File

@ -58,8 +58,9 @@ namespace osu.Game.Database
/// 12 2021-11-24 Add Status to RealmBeatmapSet.
/// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields).
/// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo.
/// 15 2022-07-13 Added LastPlayed to BeatmapInfo.
/// </summary>
private const int schema_version = 14;
private const int schema_version = 15;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.

View File

@ -94,6 +94,8 @@ namespace osu.Game.IO
error = OsuStorageError.None;
Storage lastStorage = UnderlyingStorage;
Logger.Log($"Attempting to use custom storage location {CustomStoragePath}");
try
{
Storage userStorage = host.GetStorage(CustomStoragePath);
@ -102,6 +104,7 @@ namespace osu.Game.IO
error = OsuStorageError.AccessibleButEmpty;
ChangeTargetStorage(userStorage);
Logger.Log($"Storage successfully changed to {CustomStoragePath}.");
}
catch
{
@ -109,6 +112,9 @@ namespace osu.Game.IO
ChangeTargetStorage(lastStorage);
}
if (error != OsuStorageError.None)
Logger.Log($"Custom storage location could not be used ({error}).");
return error == OsuStorageError.None;
}

View File

@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit
loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
// required so we can get the track length in EditorClock.
// this is safe as nothing has yet got a reference to this new beatmap.
// this is ONLY safe because the track being provided is a `TrackVirtual` which we don't really care about disposing.
loadableBeatmap.LoadTrack();
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if

View File

@ -11,6 +11,8 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
@ -117,6 +119,23 @@ namespace osu.Game.Screens.Play
await submitScore(score).ConfigureAwait(false);
}
[Resolved]
private RealmAccess realm { get; set; }
protected override void StartGameplay()
{
base.StartGameplay();
// User expectation is that last played should be updated when entering the gameplay loop
// from multiplayer / playlists / solo.
realm.WriteAsync(r =>
{
var realmBeatmap = r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID);
if (realmBeatmap != null)
realmBeatmap.LastPlayed = DateTimeOffset.Now;
});
}
public override bool OnExiting(ScreenExitEvent e)
{
bool exiting = base.OnExiting(e);

View File

@ -81,6 +81,9 @@ namespace osu.Game.Screens.Select.Carousel
case SortMode.DateAdded:
return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded);
case SortMode.LastPlayed:
return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds());
case SortMode.BPM:
return compareUsingAggregateMax(otherSet, b => b.BPM);

View File

@ -23,6 +23,9 @@ namespace osu.Game.Screens.Select.Filter
[Description("Date Added")]
DateAdded,
[Description("Last Played")]
LastPlayed,
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingDifficulty))]
Difficulty,

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select
Current.BindValueChanged(_ => updateMultiplierText(), true);
}
private void updateMultiplierText()
private void updateMultiplierText() => Schedule(() =>
{
double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1;
@ -85,6 +85,6 @@ namespace osu.Game.Screens.Select
modDisplay.FadeIn();
else
modDisplay.FadeOut();
}
});
}
}