1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 12:22:56 +08:00

Merge branch 'master' into refactor-metadata-lookup-sources

This commit is contained in:
Dean Herbert 2024-02-11 20:10:41 +08:00 committed by GitHub
commit 1df38b54a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 174 additions and 25 deletions

View File

@ -68,6 +68,7 @@ Aside from the above, below is a brief checklist of things to watch out when you
- Please do not make code changes via the GitHub web interface.
- Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing).
- Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so.
- **Do not run the game in release configuration at any point during your testing** (the sole exception to this being benchmarks). Using release is an unnecessary and harmful practice, and can even lead to you losing your local realm database if you start making changes to the schema. The debug configuration has a completely separated full-stack environment, including a development website instance at https://dev.ppy.sh/. It is permitted to register an account on that development instance for testing purposes and not worry about multi-accounting infractions.
After you're done with your changes and you wish to open the PR, please observe the following recommendations:

View File

@ -124,6 +124,113 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count == 2);
}
[Test]
public void TestControlClickAddsControlPointsIfSingleSliderSelected()
{
var firstSlider = new Slider
{
StartTime = 0,
Position = new Vector2(0, 0),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100))
}
}
};
var secondSlider = new Slider
{
StartTime = 1000,
Position = new Vector2(200, 200),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100, -100))
}
}
};
AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider }));
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { secondSlider }));
AddStep("move mouse to middle of slider", () =>
{
var pos = blueprintContainer.SelectionBlueprints
.First(s => s.Item == secondSlider)
.ChildrenOfType<SliderBodyPiece>().First()
.ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("control-click left mouse", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1));
AddAssert("slider has 3 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(3));
}
[Test]
public void TestControlClickDoesNotAddSliderControlPointsIfMultipleObjectsSelected()
{
var firstSlider = new Slider
{
StartTime = 0,
Position = new Vector2(0, 0),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100))
}
}
};
var secondSlider = new Slider
{
StartTime = 1000,
Position = new Vector2(200, 200),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(100, -100))
}
}
};
AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider }));
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange(new HitObject[] { firstSlider, secondSlider }));
AddStep("move mouse to middle of slider", () =>
{
var pos = blueprintContainer.SelectionBlueprints
.First(s => s.Item == secondSlider)
.ChildrenOfType<SliderBodyPiece>().First()
.ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("control-click left mouse", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("selection not preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1));
AddAssert("second slider not selected",
() => blueprintContainer.SelectionBlueprints.First(s => s.Item == secondSlider).IsSelected,
() => Is.False);
AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2));
}
private ComposeBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();

View File

@ -171,7 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false; // Allow right click to be handled by context menu
case MouseButton.Left:
if (e.ControlPressed && IsSelected)
// If there's more than two objects selected, ctrl+click should deselect
if (e.ControlPressed && IsSelected && selectedObjects.Count < 2)
{
changeHandler?.BeginChange();
placementControlPoint = addControlPoint(e.MousePosition);

View File

@ -206,6 +206,12 @@ namespace osu.Game.Tests.Visual.Online
Total = 50
},
SupportLevel = 2,
Location = "Somewhere",
Interests = "Rhythm games",
Occupation = "Gamer",
Twitter = "test_user",
Discord = "test_user",
Website = "https://google.com",
};
}
}

View File

@ -23,9 +23,12 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(exception != null);
string message = exception.GetHubExceptionMessage() ?? exception.Message;
if (exception.GetHubExceptionMessage() is string message)
// Hub exceptions generally contain something we can show the user directly.
Logger.Log(message, level: LogLevel.Important);
else
Logger.Error(exception, $"Unobserved exception occurred via {nameof(FireAndForget)} call: {exception.Message}");
Logger.Log(message, level: LogLevel.Important);
onError?.Invoke(exception);
}
else

View File

@ -1,8 +1,10 @@
// 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.Globalization;
using System.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
namespace osu.Game.Online.Rooms
@ -11,12 +13,16 @@ namespace osu.Game.Online.Rooms
{
private readonly long roomId;
private readonly long playlistItemId;
private readonly BeatmapInfo beatmapInfo;
private readonly int rulesetId;
private readonly string versionHash;
public CreateRoomScoreRequest(long roomId, long playlistItemId, string versionHash)
public CreateRoomScoreRequest(long roomId, long playlistItemId, BeatmapInfo beatmapInfo, int rulesetId, string versionHash)
{
this.roomId = roomId;
this.playlistItemId = playlistItemId;
this.beatmapInfo = beatmapInfo;
this.rulesetId = rulesetId;
this.versionHash = versionHash;
}
@ -25,6 +31,8 @@ namespace osu.Game.Online.Rooms
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
req.AddParameter("version_hash", versionHash);
req.AddParameter("beatmap_hash", beatmapInfo.MD5Hash);
req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture));
return req;
}

View File

@ -144,8 +144,8 @@ namespace osu.Game.Overlays.Profile.Header
bool anyInfoAdded = false;
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location);
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Heart, user.Interests);
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarkerAlt, user.Location);
anyInfoAdded |= tryAddInfo(FontAwesome.Regular.Heart, user.Interests);
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation);
if (anyInfoAdded)
@ -171,7 +171,7 @@ namespace osu.Game.Overlays.Profile.Header
bottomLinkContainer.AddIcon(icon, text =>
{
text.Font = text.Font.With(size: 10);
text.Font = text.Font.With(icon.Family, 10, icon.Weight);
text.Colour = iconColour;
});

View File

@ -4,7 +4,6 @@
#nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Difficulty.Preprocessing
@ -65,8 +64,16 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
EndTime = hitObject.GetEndTime() / clockRate;
}
public DifficultyHitObject Previous(int backwardsIndex) => difficultyHitObjects.ElementAtOrDefault(Index - (backwardsIndex + 1));
public DifficultyHitObject Previous(int backwardsIndex)
{
int index = Index - (backwardsIndex + 1);
return index >= 0 && index < difficultyHitObjects.Count ? difficultyHitObjects[index] : default;
}
public DifficultyHitObject Next(int forwardsIndex) => difficultyHitObjects.ElementAtOrDefault(Index + (forwardsIndex + 1));
public DifficultyHitObject Next(int forwardsIndex)
{
int index = Index + (forwardsIndex + 1);
return index >= 0 && index < difficultyHitObjects.Count ? difficultyHitObjects[index] : default;
}
}
}

View File

@ -4,6 +4,7 @@
#nullable disable
using System.Diagnostics;
using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Scoring;
@ -30,7 +31,16 @@ namespace osu.Game.Screens.Play
if (!(Room.RoomID.Value is long roomId))
return null;
return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Game.VersionHash);
int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID;
int rulesetId = Ruleset.Value.OnlineID;
if (beatmapId <= 0)
return null;
if (!Ruleset.Value.IsLegacyRuleset())
return null;
return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Beatmap.Value.BeatmapInfo, rulesetId, Game.VersionHash);
}
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token)

View File

@ -1,9 +1,8 @@
// 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 System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@ -22,10 +21,10 @@ namespace osu.Game.Updater
/// </summary>
public partial class SimpleUpdateManager : UpdateManager
{
private string version;
private string version = null!;
[Resolved]
private GameHost host { get; set; }
private GameHost host { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
@ -48,7 +47,7 @@ namespace osu.Game.Updater
version = version.Split('-').First();
string latestTagName = latest.TagName.Split('-').First();
if (latestTagName != version)
if (latestTagName != version && tryGetBestUrl(latest, out string? url))
{
Notifications.Post(new SimpleNotification
{
@ -57,7 +56,7 @@ namespace osu.Game.Updater
Icon = FontAwesome.Solid.Download,
Activated = () =>
{
host.OpenUrlExternally(getBestUrl(latest));
host.OpenUrlExternally(url);
return true;
}
});
@ -74,9 +73,10 @@ namespace osu.Game.Updater
return false;
}
private string getBestUrl(GitHubRelease release)
private bool tryGetBestUrl(GitHubRelease release, [NotNullWhen(true)] out string? url)
{
GitHubAsset bestAsset = null;
url = null;
GitHubAsset? bestAsset = null;
switch (RuntimeInfo.OS)
{
@ -94,17 +94,23 @@ namespace osu.Game.Updater
break;
case RuntimeInfo.Platform.iOS:
// iOS releases are available via testflight. this link seems to work well enough for now.
// see https://stackoverflow.com/a/32960501
return "itms-beta://beta.itunes.apple.com/v1/app/1447765923";
if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true)
// iOS releases are available via testflight. this link seems to work well enough for now.
// see https://stackoverflow.com/a/32960501
url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923";
break;
case RuntimeInfo.Platform.Android:
// on our testing device this causes the download to magically disappear.
//bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk"));
if (release.Assets?.Exists(f => f.Name.EndsWith(".apk", StringComparison.Ordinal)) == true)
// on our testing device using the .apk URL causes the download to magically disappear.
url = release.HtmlUrl;
break;
}
return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl;
url ??= bestAsset?.BrowserDownloadUrl;
return url != null;
}
}
}