mirror of
https://github.com/ppy/osu.git
synced 2025-01-06 08:22:56 +08:00
Merge branch 'master' into key-reversion-conflict
This commit is contained in:
commit
7e3bb763cd
@ -5,7 +5,6 @@ using Android.Content.PM;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Android
|
||||
@ -28,7 +27,7 @@ namespace osu.Android
|
||||
{
|
||||
gameActivity.RunOnUiThread(() =>
|
||||
{
|
||||
gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingState.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
||||
gameActivity.RequestedOrientation = userPlaying.NewValue == LocalUserPlayingState.Playing ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public partial class CatchEditorPlayfield : CatchPlayfield
|
||||
{
|
||||
// TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen.
|
||||
public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty)
|
||||
: base(difficulty)
|
||||
{
|
||||
|
@ -2,16 +2,22 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public partial class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
||||
{
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
|
||||
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
|
||||
|
||||
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||
@ -28,6 +34,30 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
editorBeatmap.BeatmapReprocessed += onBeatmapReprocessed;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (editorBeatmap.IsNotNull())
|
||||
editorBeatmap.BeatmapReprocessed -= onBeatmapReprocessed;
|
||||
}
|
||||
|
||||
private void onBeatmapReprocessed()
|
||||
{
|
||||
if (Playfield is CatchEditorPlayfield catchPlayfield)
|
||||
{
|
||||
catchPlayfield.Catcher.ApplyDifficulty(editorBeatmap.Difficulty);
|
||||
catchPlayfield.CatcherArea.CatcherTrails.UpdateCatcherTrailsScale(catchPlayfield.Catcher.BodyScale);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <summary>
|
||||
/// Width of the area that can be used to attempt catches during gameplay.
|
||||
/// </summary>
|
||||
public readonly float CatchWidth;
|
||||
public float CatchWidth { get; private set; }
|
||||
|
||||
private readonly SkinnableCatcher body;
|
||||
|
||||
@ -142,10 +142,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
Size = new Vector2(BASE_SIZE);
|
||||
|
||||
if (difficulty != null)
|
||||
Scale = calculateScale(difficulty);
|
||||
|
||||
CatchWidth = CalculateCatchWidth(Scale);
|
||||
ApplyDifficulty(difficulty);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -312,6 +309,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the scale and catch width.
|
||||
/// </summary>
|
||||
public void ApplyDifficulty(IBeatmapDifficultyInfo? difficulty)
|
||||
{
|
||||
if (difficulty != null)
|
||||
Scale = calculateScale(difficulty);
|
||||
|
||||
CatchWidth = CalculateCatchWidth(Scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop any fruit off the plate.
|
||||
/// </summary>
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
private readonly CatchComboDisplay comboDisplay;
|
||||
|
||||
private readonly CatcherTrailDisplay catcherTrails;
|
||||
public readonly CatcherTrailDisplay CatcherTrails;
|
||||
|
||||
private Catcher catcher = null!;
|
||||
|
||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Children = new Drawable[]
|
||||
{
|
||||
catcherContainer = new Container<Catcher> { RelativeSizeAxes = Axes.Both },
|
||||
catcherTrails = new CatcherTrailDisplay(),
|
||||
CatcherTrails = new CatcherTrailDisplay(),
|
||||
comboDisplay = new CatchComboDisplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.None,
|
||||
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
const double trail_generation_interval = 16;
|
||||
|
||||
if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval)
|
||||
if (Time.Current - CatcherTrails.LastDashTrailTime >= trail_generation_interval)
|
||||
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
||||
}
|
||||
|
||||
@ -170,6 +170,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void displayCatcherTrail(CatcherTrailAnimation animation) => catcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation));
|
||||
private void displayCatcherTrail(CatcherTrailAnimation animation) => CatcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation));
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
@ -55,6 +57,25 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the scale of all trails.
|
||||
/// </summary>
|
||||
/// <param name="scale">The new body scale of the Catcher</param>
|
||||
public void UpdateCatcherTrailsScale(Vector2 scale)
|
||||
{
|
||||
var oldEntries = Entries.ToList();
|
||||
|
||||
Clear();
|
||||
|
||||
foreach (var oldEntry in oldEntries)
|
||||
{
|
||||
// use magnitude of the new scale while preserving the sign of the old one in the X direction.
|
||||
// the end effect is preserving the direction in which the trail sprites face, which is important.
|
||||
var targetScale = new Vector2(Math.Abs(scale.X) * Math.Sign(oldEntry.Scale.X), Math.Abs(scale.Y));
|
||||
Add(new CatcherTrailEntry(oldEntry.LifetimeStart, oldEntry.CatcherState, oldEntry.Position, targetScale, oldEntry.Animation));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
}
|
||||
|
||||
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
|
||||
effectiveMissCount = Math.Min(totalHits, effectiveMissCount);
|
||||
|
||||
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||
|
||||
|
@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Width = 220,
|
||||
|
@ -241,8 +241,8 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
||||
|
||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
|
||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
|
||||
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -273,34 +273,6 @@ namespace osu.Game.Tests.Beatmaps
|
||||
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndIncorrectHash([Values] bool preferOnlineFetch)
|
||||
{
|
||||
var lookupResult = new OnlineBeatmapMetadata
|
||||
{
|
||||
BeatmapID = 654321,
|
||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
||||
MD5Hash = @"cafebabe",
|
||||
};
|
||||
|
||||
var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
|
||||
targetMock.Setup(src => src.Available).Returns(true);
|
||||
targetMock.Setup(src => src.TryLookup(It.IsAny<BeatmapInfo>(), out lookupResult))
|
||||
.Returns(true);
|
||||
|
||||
var beatmap = new BeatmapInfo
|
||||
{
|
||||
MD5Hash = @"deadbeef"
|
||||
};
|
||||
var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
|
||||
beatmap.BeatmapSet = beatmapSet;
|
||||
|
||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
||||
|
||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch)
|
||||
{
|
||||
@ -383,58 +355,5 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPartiallyMaliciousSet([Values] bool preferOnlineFetch)
|
||||
{
|
||||
var firstResult = new OnlineBeatmapMetadata
|
||||
{
|
||||
BeatmapID = 654321,
|
||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
||||
BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
|
||||
MD5Hash = @"cafebabe"
|
||||
};
|
||||
var secondResult = new OnlineBeatmapMetadata
|
||||
{
|
||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
||||
BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
|
||||
MD5Hash = @"dededede"
|
||||
};
|
||||
|
||||
var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
|
||||
targetMock.Setup(src => src.Available).Returns(true);
|
||||
targetMock.Setup(src => src.TryLookup(It.Is<BeatmapInfo>(bi => bi.OnlineID == 654321), out firstResult))
|
||||
.Returns(true);
|
||||
targetMock.Setup(src => src.TryLookup(It.Is<BeatmapInfo>(bi => bi.OnlineID == 666666), out secondResult))
|
||||
.Returns(true);
|
||||
|
||||
var firstBeatmap = new BeatmapInfo
|
||||
{
|
||||
OnlineID = 654321,
|
||||
MD5Hash = @"cafebabe",
|
||||
};
|
||||
var secondBeatmap = new BeatmapInfo
|
||||
{
|
||||
OnlineID = 666666,
|
||||
MD5Hash = @"deadbeef"
|
||||
};
|
||||
var beatmapSet = new BeatmapSetInfo(new[]
|
||||
{
|
||||
firstBeatmap,
|
||||
secondBeatmap
|
||||
});
|
||||
firstBeatmap.BeatmapSet = beatmapSet;
|
||||
secondBeatmap.BeatmapSet = beatmapSet;
|
||||
|
||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
||||
|
||||
Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
|
||||
Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321));
|
||||
|
||||
Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||
Assert.That(secondBeatmap.OnlineID, Is.EqualTo(-1));
|
||||
|
||||
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,28 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
@ -30,6 +44,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
SelectedMods.SetDefault();
|
||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||
Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo =
|
||||
@ -93,6 +109,126 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("check new title", getText, () => Is.EqualTo("Title: Another"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWithMods()
|
||||
{
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
BPM = 100,
|
||||
Length = 30000,
|
||||
Difficulty =
|
||||
{
|
||||
ApproachRate = 10,
|
||||
CircleSize = 9
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100.00", "BPM: 150.00");
|
||||
test(BeatmapAttribute.Length, new OsuModDoubleTime(), "Length: 00:30", "Length: 00:20");
|
||||
test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10.00", "Approach Rate: 11.00");
|
||||
test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.00", "Circle Size: 10.00");
|
||||
|
||||
void test(BeatmapAttribute attribute, Mod mod, string before, string after)
|
||||
{
|
||||
AddStep($"set attribute: {attribute}", () => text.Attribute.Value = attribute);
|
||||
AddAssert("check text is correct", getText, () => Is.EqualTo(before));
|
||||
|
||||
AddStep("add DT mod", () => SelectedMods.Value = new[] { mod });
|
||||
AddAssert("check text is correct", getText, () => Is.EqualTo(after));
|
||||
AddStep("clear mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStarRating()
|
||||
{
|
||||
AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo);
|
||||
AddStep("set star rating attribute", () => text.Attribute.Value = BeatmapAttribute.StarRating);
|
||||
AddAssert("check star rating is 0", getText, () => Is.EqualTo("Star Rating: 0.00"));
|
||||
|
||||
// Adding mod
|
||||
TestMod mod = null!;
|
||||
AddStep("add mod with difficulty 1", () => SelectedMods.Value = new[] { mod = new TestMod { Difficulty = { Value = 1 } } });
|
||||
AddUntilStep("check star rating is 1", getText, () => Is.EqualTo("Star Rating: 1.00"));
|
||||
|
||||
// Changing mod setting
|
||||
AddStep("change mod difficulty to 2", () => mod.Difficulty.Value = 2);
|
||||
AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00"));
|
||||
}
|
||||
|
||||
private string getText() => text.ChildrenOfType<SpriteText>().Single().Text.ToString();
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => new[]
|
||||
{
|
||||
new TestMod()
|
||||
};
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap)
|
||||
=> new OsuRuleset().CreateBeatmapConverter(beatmap);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap)
|
||||
=> new TestDifficultyCalculator(new TestRuleset().RulesetInfo, beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator()
|
||||
=> new TestPerformanceCalculator(new TestRuleset());
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||
=> null!;
|
||||
|
||||
public override string Description => string.Empty;
|
||||
public override string ShortName => string.Empty;
|
||||
}
|
||||
|
||||
private class TestDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public TestDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
=> new DifficultyAttributes(mods, mods.OfType<TestMod>().SingleOrDefault()?.Difficulty.Value ?? 0);
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
=> Array.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
=> Array.Empty<Skill>();
|
||||
}
|
||||
|
||||
private class TestPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
public TestPerformanceCalculator(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
|
||||
=> new PerformanceAttributes { Total = score.Mods.OfType<TestMod>().SingleOrDefault()?.Performance.Value ?? 0 };
|
||||
}
|
||||
|
||||
private class TestMod : Mod
|
||||
{
|
||||
[SettingSource("difficulty")]
|
||||
public BindableDouble Difficulty { get; } = new BindableDouble(0);
|
||||
|
||||
[SettingSource("performance")]
|
||||
public BindableDouble Performance { get; } = new BindableDouble(0);
|
||||
|
||||
[JsonConstructor]
|
||||
public TestMod()
|
||||
{
|
||||
}
|
||||
|
||||
public override string Name => string.Empty;
|
||||
public override LocalisableString Description => string.Empty;
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override string Acronym => "Test";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||
|
||||
var req = new GetBeatmapRequest(beatmapInfo);
|
||||
var req = new GetBeatmapRequest(md5Hash: beatmapInfo.MD5Hash, filename: beatmapInfo.Path);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
@ -44,10 +43,19 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
foreach (var beatmapInfo in beatmapSet.Beatmaps)
|
||||
{
|
||||
// note that these lookups DO NOT ACTUALLY FULLY GUARANTEE that the beatmap is what it claims it is,
|
||||
// i.e. the correctness of this lookup should be treated as APPROXIMATE AT WORST.
|
||||
// this is because the beatmap filename is used as a fallback in some scenarios where the MD5 of the beatmap may mismatch.
|
||||
// this is considered to be an acceptable casualty so that things can continue to work as expected for users in some rare scenarios
|
||||
// (stale beatmap files in beatmap packs, beatmap mirror desyncs).
|
||||
// however, all this means that other places such as score submission ARE EXPECTED TO VERIFY THE MD5 OF THE BEATMAP AGAINST THE ONLINE ONE EXPLICITLY AGAIN.
|
||||
//
|
||||
// additionally note that the online ID stored to the map is EXPLICITLY NOT USED because some users in a silly attempt to "fix" things for themselves on stable
|
||||
// would reuse online IDs of already submitted beatmaps, which means that information is pretty much expected to be bogus in a nonzero number of beatmapsets.
|
||||
if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res))
|
||||
continue;
|
||||
|
||||
if (res == null || shouldDiscardLookupResult(res, beatmapInfo))
|
||||
if (res == null)
|
||||
{
|
||||
beatmapInfo.ResetOnlineInfo();
|
||||
lookupResults.Add(null); // mark lookup failure
|
||||
@ -83,23 +91,6 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo)
|
||||
{
|
||||
if (beatmapInfo.OnlineID > 0 && result.BeatmapID != beatmapInfo.OnlineID)
|
||||
{
|
||||
Logger.Log($"Discarding metadata lookup result due to mismatching online ID (expected: {beatmapInfo.OnlineID} actual: {result.BeatmapID})", LoggingTarget.Database);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash)
|
||||
{
|
||||
Logger.Log($"Discarding metadata lookup result due to mismatching hash (expected: {beatmapInfo.MD5Hash} actual: {result.MD5Hash})", LoggingTarget.Database);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the <see cref="OnlineBeatmapMetadata"/> for the given <paramref name="beatmapInfo"/>.
|
||||
/// </summary>
|
||||
|
@ -90,8 +90,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
|
||||
&& string.IsNullOrEmpty(beatmapInfo.Path)
|
||||
&& beatmapInfo.OnlineID <= 0)
|
||||
&& string.IsNullOrEmpty(beatmapInfo.Path))
|
||||
{
|
||||
onlineMetadata = null;
|
||||
return false;
|
||||
@ -240,10 +239,9 @@ namespace osu.Game.Beatmaps
|
||||
using var cmd = db.CreateCommand();
|
||||
|
||||
cmd.CommandText =
|
||||
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";
|
||||
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR filename = @Path";
|
||||
|
||||
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
||||
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
|
||||
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
||||
|
||||
using var reader = cmd.ExecuteReader();
|
||||
@ -281,11 +279,10 @@ namespace osu.Game.Beatmaps
|
||||
SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date`
|
||||
FROM `osu_beatmaps` AS `b`
|
||||
JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id`
|
||||
WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path
|
||||
WHERE `b`.`checksum` = @MD5Hash OR `b`.`filename` = @Path
|
||||
""";
|
||||
|
||||
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
||||
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
|
||||
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
||||
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
@ -528,7 +528,7 @@ namespace osu.Game.Database
|
||||
/// <param name="model">The new model proposed for import.</param>
|
||||
/// <param name="realm">The current realm context.</param>
|
||||
/// <returns>An existing model which matches the criteria to skip importing, else null.</returns>
|
||||
protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All<TModel>().FirstOrDefault(b => b.Hash == model.Hash);
|
||||
protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All<TModel>().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash);
|
||||
|
||||
/// <summary>
|
||||
/// Whether import can be skipped after finding an existing import early in the process.
|
||||
|
@ -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.Globalization;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -9,23 +10,30 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
||||
{
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
public readonly string Filename;
|
||||
public readonly int OnlineID;
|
||||
public readonly string? MD5Hash;
|
||||
public readonly string? Filename;
|
||||
|
||||
public GetBeatmapRequest(IBeatmapInfo beatmapInfo)
|
||||
: this(onlineId: beatmapInfo.OnlineID, md5Hash: beatmapInfo.MD5Hash, filename: (beatmapInfo as BeatmapInfo)?.Path)
|
||||
{
|
||||
BeatmapInfo = beatmapInfo;
|
||||
Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty;
|
||||
}
|
||||
|
||||
public GetBeatmapRequest(int onlineId = -1, string? md5Hash = null, string? filename = null)
|
||||
{
|
||||
OnlineID = onlineId;
|
||||
MD5Hash = md5Hash;
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var request = base.CreateWebRequest();
|
||||
|
||||
if (BeatmapInfo.OnlineID > 0)
|
||||
request.AddParameter(@"id", BeatmapInfo.OnlineID.ToString());
|
||||
if (!string.IsNullOrEmpty(BeatmapInfo.MD5Hash))
|
||||
request.AddParameter(@"checksum", BeatmapInfo.MD5Hash);
|
||||
if (OnlineID > 0)
|
||||
request.AddParameter(@"id", OnlineID.ToString(CultureInfo.InvariantCulture));
|
||||
if (!string.IsNullOrEmpty(MD5Hash))
|
||||
request.AddParameter(@"checksum", MD5Hash);
|
||||
if (!string.IsNullOrEmpty(Filename))
|
||||
request.AddParameter(@"filename", Filename);
|
||||
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets
|
||||
/// <param name="acronym">The acronym to query for .</param>
|
||||
public Mod? CreateModFromAcronym(string acronym)
|
||||
{
|
||||
return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance();
|
||||
return AllMods.FirstOrDefault(m => string.Equals(m.Acronym, acronym, StringComparison.OrdinalIgnoreCase))?.CreateInstance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -18,6 +20,9 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Localisation.SkinComponents;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Skinning.Components
|
||||
{
|
||||
@ -33,7 +38,20 @@ namespace osu.Game.Skinning.Components
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||
|
||||
private readonly OsuSpriteText text;
|
||||
private IBindable<StarDifficulty?>? difficultyBindable;
|
||||
private CancellationTokenSource? difficultyCancellationSource;
|
||||
private ModSettingChangeTracker? modSettingTracker;
|
||||
private StarDifficulty? starDifficulty;
|
||||
|
||||
public BeatmapAttributeText()
|
||||
{
|
||||
@ -55,7 +73,35 @@ namespace osu.Game.Skinning.Components
|
||||
|
||||
Attribute.BindValueChanged(_ => updateText());
|
||||
Template.BindValueChanged(_ => updateText());
|
||||
beatmap.BindValueChanged(_ => updateText());
|
||||
|
||||
beatmap.BindValueChanged(b =>
|
||||
{
|
||||
difficultyCancellationSource?.Cancel();
|
||||
difficultyCancellationSource = new CancellationTokenSource();
|
||||
|
||||
difficultyBindable?.UnbindAll();
|
||||
difficultyBindable = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, difficultyCancellationSource.Token);
|
||||
difficultyBindable.BindValueChanged(d =>
|
||||
{
|
||||
starDifficulty = d.NewValue;
|
||||
updateText();
|
||||
});
|
||||
|
||||
updateText();
|
||||
}, true);
|
||||
|
||||
mods.BindValueChanged(m =>
|
||||
{
|
||||
modSettingTracker?.Dispose();
|
||||
modSettingTracker = new ModSettingChangeTracker(m.NewValue)
|
||||
{
|
||||
SettingChanged = _ => updateText()
|
||||
};
|
||||
|
||||
updateText();
|
||||
}, true);
|
||||
|
||||
ruleset.BindValueChanged(_ => updateText());
|
||||
|
||||
updateText();
|
||||
}
|
||||
@ -156,37 +202,64 @@ namespace osu.Game.Skinning.Components
|
||||
return beatmap.Value.BeatmapInfo.Metadata.Source;
|
||||
|
||||
case BeatmapAttribute.Length:
|
||||
return TimeSpan.FromMilliseconds(beatmap.Value.BeatmapInfo.Length).ToFormattedDuration();
|
||||
return Math.Round(beatmap.Value.BeatmapInfo.Length / ModUtils.CalculateRateWithMods(mods.Value)).ToFormattedDuration();
|
||||
|
||||
case BeatmapAttribute.RankedStatus:
|
||||
return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription();
|
||||
|
||||
case BeatmapAttribute.BPM:
|
||||
return beatmap.Value.BeatmapInfo.BPM.ToLocalisableString(@"F2");
|
||||
return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"F2");
|
||||
|
||||
case BeatmapAttribute.CircleSize:
|
||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2");
|
||||
return computeDifficulty().CircleSize.ToLocalisableString(@"F2");
|
||||
|
||||
case BeatmapAttribute.HPDrain:
|
||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2");
|
||||
return computeDifficulty().DrainRate.ToLocalisableString(@"F2");
|
||||
|
||||
case BeatmapAttribute.Accuracy:
|
||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2");
|
||||
return computeDifficulty().OverallDifficulty.ToLocalisableString(@"F2");
|
||||
|
||||
case BeatmapAttribute.ApproachRate:
|
||||
return ((double)beatmap.Value.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2");
|
||||
return computeDifficulty().ApproachRate.ToLocalisableString(@"F2");
|
||||
|
||||
case BeatmapAttribute.StarRating:
|
||||
return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2");
|
||||
return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2");
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
BeatmapDifficulty computeDifficulty()
|
||||
{
|
||||
BeatmapDifficulty difficulty = new BeatmapDifficulty(beatmap.Value.BeatmapInfo.Difficulty);
|
||||
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(difficulty);
|
||||
|
||||
if (ruleset.Value is RulesetInfo rulesetInfo)
|
||||
{
|
||||
double rate = ModUtils.CalculateRateWithMods(mods.Value);
|
||||
difficulty = rulesetInfo.CreateInstance().GetRateAdjustedDisplayDifficulty(difficulty, rate);
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40);
|
||||
|
||||
protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour;
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
difficultyCancellationSource?.Cancel();
|
||||
difficultyCancellationSource?.Dispose();
|
||||
difficultyCancellationSource = null;
|
||||
|
||||
modSettingTracker?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END.
|
||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
|
||||
case GetBeatmapRequest getBeatmapRequest:
|
||||
{
|
||||
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.BeatmapInfo.OnlineID).Single());
|
||||
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.OnlineID).Single());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user