1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-13 16:13:34 +08:00

Merge branch 'master' into availability-fixes

This commit is contained in:
Bartłomiej Dach 2023-07-02 20:38:37 +02:00 committed by GitHub
commit 21f758947d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 239 additions and 32 deletions

View File

@ -18,6 +18,7 @@ using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Tests.Resources;
using Realms;
using SharpCompress.Archives;
@ -416,6 +417,53 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestImport_ThenModifyMapWithScore_ThenImport()
{
RunTestWithRealmAsync(async (realm, storage) =>
{
var importer = new BeatmapImporter(storage, realm);
using var store = new RealmRulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
var imported = await LoadOszIntoStore(importer, realm.Realm);
await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
// imitate making local changes via editor
// ReSharper disable once MethodHasAsyncOverload
realm.Write(_ =>
{
BeatmapInfo beatmap = imported.Beatmaps.First();
beatmap.Hash = "new_hash";
beatmap.ResetOnlineInfo();
});
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (see: https://github.com/ppy/osu/pull/22539).
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
Assert.That(imported.Beatmaps.First().Scores.Any());
var importedSecondTime = await importer.Import(new ImportTask(temp));
EnsureLoaded(realm.Realm);
// check the newly "imported" beatmap is not the original.
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
Assert.That(imported.ID != importedSecondTime.ID);
var importedFirstTimeBeatmap = imported.Beatmaps.First();
var importedSecondTimeBeatmap = importedSecondTime.PerformRead(s => s.Beatmaps.First());
Assert.That(importedFirstTimeBeatmap.ID != importedSecondTimeBeatmap.ID);
Assert.That(importedFirstTimeBeatmap.Hash != importedSecondTimeBeatmap.Hash);
Assert.That(!importedFirstTimeBeatmap.Scores.Any());
Assert.That(importedSecondTimeBeatmap.Scores.Count() == 1);
});
}
[Test]
public void TestImportThenImportWithChangedFile()
{
@ -1074,18 +1122,16 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
}
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap)
{
// TODO: reimplement when we have score support in realm.
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
// {
// OnlineID = 2,
// Beatmap = beatmap,
// BeatmapInfoID = beatmap.ID
// }, new ImportScoreTest.TestArchiveReader());
return Task.CompletedTask;
}
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) =>
realm.WriteAsync(() =>
{
realm.Add(new ScoreInfo
{
OnlineID = 2,
BeatmapInfo = beatmap,
BeatmapHash = beatmap.Hash
});
});
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
{

View File

@ -347,6 +347,73 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestDanglingScoreTransferred()
{
RunTestWithRealmAsync(async (realm, storage) =>
{
var importer = new BeatmapImporter(storage, realm);
using var rulesets = new RealmRulesetStore(realm, storage);
using var __ = getBeatmapArchive(out string pathOriginal);
using var _ = getBeatmapArchive(out string pathOnlineCopy);
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
Assert.That(importBeforeUpdate, Is.Not.Null);
Debug.Assert(importBeforeUpdate != null);
string scoreTargetBeatmapHash = string.Empty;
// set a score on the beatmap
importBeforeUpdate.PerformWrite(s =>
{
var beatmapInfo = s.Beatmaps.First();
scoreTargetBeatmapHash = beatmapInfo.Hash;
s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All<RulesetInfo>().First(), new RealmUser()));
});
// locally modify beatmap
const string new_beatmap_hash = "new_hash";
importBeforeUpdate.PerformWrite(s =>
{
var beatmapInfo = s.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash);
beatmapInfo.Hash = new_beatmap_hash;
beatmapInfo.ResetOnlineInfo();
});
realm.Run(r => r.Refresh());
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (https://github.com/ppy/osu/pull/22539).
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
checkCount<ScoreInfo>(realm, 1);
// reimport the original beatmap before local modifications
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value);
Assert.That(importAfterUpdate, Is.Not.Null);
Debug.Assert(importAfterUpdate != null);
realm.Run(r => r.Refresh());
// both original and locally modified versions present
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
checkCount<BeatmapSetInfo>(realm, 2);
// score is preserved
checkCount<ScoreInfo>(realm, 1);
// score is transferred to new beatmap
Assert.That(importBeforeUpdate.Value.Beatmaps.First(b => b.Hash == new_beatmap_hash).Scores, Has.Count.EqualTo(0));
Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1));
});
}
[Test]
public void TestScoreLostOnModification()
{

View File

@ -133,6 +133,32 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("objects reverted to original position", () => addedObjects[0].Position == new Vector2(0));
}
[Test]
public void TestGlobalFlipHotkeys()
{
HitCircle addedObject = null;
AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 }));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
AddStep("flip horizontally across playfield", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.H);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("objects flipped horizontally", () => addedObject.Position == new Vector2(OsuPlayfield.BASE_SIZE.X, 0));
AddStep("flip vertically across playfield", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.J);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("objects flipped vertically", () => addedObject.Position == OsuPlayfield.BASE_SIZE);
}
[Test]
public void TestBasicSelect()
{

View File

@ -188,7 +188,7 @@ namespace osu.Game.Tournament.Components
Children = new Drawable[]
{
new DiffPiece(stats),
new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.##}{srExtra}"))
new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.00}{srExtra}"))
}
},
new FillFlowContainer

View File

@ -20,6 +20,7 @@ using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using Realms;
namespace osu.Game.Beatmaps
@ -204,6 +205,15 @@ namespace osu.Game.Beatmaps
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
{
base.PostImport(model, realm, parameters);
// Scores are stored separately from beatmaps, and persist even when a beatmap is modified or deleted.
// Let's reattach any matching scores that exist in the database, based on hash.
foreach (BeatmapInfo beatmap in model.Beatmaps)
{
foreach (var score in realm.All<ScoreInfo>().Where(score => score.BeatmapHash == beatmap.Hash))
score.BeatmapInfo = beatmap;
}
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
}

View File

@ -188,7 +188,7 @@ namespace osu.Game.Collections
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
X = -OsuScrollContainer.SCROLL_BAR_HEIGHT,
X = -OsuScrollContainer.SCROLL_BAR_WIDTH,
Scale = new Vector2(0.65f),
Action = addOrRemove,
});

View File

@ -3,10 +3,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using AutoMapper;
using AutoMapper.Internal;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
using osu.Game.Models;
@ -52,10 +54,23 @@ namespace osu.Game.Database
{
foreach (var beatmap in s.Beatmaps)
{
var existing = d.Beatmaps.FirstOrDefault(b => b.ID == beatmap.ID);
// Importantly, search all of realm for the beatmap (not just the set's beatmaps).
// It may have gotten detached, and if that's the case let's use this opportunity to fix
// things up.
var existingBeatmap = d.Realm.Find<BeatmapInfo>(beatmap.ID);
if (existing != null)
copyChangesToRealm(beatmap, existing);
if (existingBeatmap != null)
{
// As above, reattach if it happens to not be in the set's beatmaps.
if (!d.Beatmaps.Contains(existingBeatmap))
{
Debug.Fail("Beatmaps should never become detached under normal circumstances. If this ever triggers, it should be investigated further.");
Logger.Log("WARNING: One of the difficulties in a beatmap was detached from its set. Please save a copy of logs and report this to devs.", LoggingTarget.Database, LogLevel.Important);
d.Beatmaps.Add(existingBeatmap);
}
copyChangesToRealm(beatmap, existingBeatmap);
}
else
{
var newBeatmap = new BeatmapInfo
@ -64,6 +79,7 @@ namespace osu.Game.Database
BeatmapSet = d,
Ruleset = d.Realm.Find<RulesetInfo>(beatmap.Ruleset.ShortName)
};
d.Beatmaps.Add(newBeatmap);
copyChangesToRealm(beatmap, newBeatmap);
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Containers
public partial class OsuScrollContainer<T> : ScrollContainer<T> where T : Drawable
{
public const float SCROLL_BAR_HEIGHT = 10;
public const float SCROLL_BAR_WIDTH = 10;
public const float SCROLL_BAR_PADDING = 3;
/// <summary>
@ -139,6 +139,8 @@ namespace osu.Game.Graphics.Containers
private readonly Box box;
protected override float MinimumDimSize => SCROLL_BAR_WIDTH * 3;
public OsuScrollbar(Direction scrollDir)
: base(scrollDir)
{
@ -147,7 +149,7 @@ namespace osu.Game.Graphics.Containers
CornerRadius = 5;
// needs to be set initially for the ResizeTo to respect minimum size
Size = new Vector2(SCROLL_BAR_HEIGHT);
Size = new Vector2(SCROLL_BAR_WIDTH);
const float margin = 3;
@ -173,11 +175,10 @@ namespace osu.Game.Graphics.Containers
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)
{
Vector2 size = new Vector2(SCROLL_BAR_HEIGHT)
this.ResizeTo(new Vector2(SCROLL_BAR_WIDTH)
{
[(int)ScrollDirection] = val
};
this.ResizeTo(size, duration, easing);
}, duration, easing);
}
protected override bool OnHover(HoverEvent e)

View File

@ -24,6 +24,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString SignedIn => new TranslatableString(getKey(@"signed_in"), @"Signed in");
/// <summary>
/// "Sign out"
/// </summary>
public static LocalisableString SignOut => new TranslatableString(getKey(@"sign_out"), @"Sign out");
/// <summary>
/// "Account"
/// </summary>

View File

@ -88,6 +88,16 @@ Please try changing your audio device to a working setting.");
/// </summary>
public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!");
/// <summary>
/// "You received a private message from '{0}'. Click to read it!"
/// </summary>
public static LocalisableString PrivateMessageReceived(string username) => new TranslatableString(getKey(@"private_message_received"), @"You received a private message from '{0}'. Click to read it!", username);
/// <summary>
/// "Your name was mentioned in chat by '{0}'. Click to find out why!"
/// </summary>
public static LocalisableString YourNameWasMentioned(string username) => new TranslatableString(getKey(@"your_name_was_mentioned"), @"Your name was mentioned in chat by '{0}'. Click to find out why!", username);
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
@ -154,7 +155,7 @@ namespace osu.Game.Online.Chat
: base(message, channel)
{
Icon = FontAwesome.Solid.Envelope;
Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!";
Text = NotificationsStrings.PrivateMessageReceived(message.Sender.Username);
}
}
@ -164,7 +165,7 @@ namespace osu.Game.Online.Chat
: base(message, channel)
{
Icon = FontAwesome.Solid.At;
Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!";
Text = NotificationsStrings.YourNameWasMentioned(message.Sender.Username);
}
}

View File

@ -1164,7 +1164,9 @@ namespace osu.Game
private void forwardTabletLogsToNotifications()
{
const string tablet_prefix = @"[Tablet] ";
bool notifyOnWarning = true;
bool notifyOnError = true;
Logger.NewEntry += entry =>
{
@ -1175,6 +1177,11 @@ namespace osu.Game
if (entry.Level == LogLevel.Error)
{
if (!notifyOnError)
return;
notifyOnError = false;
Schedule(() =>
{
Notifications.Post(new SimpleNotification
@ -1213,7 +1220,11 @@ namespace osu.Game
Schedule(() =>
{
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
tablet?.Tablet.BindValueChanged(_ => notifyOnWarning = true, true);
tablet?.Tablet.BindValueChanged(_ =>
{
notifyOnWarning = true;
notifyOnError = true;
}, true);
});
}

View File

@ -185,7 +185,7 @@ namespace osu.Game.Overlays.BeatmapSet
OnHovered = beatmap =>
{
showBeatmap(beatmap);
starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.##");
starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.00");
starRatingContainer.FadeIn(100);
},
OnClicked = beatmap => { Beatmap.Value = beatmap; },

View File

@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Login
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))]
AppearOffline,
[LocalisableDescription(typeof(UserVerificationStrings), nameof(UserVerificationStrings.BoxInfoLogoutLink))]
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.SignOut))]
SignOut,
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Overlays
scrollbarBackground = new Box
{
RelativeSizeAxes = Axes.Y,
Width = OsuScrollContainer.SCROLL_BAR_HEIGHT,
Width = OsuScrollContainer.SCROLL_BAR_WIDTH,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Alpha = 0.5f

View File

@ -82,6 +82,7 @@ namespace osu.Game.Scoring
{
Ruleset = ruleset ?? new RulesetInfo();
BeatmapInfo = beatmap ?? new BeatmapInfo();
BeatmapHash = BeatmapInfo.Hash;
RealmUser = realmUser ?? new RealmUser();
ID = Guid.NewGuid();
}

View File

@ -377,10 +377,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X;
float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X;
if (topExcess + bottomExcess < buttons.Height + button_padding)
float minHeight = buttons.ScreenSpaceDrawQuad.Height;
if (topExcess < minHeight && bottomExcess < minHeight)
{
buttons.Anchor = Anchor.BottomCentre;
buttons.Origin = Anchor.BottomCentre;
buttons.Y = Math.Min(0, ToLocalSpace(Parent.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
}
else if (topExcess > bottomExcess)
{

View File

@ -160,13 +160,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (e.Repeat)
return false;
bool handled;
switch (e.Action)
{
case GlobalAction.EditorFlipHorizontally:
return HandleFlip(Direction.Horizontal, true);
ChangeHandler?.BeginChange();
handled = HandleFlip(Direction.Horizontal, true);
ChangeHandler?.EndChange();
return handled;
case GlobalAction.EditorFlipVertically:
return HandleFlip(Direction.Vertical, true);
ChangeHandler?.BeginChange();
handled = HandleFlip(Direction.Vertical, true);
ChangeHandler?.EndChange();
return handled;
}
return false;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
RelativeSizeAxes = Axes.X;
scroll.RelativeSizeAxes = Axes.X;
scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_HEIGHT + OsuScrollContainer.SCROLL_BAR_PADDING * 2;
scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_WIDTH + OsuScrollContainer.SCROLL_BAR_PADDING * 2;
list.RelativeSizeAxes = Axes.Y;
list.AutoSizeAxes = Axes.X;