mirror of
https://github.com/ppy/osu.git
synced 2025-03-20 03:47:28 +08:00
Merge branch 'master' into tournament-ban-count
This commit is contained in:
commit
2605aafe24
4
.github/workflows/diffcalc.yml
vendored
4
.github/workflows/diffcalc.yml
vendored
@ -189,8 +189,8 @@ jobs:
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
# Add comment environment
|
||||
echo $COMMENT_BODY | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do
|
||||
opt=$(echo ${line} | cut -d '=' -f1)
|
||||
echo "$COMMENT_BODY" | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do
|
||||
opt=$(echo "${line}" | cut -d '=' -f1)
|
||||
sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||
done
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1127.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1201.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -5,4 +5,12 @@
|
||||
<!-- for editor usage -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
</manifest>
|
||||
<!--
|
||||
READ_MEDIA_* permissions are available only on API 33 or greater. Devices with older android versions
|
||||
don't understand the new permissions, so request the old READ_EXTERNAL_STORAGE permission to get storage access.
|
||||
Since the old permission has no effect on >= API 33, don't request it.
|
||||
|
||||
Care needs to be taken to ensure runtime permission checks target the correct permission for the API level.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||
</manifest>
|
||||
|
@ -4,11 +4,19 @@
|
||||
using System;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Scoring
|
||||
{
|
||||
public partial class CatchScoreProcessor : ScoreProcessor
|
||||
{
|
||||
private const double accuracy_cutoff_x = 1;
|
||||
private const double accuracy_cutoff_s = 0.98;
|
||||
private const double accuracy_cutoff_a = 0.94;
|
||||
private const double accuracy_cutoff_b = 0.9;
|
||||
private const double accuracy_cutoff_c = 0.85;
|
||||
private const double accuracy_cutoff_d = 0;
|
||||
|
||||
private const int combo_cap = 200;
|
||||
private const double combo_base = 4;
|
||||
|
||||
@ -26,5 +34,50 @@ namespace osu.Game.Rulesets.Catch.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(combo_cap, combo_base));
|
||||
|
||||
public override ScoreRank RankFromAccuracy(double accuracy)
|
||||
{
|
||||
if (accuracy == accuracy_cutoff_x)
|
||||
return ScoreRank.X;
|
||||
if (accuracy >= accuracy_cutoff_s)
|
||||
return ScoreRank.S;
|
||||
if (accuracy >= accuracy_cutoff_a)
|
||||
return ScoreRank.A;
|
||||
if (accuracy >= accuracy_cutoff_b)
|
||||
return ScoreRank.B;
|
||||
if (accuracy >= accuracy_cutoff_c)
|
||||
return ScoreRank.C;
|
||||
|
||||
return ScoreRank.D;
|
||||
}
|
||||
|
||||
public override double AccuracyCutoffFromRank(ScoreRank rank)
|
||||
{
|
||||
switch (rank)
|
||||
{
|
||||
case ScoreRank.X:
|
||||
case ScoreRank.XH:
|
||||
return accuracy_cutoff_x;
|
||||
|
||||
case ScoreRank.S:
|
||||
case ScoreRank.SH:
|
||||
return accuracy_cutoff_s;
|
||||
|
||||
case ScoreRank.A:
|
||||
return accuracy_cutoff_a;
|
||||
|
||||
case ScoreRank.B:
|
||||
return accuracy_cutoff_b;
|
||||
|
||||
case ScoreRank.C:
|
||||
return accuracy_cutoff_c;
|
||||
|
||||
case ScoreRank.D:
|
||||
return accuracy_cutoff_d;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rank), rank, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs
Normal file
31
osu.Game.Rulesets.Mania.Tests/ManiaHealthProcessorTest.cs
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ManiaHealthProcessorTest
|
||||
{
|
||||
[Test]
|
||||
public void TestNoDrain()
|
||||
{
|
||||
var processor = new ManiaHealthProcessor(0);
|
||||
processor.ApplyBeatmap(new ManiaBeatmap(new StageDefinition(4))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 0 },
|
||||
new Note { StartTime = 1000 },
|
||||
}
|
||||
});
|
||||
|
||||
// No matter what, mania doesn't have passive HP drain.
|
||||
Assert.That(processor.DrainRate, Is.Zero);
|
||||
}
|
||||
}
|
||||
}
|
@ -174,9 +174,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
switch (original)
|
||||
{
|
||||
case IHasDistance:
|
||||
case IHasPath:
|
||||
{
|
||||
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
||||
var generator = new PathObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
||||
conversion = generator;
|
||||
|
||||
var positionData = original as IHasPosition;
|
||||
|
@ -22,13 +22,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <summary>
|
||||
/// A pattern generator for IHasDistance hit objects.
|
||||
/// </summary>
|
||||
internal class DistanceObjectPatternGenerator : PatternGenerator
|
||||
internal class PathObjectPatternGenerator : PatternGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Base osu! slider scoring distance.
|
||||
/// </summary>
|
||||
private const float osu_base_scoring_distance = 100;
|
||||
|
||||
public readonly int StartTime;
|
||||
public readonly int EndTime;
|
||||
public readonly int SegmentDuration;
|
||||
@ -36,17 +31,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
private PatternType convertType;
|
||||
|
||||
public DistanceObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
||||
public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
||||
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
|
||||
{
|
||||
convertType = PatternType.None;
|
||||
if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
|
||||
convertType = PatternType.LowProbability;
|
||||
|
||||
var distanceData = hitObject as IHasDistance;
|
||||
var pathData = hitObject as IHasPath;
|
||||
var repeatsData = hitObject as IHasRepeats;
|
||||
|
||||
Debug.Assert(distanceData != null);
|
||||
Debug.Assert(pathData != null);
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
|
||||
@ -60,8 +55,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||
|
||||
double distance = pathData.Path.ExpectedDistance.Value ?? 0;
|
||||
|
||||
// This matches stable's calculation.
|
||||
EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier);
|
||||
EndTime = (int)Math.Floor(StartTime + distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier);
|
||||
|
||||
SegmentDuration = (EndTime - StartTime) / SpanCount;
|
||||
}
|
@ -15,6 +15,15 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
}
|
||||
|
||||
protected override double ComputeDrainRate()
|
||||
{
|
||||
// Base call is run only to compute HP recovery (namely, `HpMultiplierNormal`).
|
||||
// This closely mirrors (broken) behaviour of stable and as such is preserved unchanged.
|
||||
base.ComputeDrainRate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override IEnumerable<HitObject> EnumerateTopLevelHitObjects() => Beatmap.HitObjects;
|
||||
|
||||
protected override IEnumerable<HitObject> EnumerateNestedHitObjects(HitObject hitObject) => hitObject.NestedHitObjects;
|
||||
|
@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case IHasDistance distanceData:
|
||||
case IHasPath pathData:
|
||||
{
|
||||
if (shouldConvertSliderToHits(obj, beatmap, distanceData, out int taikoDuration, out double tickSpacing))
|
||||
if (shouldConvertSliderToHits(obj, beatmap, pathData, out int taikoDuration, out double tickSpacing))
|
||||
{
|
||||
IList<IList<HitSampleInfo>> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||
|
||||
@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out int taikoDuration, out double tickSpacing)
|
||||
private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasPath pathData, out int taikoDuration, out double tickSpacing)
|
||||
{
|
||||
// DO NOT CHANGE OR REFACTOR ANYTHING IN HERE WITHOUT TESTING AGAINST _ALL_ BEATMAPS.
|
||||
// Some of these calculations look redundant, but they are not - extremely small floating point errors are introduced to maintain 1:1 compatibility with stable.
|
||||
@ -182,7 +182,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
||||
int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
|
||||
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||
double distance = pathData.Path.ExpectedDistance.Value ?? 0;
|
||||
|
||||
// Do not combine the following two lines!
|
||||
distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||
distance *= spans;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||
|
||||
|
@ -15,12 +15,14 @@ using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
@ -1156,5 +1158,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(((IHasComboInformation)playable.HitObjects[17]).ComboIndexWithOffsets, Is.EqualTo(9));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderConversionWithCustomDistance([Values("taiko", "mania")] string rulesetName)
|
||||
{
|
||||
using (var resStream = TestResources.OpenResource("custom-slider-length.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Ruleset ruleset;
|
||||
|
||||
switch (rulesetName)
|
||||
{
|
||||
case "taiko":
|
||||
ruleset = new TaikoRuleset();
|
||||
break;
|
||||
|
||||
case "mania":
|
||||
ruleset = new ManiaRuleset();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rulesetName), rulesetName, null);
|
||||
}
|
||||
|
||||
var decoder = Decoder.GetDecoder<Beatmap>(stream);
|
||||
var working = new TestWorkingBeatmap(decoder.Decode(stream));
|
||||
IBeatmap beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
|
||||
|
||||
Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
osu.Game.Tests/Resources/custom-slider-length.osu
Normal file
19
osu.Game.Tests/Resources/custom-slider-length.osu
Normal file
@ -0,0 +1,19 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:7
|
||||
OverallDifficulty:7
|
||||
ApproachRate:10
|
||||
SliderMultiplier:1.7
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
29,333.333333333333,4,1,0,100,1,0
|
||||
29,-10000,4,1,0,100,0,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,29,6,0,P|384:192|384:192,1,159.375
|
@ -378,6 +378,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}, users);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAbortMatch()
|
||||
{
|
||||
AddStep("setup client", () =>
|
||||
{
|
||||
multiplayerClient.Setup(m => m.StartMatch())
|
||||
.Callback(() =>
|
||||
{
|
||||
multiplayerClient.Raise(m => m.LoadRequested -= null);
|
||||
multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad;
|
||||
|
||||
// The local user state doesn't really matter, so let's do the same as the base implementation for these tests.
|
||||
changeUserState(localUser.UserID, MultiplayerUserState.Idle);
|
||||
});
|
||||
|
||||
multiplayerClient.Setup(m => m.AbortMatch())
|
||||
.Callback(() =>
|
||||
{
|
||||
multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open;
|
||||
raiseRoomUpdated();
|
||||
});
|
||||
});
|
||||
|
||||
// Ready
|
||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||
|
||||
// Start match
|
||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||
AddUntilStep("countdown button disabled", () => !this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
||||
|
||||
// Abort
|
||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||
AddStep("check abort request received", () => multiplayerClient.Verify(m => m.AbortMatch(), Times.Once));
|
||||
}
|
||||
|
||||
private void verifyGameplayStartFlow()
|
||||
{
|
||||
checkLocalUserState(MultiplayerUserState.Ready);
|
||||
|
@ -12,9 +12,12 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -38,6 +41,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
BarHitErrorMeter hitErrorMeter = null;
|
||||
@ -98,6 +104,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
AddUntilStep("wait for components", () => skinEditor.ChildrenOfType<SkinBlueprint>().Any());
|
||||
@ -162,6 +172,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
openSkinEditor();
|
||||
AddStep("select DT", () => Game.SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
AddAssert("DT still selected", () => ((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Single() is OsuModDoubleTime);
|
||||
@ -174,6 +187,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
openSkinEditor();
|
||||
AddStep("select relax and spun out", () => Game.SelectedMods.Value = new Mod[] { new OsuModRelax(), new OsuModSpunOut() });
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
||||
@ -186,6 +202,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
openSkinEditor();
|
||||
AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() });
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
||||
@ -198,6 +217,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
openSkinEditor();
|
||||
AddStep("select cinema", () => Game.SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
|
||||
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
||||
@ -240,6 +262,43 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("editor sidebars not empty", () => skinEditor.ChildrenOfType<EditorSidebar>().SelectMany(sidebar => sidebar.Children).Count(), () => Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenSkinEditorGameplaySceneOnBeatmapWithNoObjects()
|
||||
{
|
||||
AddStep("set dummy beatmap", () => Game.Beatmap.SetDefault());
|
||||
advanceToSongSelect();
|
||||
|
||||
AddStep("create empty beatmap", () => Game.BeatmapManager.CreateNew(new OsuRuleset().RulesetInfo, new GuestUser()));
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
openSkinEditor();
|
||||
switchToGameplayScene();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenSkinEditorGameplaySceneWhenDummyBeatmapActive()
|
||||
{
|
||||
AddStep("set dummy beatmap", () => Game.Beatmap.SetDefault());
|
||||
|
||||
openSkinEditor();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive()
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
AddStep("select mania difficulty", () =>
|
||||
{
|
||||
var beatmap = beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 3);
|
||||
Game.Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(beatmap);
|
||||
});
|
||||
|
||||
openSkinEditor();
|
||||
switchToGameplayScene();
|
||||
}
|
||||
|
||||
private void advanceToSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
@ -266,9 +325,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
private void switchToGameplayScene()
|
||||
{
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("Click gameplay scene button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text.ToString() == "Gameplay"));
|
||||
|
@ -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 System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -56,92 +54,76 @@ namespace osu.Game.Tests.Visual.Online
|
||||
textContainer.Clear();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestLinksGeneral()
|
||||
[TestCase("test!")]
|
||||
[TestCase("dev.ppy.sh!")]
|
||||
[TestCase("https://dev.ppy.sh!", LinkAction.External)]
|
||||
[TestCase("http://dev.ppy.sh!", LinkAction.External)]
|
||||
[TestCase("forgothttps://dev.ppy.sh!", LinkAction.External)]
|
||||
[TestCase("forgothttp://dev.ppy.sh!", LinkAction.External)]
|
||||
[TestCase("00:12:345 - Test?", LinkAction.OpenEditorTimestamp)]
|
||||
[TestCase("00:12:345 (1,2) - Test?", LinkAction.OpenEditorTimestamp)]
|
||||
[TestCase($"{OsuGameBase.OSU_PROTOCOL}edit/00:12:345 - Test?", LinkAction.OpenEditorTimestamp)]
|
||||
[TestCase($"{OsuGameBase.OSU_PROTOCOL}edit/00:12:345 (1,2) - Test?", LinkAction.OpenEditorTimestamp)]
|
||||
[TestCase($"{OsuGameBase.OSU_PROTOCOL}00:12:345 - not an editor timestamp", LinkAction.External)]
|
||||
[TestCase("Wiki link for tasty [[Performance Points]]", LinkAction.OpenWiki)]
|
||||
[TestCase("(osu forums)[https://dev.ppy.sh/forum] (old link format)", LinkAction.External)]
|
||||
[TestCase("[https://dev.ppy.sh/home New site] (new link format)", LinkAction.External)]
|
||||
[TestCase("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", LinkAction.External)]
|
||||
[TestCase("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", LinkAction.External)]
|
||||
[TestCase("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External)]
|
||||
[TestCase("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", LinkAction.External)]
|
||||
[TestCase("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", LinkAction.External)]
|
||||
[TestCase("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", LinkAction.External, LinkAction.OpenWiki)]
|
||||
[TestCase("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).")] // note that there's 0 links here (they get removed if a channel is not found)
|
||||
[TestCase("Join my multiplayer game osump://12346.", LinkAction.JoinMultiplayerMatch)]
|
||||
[TestCase("Join my multiplayer gameosump://12346.", LinkAction.JoinMultiplayerMatch)]
|
||||
[TestCase("Join my [multiplayer game](osump://12346).", LinkAction.JoinMultiplayerMatch)]
|
||||
[TestCase($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", LinkAction.OpenChannel)]
|
||||
[TestCase($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", LinkAction.OpenChannel)]
|
||||
[TestCase($"Join my{OsuGameBase.OSU_PROTOCOL}chan/#english.", LinkAction.OpenChannel)]
|
||||
[TestCase("Join my #english or #japanese channels.", LinkAction.OpenChannel, LinkAction.OpenChannel)]
|
||||
[TestCase("Join my #english or #nonexistent #hashtag channels.", LinkAction.OpenChannel)]
|
||||
[TestCase("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20")]
|
||||
public void TestLinksGeneral(string text, params LinkAction[] actions)
|
||||
{
|
||||
int messageIndex = 0;
|
||||
addMessageWithChecks(text, expectedActions: actions);
|
||||
}
|
||||
|
||||
addMessageWithChecks("test!");
|
||||
addMessageWithChecks("dev.ppy.sh!");
|
||||
addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("http://dev.ppy.sh!", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("forgothttps://dev.ppy.sh!", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("forgothttp://dev.ppy.sh!", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp);
|
||||
addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.OpenWiki);
|
||||
addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmapSet);
|
||||
addMessageWithChecks("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- <Version 0>]", 1, true, expectedActions: LinkAction.OpenBeatmap);
|
||||
addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", 3,
|
||||
expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External });
|
||||
addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2,
|
||||
expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki });
|
||||
// note that there's 0 links here (they get removed if a channel is not found)
|
||||
addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).");
|
||||
addMessageWithChecks("I am important!", 0, false, true);
|
||||
addMessageWithChecks("feels important", 0, true, true);
|
||||
addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
|
||||
addMessageWithChecks("Join my multiplayer gameosump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
|
||||
addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
|
||||
addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks($"Join my{OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel });
|
||||
addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks("Hello world\uD83D\uDE12(<--This is an emoji). There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20");
|
||||
[TestCase("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- <Version 0>]", true, false, LinkAction.OpenBeatmapSet)]
|
||||
[TestCase("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- <Version 0>]", true, false, LinkAction.OpenBeatmap)]
|
||||
[TestCase("I am important!", false, true)]
|
||||
[TestCase("feels important", true, true)]
|
||||
[TestCase("likes to post this [https://dev.ppy.sh/home link].", true, true, LinkAction.External)]
|
||||
public void TestActionAndImportantLinks(string text, bool isAction, bool isImportant, params LinkAction[] expectedActions)
|
||||
{
|
||||
addMessageWithChecks(text, isAction, isImportant, expectedActions);
|
||||
}
|
||||
|
||||
void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions)
|
||||
private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions)
|
||||
{
|
||||
ChatLine newLine = null!;
|
||||
|
||||
AddStep("add message", () =>
|
||||
{
|
||||
ChatLine newLine = null;
|
||||
int index = messageIndex++;
|
||||
newLine = new ChatLine(new DummyMessage(text, isAction, isImportant));
|
||||
textContainer.Add(newLine);
|
||||
});
|
||||
|
||||
AddStep("add message", () =>
|
||||
{
|
||||
newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index));
|
||||
textContainer.Add(newLine);
|
||||
});
|
||||
AddAssert("msg has the right action", () => newLine.Message.Links.Select(l => l.Action), () => Is.EqualTo(expectedActions));
|
||||
AddAssert($"msg shows {expectedActions.Length} link(s)", isShowingLinks);
|
||||
|
||||
AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount);
|
||||
AddAssert($"msg #{index} has the right action", hasExpectedActions);
|
||||
//AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
|
||||
AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks);
|
||||
bool isShowingLinks()
|
||||
{
|
||||
bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour);
|
||||
|
||||
bool hasExpectedActions()
|
||||
{
|
||||
var expectedActionsList = expectedActions.ToList();
|
||||
Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White;
|
||||
|
||||
if (expectedActionsList.Count != newLine.Message.Links.Count)
|
||||
return false;
|
||||
var linkCompilers = newLine.DrawableContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
|
||||
var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts);
|
||||
|
||||
for (int i = 0; i < newLine.Message.Links.Count; i++)
|
||||
{
|
||||
var action = newLine.Message.Links[i].Action;
|
||||
if (action != expectedActions[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast<OsuSpriteText>().All(sprite => sprite.Font.Italics);
|
||||
|
||||
bool isShowingLinks()
|
||||
{
|
||||
bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour);
|
||||
|
||||
Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White;
|
||||
|
||||
var linkCompilers = newLine.DrawableContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
|
||||
var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts);
|
||||
|
||||
return linkSprites.All(d => d.Colour == linkColour)
|
||||
&& newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour);
|
||||
}
|
||||
return linkSprites.All(d => d.Colour == linkColour)
|
||||
&& newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour)
|
||||
&& linkCompilers.Count == expectedActions.Length;
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +137,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
addEchoWithWait("[https://dev.ppy.sh/forum let's try multiple words too!]");
|
||||
addEchoWithWait("(long loading times! clickable while loading?)[https://dev.ppy.sh/home]", null, 5000);
|
||||
|
||||
void addEchoWithWait(string text, string completeText = null, double delay = 250)
|
||||
void addEchoWithWait(string text, string? completeText = null, double delay = 250)
|
||||
{
|
||||
int index = messageIndex++;
|
||||
|
||||
@ -184,21 +166,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
private static long messageCounter;
|
||||
|
||||
internal static readonly APIUser TEST_SENDER_BACKGROUND = new APIUser
|
||||
{
|
||||
Username = @"i-am-important",
|
||||
Id = 42,
|
||||
Colour = "#250cc9",
|
||||
};
|
||||
|
||||
internal static readonly APIUser TEST_SENDER = new APIUser
|
||||
{
|
||||
Username = @"Somebody",
|
||||
Id = 1,
|
||||
};
|
||||
|
||||
public new DateTimeOffset Timestamp = DateTimeOffset.Now;
|
||||
|
||||
public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0)
|
||||
: base(messageCounter++)
|
||||
{
|
||||
|
@ -9,6 +9,8 @@ using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@ -22,31 +24,48 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneAccuracyCircle : OsuTestScene
|
||||
{
|
||||
[TestCase(0)]
|
||||
[TestCase(0.2)]
|
||||
[TestCase(0.5)]
|
||||
[TestCase(0.6999)]
|
||||
[TestCase(0.7)]
|
||||
[TestCase(0.75)]
|
||||
[TestCase(0.7999)]
|
||||
[TestCase(0.8)]
|
||||
[TestCase(0.85)]
|
||||
[TestCase(0.8999)]
|
||||
[TestCase(0.9)]
|
||||
[TestCase(0.925)]
|
||||
[TestCase(0.9499)]
|
||||
[TestCase(0.95)]
|
||||
[TestCase(0.975)]
|
||||
[TestCase(0.9999)]
|
||||
[TestCase(1)]
|
||||
public void TestRank(double accuracy)
|
||||
[Test]
|
||||
public void TestOsuRank()
|
||||
{
|
||||
var score = createScore(accuracy, ScoreProcessor.RankFromAccuracy(accuracy));
|
||||
|
||||
addCircleStep(score);
|
||||
addCircleStep(createScore(0, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.5, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.699, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.7, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.75, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.799, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.8, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.85, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.899, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.9, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.925, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.9499, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.95, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.975, new OsuRuleset()));
|
||||
addCircleStep(createScore(0.99, new OsuRuleset()));
|
||||
addCircleStep(createScore(1, new OsuRuleset()));
|
||||
}
|
||||
|
||||
private void addCircleStep(ScoreInfo score) => AddStep("add panel", () =>
|
||||
[Test]
|
||||
public void TestCatchRank()
|
||||
{
|
||||
addCircleStep(createScore(0, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.5, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.8499, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.85, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.875, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.899, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.9, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.925, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.9399, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.94, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.9675, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.9799, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.98, new CatchRuleset()));
|
||||
addCircleStep(createScore(0.99, new CatchRuleset()));
|
||||
addCircleStep(createScore(1, new CatchRuleset()));
|
||||
}
|
||||
|
||||
private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy})", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -73,28 +92,33 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
};
|
||||
});
|
||||
|
||||
private ScoreInfo createScore(double accuracy, ScoreRank rank) => new ScoreInfo
|
||||
private ScoreInfo createScore(double accuracy, Ruleset ruleset)
|
||||
{
|
||||
User = new APIUser
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
|
||||
return new ScoreInfo
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
},
|
||||
BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
Accuracy = accuracy,
|
||||
MaxCombo = 999,
|
||||
Rank = rank,
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics =
|
||||
{
|
||||
{ HitResult.Miss, 1 },
|
||||
{ HitResult.Meh, 50 },
|
||||
{ HitResult.Good, 100 },
|
||||
{ HitResult.Great, 300 },
|
||||
}
|
||||
};
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
},
|
||||
BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||
Ruleset = ruleset.RulesetInfo,
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
Accuracy = accuracy,
|
||||
MaxCombo = 999,
|
||||
Rank = scoreProcessor.RankFromAccuracy(accuracy),
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics =
|
||||
{
|
||||
{ HitResult.Miss, 1 },
|
||||
{ HitResult.Meh, 50 },
|
||||
{ HitResult.Good, 100 },
|
||||
{ HitResult.Great, 300 },
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -29,20 +29,22 @@ namespace osu.Game.Beatmaps
|
||||
return new RomanisableString($"{metadata.GetPreferred(true)}".Trim(), $"{metadata.GetPreferred(false)}".Trim());
|
||||
}
|
||||
|
||||
public static List<string> GetSearchableTerms(this IBeatmapInfo beatmapInfo)
|
||||
public static bool Match(this IBeatmapInfo beatmapInfo, params FilterCriteria.OptionalTextFilter[] filters)
|
||||
{
|
||||
var termsList = new List<string>(BeatmapMetadataInfoExtensions.MAX_SEARCHABLE_TERM_COUNT + 1);
|
||||
|
||||
addIfNotNull(beatmapInfo.DifficultyName);
|
||||
|
||||
BeatmapMetadataInfoExtensions.CollectSearchableTerms(beatmapInfo.Metadata, termsList);
|
||||
return termsList;
|
||||
|
||||
void addIfNotNull(string? s)
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
termsList.Add(s);
|
||||
if (filter.Matches(beatmapInfo.DifficultyName))
|
||||
continue;
|
||||
|
||||
if (BeatmapMetadataInfoExtensions.Match(beatmapInfo.Metadata, filter))
|
||||
continue;
|
||||
|
||||
// failed to match a single filter at all - fail the whole match.
|
||||
return false;
|
||||
}
|
||||
|
||||
// got through all filters without failing any - pass the whole match.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
|
||||
|
@ -3,11 +3,14 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public static class BeatmapMetadataInfoExtensions
|
||||
{
|
||||
internal const int MAX_SEARCHABLE_TERM_COUNT = 7;
|
||||
|
||||
/// <summary>
|
||||
/// An array of all searchable terms provided in contained metadata.
|
||||
/// </summary>
|
||||
@ -18,7 +21,18 @@ namespace osu.Game.Beatmaps
|
||||
return termsList.ToArray();
|
||||
}
|
||||
|
||||
internal const int MAX_SEARCHABLE_TERM_COUNT = 7;
|
||||
public static bool Match(IBeatmapMetadataInfo metadataInfo, FilterCriteria.OptionalTextFilter filter)
|
||||
{
|
||||
if (filter.Matches(metadataInfo.Author.Username)) return true;
|
||||
if (filter.Matches(metadataInfo.Artist)) return true;
|
||||
if (filter.Matches(metadataInfo.ArtistUnicode)) return true;
|
||||
if (filter.Matches(metadataInfo.Title)) return true;
|
||||
if (filter.Matches(metadataInfo.TitleUnicode)) return true;
|
||||
if (filter.Matches(metadataInfo.Source)) return true;
|
||||
if (filter.Matches(metadataInfo.Tags)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void CollectSearchableTerms(IBeatmapMetadataInfo metadataInfo, IList<string> termsList)
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Users;
|
||||
@ -47,9 +48,16 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
string displayText = text.Substring(link.Index, link.Length);
|
||||
|
||||
if (previousLinkEnd > link.Index)
|
||||
{
|
||||
Logger.Log($@"Link ""{link.Url}"" with text ""{displayText}"" overlaps previous link, ignoring.");
|
||||
continue;
|
||||
}
|
||||
|
||||
AddText(text[previousLinkEnd..link.Index]);
|
||||
|
||||
string displayText = text.Substring(link.Index, link.Length);
|
||||
object linkArgument = link.Argument;
|
||||
string tooltip = displayText == link.Url ? null : link.Url;
|
||||
|
||||
|
@ -179,21 +179,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ClickToSelectTrack => new TranslatableString(getKey(@"click_to_select_track"), @"Click to select a track");
|
||||
|
||||
/// <summary>
|
||||
/// "Click to replace the track"
|
||||
/// </summary>
|
||||
public static LocalisableString ClickToReplaceTrack => new TranslatableString(getKey(@"click_to_replace_track"), @"Click to replace the track");
|
||||
|
||||
/// <summary>
|
||||
/// "Click to select a background image"
|
||||
/// </summary>
|
||||
public static LocalisableString ClickToSelectBackground => new TranslatableString(getKey(@"click_to_select_background"), @"Click to select a background image");
|
||||
|
||||
/// <summary>
|
||||
/// "Click to replace the background image"
|
||||
/// </summary>
|
||||
public static LocalisableString ClickToReplaceBackground => new TranslatableString(getKey(@"click_to_replace_background"), @"Click to replace the background image");
|
||||
|
||||
/// <summary>
|
||||
/// "Ruleset ({0})"
|
||||
/// </summary>
|
||||
|
@ -85,8 +85,8 @@ namespace osu.Game.Online.Chat
|
||||
if (escapeChars != null)
|
||||
displayText = escapeChars.Aggregate(displayText, (current, c) => current.Replace($"\\{c}", c.ToString()));
|
||||
|
||||
// Check for encapsulated links
|
||||
if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || (index <= l.Index && index + m.Length >= l.Index + l.Length)) == null)
|
||||
// Check for overlapping links
|
||||
if (!result.Links.Exists(l => l.Overlaps(index, m.Length)))
|
||||
{
|
||||
result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText);
|
||||
|
||||
@ -364,7 +364,9 @@ namespace osu.Game.Online.Chat
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
public bool Overlaps(Link otherLink) => Index < otherLink.Index + otherLink.Length && otherLink.Index < Index + Length;
|
||||
public bool Overlaps(Link otherLink) => Overlaps(otherLink.Index, otherLink.Length);
|
||||
|
||||
public bool Overlaps(int otherIndex, int otherLength) => Index < otherIndex + otherLength && otherIndex < Index + Length;
|
||||
|
||||
public int CompareTo(Link? otherLink) => Index > otherLink?.Index ? 1 : -1;
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
||||
if (Score.Mods.Length > 0 && songSelect != null)
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||
|
||||
if (Score.Files.Count > 0)
|
||||
|
11
osu.Game/Online/Multiplayer/GameplayAbortReason.cs
Normal file
11
osu.Game/Online/Multiplayer/GameplayAbortReason.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public enum GameplayAbortReason
|
||||
{
|
||||
LoadTookTooLong,
|
||||
HostAbortedTheMatch
|
||||
}
|
||||
}
|
@ -107,17 +107,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
Task LoadRequested();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that loading of gameplay is to be aborted.
|
||||
/// </summary>
|
||||
Task LoadAborted();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that gameplay has started.
|
||||
/// All users in the <see cref="MultiplayerUserState.Loaded"/> or <see cref="MultiplayerUserState.ReadyForGameplay"/> states should begin gameplay as soon as possible.
|
||||
/// </summary>
|
||||
Task GameplayStarted();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that gameplay has been aborted.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason why gameplay was aborted.</param>
|
||||
Task GameplayAborted(GameplayAbortReason reason);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the match has ended, all players have finished and results are ready to be displayed.
|
||||
/// </summary>
|
||||
|
@ -77,6 +77,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <exception cref="InvalidStateException">If an attempt to start the game occurs when the game's (or users') state disallows it.</exception>
|
||||
Task StartMatch();
|
||||
|
||||
/// <summary>
|
||||
/// As the host of a room, aborts an on-going match.
|
||||
/// </summary>
|
||||
Task AbortMatch();
|
||||
|
||||
/// <summary>
|
||||
/// Aborts an ongoing gameplay load.
|
||||
/// </summary>
|
||||
|
@ -73,9 +73,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
public virtual event Action? LoadRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the multiplayer server requests loading of play to be aborted.
|
||||
/// Invoked when the multiplayer server requests gameplay to be aborted.
|
||||
/// </summary>
|
||||
public event Action? LoadAborted;
|
||||
public event Action<GameplayAbortReason>? GameplayAborted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the multiplayer server requests gameplay to be started.
|
||||
@ -374,6 +374,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task AbortGameplay();
|
||||
|
||||
public abstract Task AbortMatch();
|
||||
|
||||
public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item);
|
||||
@ -682,14 +684,14 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.LoadAborted()
|
||||
Task IMultiplayerClient.GameplayAborted(GameplayAbortReason reason)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
LoadAborted?.Invoke();
|
||||
GameplayAborted?.Invoke(reason);
|
||||
}, false);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
|
||||
connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested);
|
||||
connection.On(nameof(IMultiplayerClient.GameplayStarted), ((IMultiplayerClient)this).GameplayStarted);
|
||||
connection.On(nameof(IMultiplayerClient.LoadAborted), ((IMultiplayerClient)this).LoadAborted);
|
||||
connection.On<GameplayAbortReason>(nameof(IMultiplayerClient.GameplayAborted), ((IMultiplayerClient)this).GameplayAborted);
|
||||
connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady);
|
||||
connection.On<int, IEnumerable<APIMod>>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged);
|
||||
connection.On<int, BeatmapAvailability>(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged);
|
||||
@ -226,6 +226,16 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay));
|
||||
}
|
||||
|
||||
public override Task AbortMatch()
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
Debug.Assert(connection != null);
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.AbortMatch));
|
||||
}
|
||||
|
||||
public override Task AddPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
|
@ -183,9 +183,7 @@ namespace osu.Game.Overlays
|
||||
// new results may contain beatmaps from a previous page,
|
||||
// this is dodgy but matches web behaviour for now.
|
||||
// see: https://github.com/ppy/osu-web/issues/9270
|
||||
// todo: replace custom equality compraer with ExceptBy in net6.0
|
||||
// newCards = newCards.ExceptBy(foundContent.Select(c => c.BeatmapSet.OnlineID), c => c.BeatmapSet.OnlineID);
|
||||
newCards = newCards.Except(foundContent, BeatmapCardEqualityComparer.Default);
|
||||
newCards = newCards.ExceptBy(foundContent.Select(c => c.BeatmapSet.OnlineID), c => c.BeatmapSet.OnlineID);
|
||||
|
||||
panelLoadTask = LoadComponentsAsync(newCards, loaded =>
|
||||
{
|
||||
@ -378,21 +376,5 @@ namespace osu.Game.Overlays
|
||||
if (shouldShowMore)
|
||||
filterControl.FetchNextPage();
|
||||
}
|
||||
|
||||
private class BeatmapCardEqualityComparer : IEqualityComparer<BeatmapCard>
|
||||
{
|
||||
public static BeatmapCardEqualityComparer Default { get; } = new BeatmapCardEqualityComparer();
|
||||
|
||||
public bool Equals(BeatmapCard x, BeatmapCard y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return true;
|
||||
if (ReferenceEquals(x, null)) return false;
|
||||
if (ReferenceEquals(y, null)) return false;
|
||||
|
||||
return x.BeatmapSet.Equals(y.BeatmapSet);
|
||||
}
|
||||
|
||||
public int GetHashCode(BeatmapCard obj) => obj.BeatmapSet.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = HexaconsIcons.Social,
|
||||
Icon = HexaconsIcons.Messaging,
|
||||
Size = new Vector2(24),
|
||||
},
|
||||
// Placeholder text
|
||||
|
@ -23,6 +23,11 @@ namespace osu.Game.Overlays.Dialog
|
||||
/// </summary>
|
||||
protected Action? DangerousAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The action to perform if cancelled.
|
||||
/// </summary>
|
||||
protected Action? CancelAction { get; set; }
|
||||
|
||||
protected DangerousActionDialog()
|
||||
{
|
||||
HeaderText = DeleteConfirmationDialogStrings.HeaderText;
|
||||
@ -38,7 +43,8 @@ namespace osu.Game.Overlays.Dialog
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = DeleteConfirmationDialogStrings.Cancel
|
||||
Text = DeleteConfirmationDialogStrings.Cancel,
|
||||
Action = () => CancelAction?.Invoke()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
@ -26,6 +25,7 @@ using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
@ -55,9 +55,6 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
[Resolved]
|
||||
private MusicController music { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
@ -139,6 +136,12 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
performer?.PerformFromScreen(screen =>
|
||||
{
|
||||
if (beatmap.Value is DummyWorkingBeatmap)
|
||||
{
|
||||
// presume we don't have anything good to play and just bail.
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're playing the intro, switch away to another beatmap.
|
||||
if (beatmap.Value.BeatmapSetInfo.Protected)
|
||||
{
|
||||
@ -150,7 +153,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
if (screen is Player)
|
||||
return;
|
||||
|
||||
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||
var replayGeneratingMod = beatmap.Value.BeatmapInfo.Ruleset.CreateInstance().GetAutoplayMod();
|
||||
|
||||
IReadOnlyList<Mod> usableMods = mods.Value;
|
||||
|
||||
@ -285,6 +288,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
private partial class EndlessPlayer : ReplayPlayer
|
||||
{
|
||||
protected override UserActivity? InitialActivity => null;
|
||||
|
||||
public EndlessPlayer(Func<IBeatmap, IReadOnlyList<Mod>, Score> createScore)
|
||||
: base(createScore, new PlayerConfiguration
|
||||
{
|
||||
@ -294,10 +299,21 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
Scheduler.AddDelayed(this.Exit, 3000);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
if (GameplayState.HasPassed)
|
||||
GameplayClockContainer.Seek(0);
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <summary>
|
||||
/// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>.
|
||||
/// </summary>
|
||||
public static ScoreRank RankFromAccuracy(double accuracy)
|
||||
public virtual ScoreRank RankFromAccuracy(double accuracy)
|
||||
{
|
||||
if (accuracy == accuracy_cutoff_x)
|
||||
return ScoreRank.X;
|
||||
@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Given a <see cref="ScoreRank"/>, return the cutoff accuracy (0..1).
|
||||
/// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank.
|
||||
/// </summary>
|
||||
public static double AccuracyCutoffFromRank(ScoreRank rank)
|
||||
public virtual double AccuracyCutoffFromRank(ScoreRank rank)
|
||||
{
|
||||
switch (rank)
|
||||
{
|
||||
|
@ -342,23 +342,7 @@ namespace osu.Game.Scoring
|
||||
switch (r.result)
|
||||
{
|
||||
case HitResult.SmallTickHit:
|
||||
{
|
||||
int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
|
||||
if (total > 0)
|
||||
yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HitResult.LargeTickHit:
|
||||
{
|
||||
int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss);
|
||||
if (total > 0)
|
||||
yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
case HitResult.SmallBonus:
|
||||
if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0)
|
||||
|
@ -146,13 +146,8 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
private void updatePlaceholderText()
|
||||
{
|
||||
audioTrackChooser.Text = audioTrackChooser.Current.Value == null
|
||||
? EditorSetupStrings.ClickToSelectTrack
|
||||
: EditorSetupStrings.ClickToReplaceTrack;
|
||||
|
||||
backgroundChooser.Text = backgroundChooser.Current.Value == null
|
||||
? EditorSetupStrings.ClickToSelectBackground
|
||||
: EditorSetupStrings.ClickToReplaceBackground;
|
||||
audioTrackChooser.Text = audioTrackChooser.Current.Value?.Name ?? EditorSetupStrings.ClickToSelectTrack;
|
||||
backgroundChooser.Text = backgroundChooser.Current.Value?.Name ?? EditorSetupStrings.ClickToSelectBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.Countdown;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
@ -28,6 +30,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
[CanBeNull]
|
||||
private IDisposable clickOperation;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IDialogOverlay dialogOverlay { get; set; }
|
||||
|
||||
private Sample sampleReady;
|
||||
private Sample sampleReadyAll;
|
||||
private Sample sampleUnready;
|
||||
@ -56,7 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One,
|
||||
Action = onReadyClick,
|
||||
Action = onReadyButtonClick,
|
||||
},
|
||||
countdownButton = new MultiplayerCountdownButton
|
||||
{
|
||||
@ -101,7 +106,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
endOperation();
|
||||
}
|
||||
|
||||
private void onReadyClick()
|
||||
private void onReadyButtonClick()
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
@ -109,9 +114,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
Debug.Assert(clickOperation == null);
|
||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||
|
||||
if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||
startMatch();
|
||||
else
|
||||
if (Client.IsHost)
|
||||
{
|
||||
if (Room.State == MultiplayerRoomState.Open)
|
||||
{
|
||||
if (isReady() && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||
startMatch();
|
||||
else
|
||||
toggleReady();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dialogOverlay == null)
|
||||
abortMatch();
|
||||
else
|
||||
dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation));
|
||||
}
|
||||
}
|
||||
else if (Room.State != MultiplayerRoomState.Closed)
|
||||
toggleReady();
|
||||
|
||||
bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating;
|
||||
@ -128,6 +148,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
// gameplay was not started due to an exception; unblock button.
|
||||
endOperation();
|
||||
});
|
||||
|
||||
void abortMatch() => Client.AbortMatch().FireAndForget(endOperation, _ => endOperation());
|
||||
}
|
||||
|
||||
private void startCountdown(TimeSpan duration)
|
||||
@ -189,7 +211,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
|
||||
readyButton.Enabled.Value = countdownButton.Enabled.Value =
|
||||
Room.State == MultiplayerRoomState.Open
|
||||
Room.State != MultiplayerRoomState.Closed
|
||||
&& CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
|
||||
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
|
||||
&& !operationInProgress.Value;
|
||||
@ -198,6 +220,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||
readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown);
|
||||
|
||||
// When the local user is not the host, the button should only be enabled when no match is in progress.
|
||||
if (!Client.IsHost)
|
||||
readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open;
|
||||
|
||||
// At all times, the countdown button should only be enabled when no match is in progress.
|
||||
countdownButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open;
|
||||
|
||||
if (newCountReady == countReady)
|
||||
return;
|
||||
|
||||
@ -219,5 +248,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
countReady = newCountReady;
|
||||
});
|
||||
}
|
||||
|
||||
public partial class ConfirmAbortDialog : DangerousActionDialog
|
||||
{
|
||||
public ConfirmAbortDialog(Action abortMatch, Action cancel)
|
||||
{
|
||||
HeaderText = "Are you sure you want to abort the match?";
|
||||
|
||||
DangerousAction = abortMatch;
|
||||
CancelAction = cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,16 +149,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
switch (localUser?.State)
|
||||
{
|
||||
default:
|
||||
Text = "Ready";
|
||||
break;
|
||||
|
||||
case MultiplayerUserState.Spectating:
|
||||
case MultiplayerUserState.Ready:
|
||||
Text = room.Host?.Equals(localUser) == true
|
||||
Text = multiplayerClient.IsHost
|
||||
? $"Start match {countText}"
|
||||
: $"Waiting for host... {countText}";
|
||||
break;
|
||||
|
||||
default:
|
||||
// Show the abort button for the host as long as gameplay is in progress.
|
||||
if (multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open)
|
||||
Text = "Abort the match";
|
||||
else
|
||||
Text = "Ready";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -193,12 +196,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
switch (localUser?.State)
|
||||
{
|
||||
default:
|
||||
setGreen();
|
||||
// Show the abort button for the host as long as gameplay is in progress.
|
||||
if (multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open)
|
||||
setRed();
|
||||
else
|
||||
setGreen();
|
||||
break;
|
||||
|
||||
case MultiplayerUserState.Spectating:
|
||||
case MultiplayerUserState.Ready:
|
||||
if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||
if (multiplayerClient.IsHost && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
|
||||
setGreen();
|
||||
else
|
||||
setYellow();
|
||||
@ -206,15 +213,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
break;
|
||||
}
|
||||
|
||||
void setYellow()
|
||||
{
|
||||
BackgroundColour = colours.YellowDark;
|
||||
}
|
||||
void setYellow() => BackgroundColour = colours.YellowDark;
|
||||
|
||||
void setGreen()
|
||||
{
|
||||
BackgroundColour = colours.Green;
|
||||
}
|
||||
void setGreen() => BackgroundColour = colours.Green;
|
||||
|
||||
void setRed() => BackgroundColour = colours.Red;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
base.LoadComplete();
|
||||
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
client.LoadAborted += onLoadAborted;
|
||||
client.GameplayAborted += onGameplayAborted;
|
||||
onRoomUpdated();
|
||||
}
|
||||
|
||||
@ -39,12 +39,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
transitionFromResults();
|
||||
}
|
||||
|
||||
private void onLoadAborted()
|
||||
private void onGameplayAborted(GameplayAbortReason reason)
|
||||
{
|
||||
// If the server aborts gameplay for this user (due to loading too slow), exit gameplay screens.
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important);
|
||||
switch (reason)
|
||||
{
|
||||
case GameplayAbortReason.LoadTookTooLong:
|
||||
Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important);
|
||||
break;
|
||||
|
||||
case GameplayAbortReason.HostAbortedTheMatch:
|
||||
Logger.Log("The host aborted the match.", LoggingTarget.Runtime, LogLevel.Important);
|
||||
break;
|
||||
}
|
||||
|
||||
this.MakeCurrent();
|
||||
}
|
||||
}
|
||||
|
@ -29,13 +29,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
/// </summary>
|
||||
public partial class AccuracyCircle : CompositeDrawable
|
||||
{
|
||||
private static readonly double accuracy_x = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.X);
|
||||
private static readonly double accuracy_s = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.S);
|
||||
private static readonly double accuracy_a = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.A);
|
||||
private static readonly double accuracy_b = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.B);
|
||||
private static readonly double accuracy_c = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.C);
|
||||
private static readonly double accuracy_d = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.D);
|
||||
|
||||
/// <summary>
|
||||
/// Duration for the transforms causing this component to appear.
|
||||
/// </summary>
|
||||
@ -110,12 +103,26 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
private double lastTickPlaybackTime;
|
||||
private bool isTicking;
|
||||
|
||||
private readonly double accuracyX;
|
||||
private readonly double accuracyS;
|
||||
private readonly double accuracyA;
|
||||
private readonly double accuracyB;
|
||||
private readonly double accuracyC;
|
||||
private readonly double accuracyD;
|
||||
private readonly bool withFlair;
|
||||
|
||||
public AccuracyCircle(ScoreInfo score, bool withFlair = false)
|
||||
{
|
||||
this.score = score;
|
||||
this.withFlair = withFlair;
|
||||
|
||||
ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor();
|
||||
accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X);
|
||||
accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S);
|
||||
accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A);
|
||||
accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B);
|
||||
accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C);
|
||||
accuracyD = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.D);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -158,49 +165,49 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.X),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracy_x }
|
||||
Current = { Value = accuracyX }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.S),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracy_x - virtual_ss_percentage }
|
||||
Current = { Value = accuracyX - virtual_ss_percentage }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.A),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracy_s }
|
||||
Current = { Value = accuracyS }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.B),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracy_a }
|
||||
Current = { Value = accuracyA }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.C),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracy_b }
|
||||
Current = { Value = accuracyB }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.D),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracy_c }
|
||||
Current = { Value = accuracyC }
|
||||
},
|
||||
new RankNotch((float)accuracy_x),
|
||||
new RankNotch((float)(accuracy_x - virtual_ss_percentage)),
|
||||
new RankNotch((float)accuracy_s),
|
||||
new RankNotch((float)accuracy_a),
|
||||
new RankNotch((float)accuracy_b),
|
||||
new RankNotch((float)accuracy_c),
|
||||
new RankNotch((float)accuracyX),
|
||||
new RankNotch((float)(accuracyX - virtual_ss_percentage)),
|
||||
new RankNotch((float)accuracyS),
|
||||
new RankNotch((float)accuracyA),
|
||||
new RankNotch((float)accuracyB),
|
||||
new RankNotch((float)accuracyC),
|
||||
new BufferedContainer
|
||||
{
|
||||
Name = "Graded circle mask",
|
||||
@ -228,13 +235,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
|
||||
Children = new[]
|
||||
{
|
||||
new RankBadge(accuracyD, Interpolation.Lerp(accuracyD, accuracyC, 0.5), getRank(ScoreRank.D)),
|
||||
new RankBadge(accuracyC, Interpolation.Lerp(accuracyC, accuracyB, 0.5), getRank(ScoreRank.C)),
|
||||
new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)),
|
||||
// The S and A badges are moved down slightly to prevent collision with the SS badge.
|
||||
new RankBadge(accuracy_x, accuracy_x, getRank(ScoreRank.X)),
|
||||
new RankBadge(accuracy_s, Interpolation.Lerp(accuracy_s, (accuracy_x - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)),
|
||||
new RankBadge(accuracy_a, Interpolation.Lerp(accuracy_a, accuracy_s, 0.25), getRank(ScoreRank.A)),
|
||||
new RankBadge(accuracy_b, Interpolation.Lerp(accuracy_b, accuracy_a, 0.5), getRank(ScoreRank.B)),
|
||||
new RankBadge(accuracy_c, Interpolation.Lerp(accuracy_c, accuracy_b, 0.5), getRank(ScoreRank.C)),
|
||||
new RankBadge(accuracy_d, Interpolation.Lerp(accuracy_d, accuracy_c, 0.5), getRank(ScoreRank.D)),
|
||||
new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)),
|
||||
new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)),
|
||||
new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)),
|
||||
}
|
||||
},
|
||||
rankText = new RankText(score.Rank)
|
||||
@ -280,10 +287,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
double targetAccuracy = score.Accuracy;
|
||||
double[] notchPercentages =
|
||||
{
|
||||
accuracy_s,
|
||||
accuracy_a,
|
||||
accuracy_b,
|
||||
accuracy_c,
|
||||
accuracyS,
|
||||
accuracyA,
|
||||
accuracyB,
|
||||
accuracyC,
|
||||
};
|
||||
|
||||
// Ensure the gauge overshoots or undershoots a bit so it doesn't land in the gaps of the inner graded circle (caused by `RankNotch`es),
|
||||
@ -302,7 +309,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
||||
targetAccuracy = 1;
|
||||
else
|
||||
targetAccuracy = Math.Min(accuracy_x - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy);
|
||||
targetAccuracy = Math.Min(accuracyX - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy);
|
||||
|
||||
// The accuracy circle gauge visually fills up a bit too much.
|
||||
// This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases.
|
||||
@ -339,7 +346,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
if (badge.Accuracy > score.Accuracy)
|
||||
continue;
|
||||
|
||||
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracy_x - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||
{
|
||||
badge.Appear();
|
||||
|
||||
|
@ -41,6 +41,21 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
return match;
|
||||
}
|
||||
|
||||
if (criteria.SearchTerms.Length > 0)
|
||||
{
|
||||
match = BeatmapInfo.Match(criteria.SearchTerms);
|
||||
|
||||
// if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs.
|
||||
// this should be done after text matching so we can prioritise matching numbers in metadata.
|
||||
if (!match && criteria.SearchNumber.HasValue)
|
||||
{
|
||||
match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) ||
|
||||
(BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating);
|
||||
match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.Difficulty.ApproachRate);
|
||||
match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.Difficulty.DrainRate);
|
||||
@ -64,40 +79,6 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
if (criteria.SearchTerms.Length > 0)
|
||||
{
|
||||
var searchableTerms = BeatmapInfo.GetSearchableTerms();
|
||||
|
||||
foreach (FilterCriteria.OptionalTextFilter criteriaTerm in criteria.SearchTerms)
|
||||
{
|
||||
bool any = false;
|
||||
|
||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
||||
foreach (string searchTerm in searchableTerms)
|
||||
{
|
||||
if (!criteriaTerm.Matches(searchTerm)) continue;
|
||||
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (any) continue;
|
||||
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs.
|
||||
// this should be done after text matching so we can prioritise matching numbers in metadata.
|
||||
if (!match && criteria.SearchNumber.HasValue)
|
||||
{
|
||||
match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) ||
|
||||
(BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true;
|
||||
if (match && criteria.RulesetCriteria != null)
|
||||
match &= criteria.RulesetCriteria.Matches(BeatmapInfo);
|
||||
|
@ -86,16 +86,20 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
items.ForEach(c => c.Filter(criteria));
|
||||
|
||||
criteriaComparer = Comparer<CarouselItem>.Create((x, y) =>
|
||||
// Sorting is expensive, so only perform if it's actually changed.
|
||||
if (lastCriteria?.Sort != criteria.Sort)
|
||||
{
|
||||
int comparison = x.CompareTo(criteria, y);
|
||||
if (comparison != 0)
|
||||
return comparison;
|
||||
criteriaComparer = Comparer<CarouselItem>.Create((x, y) =>
|
||||
{
|
||||
int comparison = x.CompareTo(criteria, y);
|
||||
if (comparison != 0)
|
||||
return comparison;
|
||||
|
||||
return x.ItemID.CompareTo(y.ItemID);
|
||||
});
|
||||
return x.ItemID.CompareTo(y.ItemID);
|
||||
});
|
||||
|
||||
items.Sort(criteriaComparer);
|
||||
items.Sort(criteriaComparer);
|
||||
}
|
||||
|
||||
lastCriteria = criteria;
|
||||
}
|
||||
|
@ -176,13 +176,15 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
default:
|
||||
case MatchMode.Substring:
|
||||
return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase);
|
||||
// Note that we are using ordinal here to avoid performance issues caused by globalisation concerns.
|
||||
// See https://github.com/ppy/osu/issues/11571 / https://github.com/dotnet/docs/issues/18423.
|
||||
return value.Contains(SearchTerm, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
case MatchMode.IsolatedPhrase:
|
||||
return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
case MatchMode.FullPhrase:
|
||||
return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0;
|
||||
return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,6 +396,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task AbortMatch()
|
||||
{
|
||||
ChangeUserState(api.LocalUser.Value.Id, MultiplayerUserState.Idle);
|
||||
await ((IMultiplayerClient)this).GameplayAborted(GameplayAbortReason.HostAbortedTheMatch).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item)
|
||||
{
|
||||
Debug.Assert(ServerRoom != null);
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.1127.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.1201.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1127.0" />
|
||||
<PackageReference Include="Sentry" Version="3.40.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -23,6 +23,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.1127.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.1201.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
x
Reference in New Issue
Block a user