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:
commit
1df38b54a1
@ -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:
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user