1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge branch 'master' into editor-metrics

This commit is contained in:
Dean Herbert 2023-07-19 15:00:51 +09:00
commit 7f336b8dac
23 changed files with 153 additions and 58 deletions

View File

@ -8,13 +8,9 @@
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.714.0" />
</ItemGroup>
<ItemGroup>
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.716.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mailto" />
</intent>
</queries>
</manifest>

View File

@ -54,9 +54,6 @@ namespace osu.Desktop
client.OnReady += onReady;
// safety measure for now, until we performance test / improve backoff for failed connections.
client.OnConnectionFailed += (_, _) => client.Deinitialize();
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);

View File

@ -26,7 +26,7 @@
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageReference Include="DiscordRichPresence" Version="1.1.4.20" />
</ItemGroup>
<ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" />

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
}
protected override IEnumerable<HitObject> EnumerateHitObjects(IBeatmap beatmap)
=> base.EnumerateHitObjects(beatmap).OrderBy(ho => (ManiaHitObject)ho, JudgementOrderComparer.DEFAULT);
=> base.EnumerateHitObjects(beatmap).OrderBy(ho => ho, JudgementOrderComparer.DEFAULT);
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override double GetComboScoreChange(JudgementResult result)
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
private class JudgementOrderComparer : IComparer<ManiaHitObject>
private class JudgementOrderComparer : IComparer<HitObject>
{
public static readonly JudgementOrderComparer DEFAULT = new JudgementOrderComparer();
public int Compare(ManiaHitObject? x, ManiaHitObject? y)
public int Compare(HitObject? x, HitObject? y)
{
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(x, null)) return -1;
@ -52,7 +52,9 @@ namespace osu.Game.Rulesets.Mania.Scoring
if (x is Note && y is not Note) return -1;
if (x is not Note && y is Note) return 1;
return x.Column.CompareTo(y.Column);
return x is ManiaHitObject maniaX && y is ManiaHitObject maniaY
? maniaX.Column.CompareTo(maniaY.Column)
: 0;
}
}
}

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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -10,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
@ -36,11 +38,12 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
() => Is.EqualTo(expectedResult));
}
protected void PerformTest(List<ReplayFrame> frames, Beatmap<TaikoHitObject>? beatmap = null)
protected void PerformTest(List<ReplayFrame> frames, Beatmap<TaikoHitObject>? beatmap = null, Mod[]? mods = null)
{
AddStep("load player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(beatmap);
SelectedMods.Value = mods ?? Array.Empty<Mod>();
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });

View File

@ -75,6 +75,25 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitNoneStrongDrumRoll()
{
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; ++i)
{
AssertResult<DrumRollTick>(i, HitResult.IgnoreMiss);
AssertResult<DrumRollTick.StrongNestedHit>(i, HitResult.IgnoreMiss);
}
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitAllStrongDrumRollWithOneKey()
{

View File

@ -4,10 +4,14 @@
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests.Judgements
{
@ -157,5 +161,58 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
AssertJudgementCount(1);
AssertResult<Hit>(0, HitResult.Ok);
}
[Test]
public void TestStrongHitOneKeyWithHidden()
{
const double hit_time = 1000;
var beatmap = CreateBeatmap(new Hit
{
Type = HitType.Centre,
StartTime = hit_time,
IsStrong = true
});
var hitWindows = new TaikoHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre),
}, beatmap, new Mod[] { new TaikoModHidden() });
AssertJudgementCount(2);
AssertResult<Hit>(0, HitResult.Ok);
AssertResult<Hit.StrongNestedHit>(0, HitResult.IgnoreMiss);
}
[Test]
public void TestStrongHitTwoKeysWithHidden()
{
const double hit_time = 1000;
var beatmap = CreateBeatmap(new Hit
{
Type = HitType.Centre,
StartTime = hit_time,
IsStrong = true
});
var hitWindows = new TaikoHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre),
new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) + DrawableHit.StrongNestedHit.SECOND_HIT_WINDOW - 2, TaikoAction.LeftCentre, TaikoAction.RightCentre),
}, beatmap, new Mod[] { new TaikoModHidden() });
AssertJudgementCount(2);
AssertResult<Hit>(0, HitResult.Ok);
AssertResult<Hit.StrongNestedHit>(0, HitResult.LargeBonus);
}
}
}

View File

@ -62,6 +62,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
hitObject.LifetimeEnd = state == ArmedState.Idle || !hitObject.AllJudged
? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss)
: hitObject.HitStateUpdateTime;
// extend the lifetime end of the object in order to allow its nested strong hit (if any) to be judged.
hitObject.LifetimeEnd += DrawableHit.StrongNestedHit.SECOND_HIT_WINDOW;
}
break;

View File

@ -195,14 +195,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
public override void OnKilled()
{
base.OnKilled();
if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged)
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
}
}

View File

@ -108,14 +108,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
public override void OnKilled()
{
base.OnKilled();
if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged)
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
}
}

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 osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -16,5 +17,17 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
: base(nestedHit)
{
}
public override void OnKilled()
{
base.OnKilled();
// usually, the strong nested hit isn't judged itself, it is judged by its parent object.
// however, in rare cases (see: drum rolls, hits with hidden active),
// it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object.
// this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing.
if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime())
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
}
}

View File

@ -722,6 +722,30 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
}
[Test]
public void TestForceExitWithOperationInProgress()
{
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
AddStep("start ongoing operation", () =>
{
Game.Notifications.Post(new ProgressNotification
{
Text = "Something is still running",
Progress = 0.5f,
State = ProgressNotificationState.Active,
});
});
AddStep("attempt exit", () =>
{
for (int i = 0; i < 2; ++i)
Game.ScreenStack.CurrentScreen.Exit();
});
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
}
[Test]
public void TestExitGameFromSongSelect()
{

View File

@ -26,6 +26,9 @@ namespace osu.Game.Database
if (score.IsLegacyScore)
return false;
if (score.TotalScoreVersion > 30000002)
return false;
// Recalculate the old-style standardised score to see if this was an old lazer score.
bool oldScoreMatchesExpectations = GetOldStandardised(score) == score.TotalScore;
// Some older scores don't have correct statistics populated, so let's give them benefit of doubt.

View File

@ -229,7 +229,7 @@ namespace osu.Game.Overlays.Dialog
{
// Buttons are regularly added in BDL or LoadComplete, so let's schedule to ensure
// they are ready to be pressed.
Schedule(() => Buttons.OfType<T>().First().TriggerClick());
Scheduler.AddOnce(() => Buttons.OfType<T>().FirstOrDefault()?.TriggerClick());
}
protected override bool OnKeyDown(KeyDownEvent e)

View File

@ -49,6 +49,13 @@ namespace osu.Game.Scoring.Legacy
scoreInfo.IsLegacyScore = version < LegacyScoreEncoder.FIRST_LAZER_VERSION;
// TotalScoreVersion gets initialised to LATEST_VERSION.
// In the case where the incoming score has either an osu!stable or old lazer version, we need
// to mark it with the correct version increment to trigger reprocessing to new standardised scoring.
//
// See StandardisedScoreMigrationTools.ShouldMigrateToNewStandardised().
scoreInfo.TotalScoreVersion = version < 30000002 ? 30000001 : LegacyScoreEncoder.LATEST_VERSION;
string beatmapHash = sr.ReadString();
workingBeatmap = GetBeatmap(beatmapHash);

View File

@ -17,6 +17,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
@ -49,6 +50,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private MultiplayerClient client { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
@ -251,10 +255,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public override bool OnExiting(ScreenExitEvent e)
{
// the room may not be left immediately after a disconnection due to async flow,
// so checking the IsConnected status is also required.
if (client.Room == null || !client.IsConnected.Value)
// so checking the MultiplayerClient / IAPIAccess statuses is also required.
if (client.Room == null || !client.IsConnected.Value || api.State.Value != APIState.Online)
{
// room has not been created yet; exit immediately.
// room has not been created yet or we're offline; exit immediately.
return base.OnExiting(e);
}

View File

@ -75,8 +75,7 @@ namespace osu.Game.Screens.Select.Carousel
if (changes?.HasCollectionChanges() == false)
return;
ScoreInfo? topScore = sender.Detach().OrderByTotalScore().FirstOrDefault();
ScoreInfo? topScore = sender.MaxBy(info => (info.TotalScore, -info.Date.UtcDateTime.Ticks));
updateable.Rank = topScore?.Rank;
updateable.Alpha = topScore != null ? 1 : 0;
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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
@ -47,7 +47,7 @@ namespace osu.Game.Updater
{
Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n"
+ "Check with your package manager / provider to bring osu! up-to-date!",
Icon = FontAwesome.Solid.Upload,
Icon = FontAwesome.Solid.Download,
});
return true;

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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
@ -54,7 +54,7 @@ namespace osu.Game.Updater
{
Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n"
+ "Click here to download the new version, which can be installed over the top of your existing installation",
Icon = FontAwesome.Solid.Upload,
Icon = FontAwesome.Solid.Download,
Activated = () =>
{
host.OpenUrlExternally(getBestUrl(latest));

View File

@ -134,7 +134,7 @@ namespace osu.Game.Updater
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Upload,
Icon = FontAwesome.Solid.Download,
Size = new Vector2(34),
Colour = OsuColour.Gray(0.2f),
Depth = float.MaxValue,

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="11.1.2" />
<PackageReference Include="ppy.osu.Framework" Version="2023.714.0" />
<PackageReference Include="ppy.osu.Framework" Version="2023.716.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.707.0" />
<PackageReference Include="Sentry" Version="3.28.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" />

View File

@ -16,6 +16,6 @@
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.714.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.716.0" />
</ItemGroup>
</Project>