mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 18:27:26 +08:00
Merge branch 'master' into beatmap-card/download-button
This commit is contained in:
commit
c38c95b373
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -5,7 +5,7 @@ updates:
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "17:00"
|
||||
open-pull-requests-limit: 99
|
||||
open-pull-requests-limit: 0 # disabled until https://github.com/dependabot/dependabot-core/issues/369 is resolved.
|
||||
ignore:
|
||||
- dependency-name: Microsoft.EntityFrameworkCore.Design
|
||||
versions:
|
||||
|
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@ -52,22 +52,29 @@ jobs:
|
||||
|
||||
build-only-android:
|
||||
name: Build only (Android)
|
||||
runs-on: windows-latest
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Pin Xamarin.Android version to 11.2 for now to avoid build failures caused by a Xamarin-side regression.
|
||||
# See: https://github.com/xamarin/xamarin-android/issues/6284
|
||||
# This can be removed/reverted when the fix makes it to upstream and is deployed on github runners.
|
||||
- name: Set default Xamarin SDK version
|
||||
run: |
|
||||
$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2
|
||||
|
||||
- name: Install .NET 5.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "5.0.x"
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1
|
||||
|
||||
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
|
||||
# cannot accept .sln(f) files as arguments.
|
||||
# Build just the main game for now.
|
||||
- name: Build
|
||||
run: msbuild osu.Android.slnf /restore /p:Configuration=Debug
|
||||
run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug
|
||||
|
||||
build-only-ios:
|
||||
# While this workflow technically *can* run, it fails as iOS builds are blocked by multiple issues.
|
||||
|
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1108.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
|
@ -0,0 +1,118 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Edit.Checks;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCheckBananaShowerGap
|
||||
{
|
||||
private CheckBananaShowerGap check;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckBananaShowerGap();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllowedSpinnerGaps()
|
||||
{
|
||||
assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Easy);
|
||||
assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Normal);
|
||||
assertOk(mockBeatmap(125, 1000, 1250), DifficultyRating.Hard);
|
||||
assertOk(mockBeatmap(125, 1000, 1125), DifficultyRating.Insane);
|
||||
assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.Expert);
|
||||
assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisallowedSpinnerGapStart()
|
||||
{
|
||||
assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Easy);
|
||||
assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Normal);
|
||||
assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Hard);
|
||||
assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Insane);
|
||||
assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.Expert);
|
||||
assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisallowedSpinnerGapEnd()
|
||||
{
|
||||
assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Easy);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Normal);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1249), DifficultyRating.Hard);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1124), DifficultyRating.Insane);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.Expert);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConsecutiveSpinners()
|
||||
{
|
||||
var spinnerConsecutiveBeatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new BananaShower { StartTime = 0, EndTime = 100, X = 0 },
|
||||
new BananaShower { StartTime = 101, EndTime = 200, X = 0 },
|
||||
new BananaShower { StartTime = 201, EndTime = 300, X = 0 }
|
||||
}
|
||||
};
|
||||
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Easy);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Normal);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Hard);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Insane);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Expert);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
private Beatmap<HitObject> mockBeatmap(double bananaShowerStart, double bananaShowerEnd, double nextFruitStart)
|
||||
{
|
||||
return new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit { StartTime = 0, X = 0 },
|
||||
new BananaShower { StartTime = bananaShowerStart, EndTime = bananaShowerEnd, X = 0 },
|
||||
new Fruit { StartTime = nextFruitStart, X = 0 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating)
|
||||
{
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
private void assertTooShortSpinnerStart(IBeatmap beatmap, DifficultyRating difficultyRating)
|
||||
{
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerStartGap));
|
||||
}
|
||||
|
||||
private void assertTooShortSpinnerEnd(IBeatmap beatmap, DifficultyRating difficultyRating)
|
||||
{
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerEndGap));
|
||||
}
|
||||
}
|
||||
}
|
110
osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
Normal file
110
osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
Normal file
@ -0,0 +1,110 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
public class TestSceneCatchModNoScope : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestVisibleDuringBreak()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModNoScope
|
||||
{
|
||||
HiddenComboCount = { Value = 0 },
|
||||
},
|
||||
Autoplay = true,
|
||||
PassCondition = () => true,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 1000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0));
|
||||
AddUntilStep("wait for start of break", isBreak);
|
||||
AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1));
|
||||
AddUntilStep("wait for end of break", () => !isBreak());
|
||||
AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibleAfterComboBreak()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModNoScope
|
||||
{
|
||||
HiddenComboCount = { Value = 2 },
|
||||
},
|
||||
Autoplay = true,
|
||||
PassCondition = () => true,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit
|
||||
{
|
||||
X = 0,
|
||||
StartTime = 1000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 3000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.WIDTH,
|
||||
StartTime = 5000,
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("catcher must start visible", () => catcherAlphaAlmostEquals(1));
|
||||
AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2);
|
||||
AddAssert("catcher must dim after combo", () => !catcherAlphaAlmostEquals(1));
|
||||
AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0);
|
||||
AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1));
|
||||
}
|
||||
|
||||
private bool isBreak() => Player.IsBreakTime.Value;
|
||||
|
||||
private bool catcherAlphaAlmostEquals(float alpha)
|
||||
{
|
||||
var playfield = (CatchPlayfield)Player.DrawableRuleset.Playfield;
|
||||
return Precision.AlmostEquals(playfield.CatcherArea.Alpha, alpha);
|
||||
}
|
||||
}
|
||||
}
|
@ -133,6 +133,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||
new CatchModFloatingFruits(),
|
||||
new CatchModMuted(),
|
||||
new CatchModNoScope(),
|
||||
};
|
||||
|
||||
default:
|
||||
@ -188,5 +189,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||
}
|
||||
}
|
||||
|
24
osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs
Normal file
24
osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// 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 System.Linq;
|
||||
using osu.Game.Rulesets.Catch.Edit.Checks;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public class CatchBeatmapVerifier : IBeatmapVerifier
|
||||
{
|
||||
private readonly List<ICheck> checks = new List<ICheck>
|
||||
{
|
||||
new CheckBananaShowerGap()
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
return checks.SelectMany(check => check.Run(context));
|
||||
}
|
||||
}
|
||||
}
|
102
osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs
Normal file
102
osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs
Normal file
@ -0,0 +1,102 @@
|
||||
// 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 osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Checks
|
||||
{
|
||||
/// <summary>
|
||||
/// Check the spinner/banana shower gaps specified in the osu!catch difficulty specific ranking criteria.
|
||||
/// </summary>
|
||||
public class CheckBananaShowerGap : ICheck
|
||||
{
|
||||
private static readonly Dictionary<DifficultyRating, (int startGap, int endGap)> spinner_delta_threshold = new Dictionary<DifficultyRating, (int, int)>
|
||||
{
|
||||
[DifficultyRating.Easy] = (250, 250),
|
||||
[DifficultyRating.Normal] = (250, 250),
|
||||
[DifficultyRating.Hard] = (125, 250),
|
||||
[DifficultyRating.Insane] = (125, 125),
|
||||
[DifficultyRating.Expert] = (62, 125),
|
||||
[DifficultyRating.ExpertPlus] = (62, 125)
|
||||
};
|
||||
|
||||
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Too short spinner gap");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateBananaShowerStartGap(this),
|
||||
new IssueTemplateBananaShowerEndGap(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
var hitObjects = context.Beatmap.HitObjects;
|
||||
(int expectedStartDelta, int expectedEndDelta) = spinner_delta_threshold[context.InterpretedDifficulty];
|
||||
|
||||
for (int i = 0; i < hitObjects.Count - 1; ++i)
|
||||
{
|
||||
if (!(hitObjects[i] is BananaShower bananaShower))
|
||||
continue;
|
||||
|
||||
// Skip if the previous hitobject is a banana shower, consecutive spinners are allowed
|
||||
if (i != 0 && hitObjects[i - 1] is CatchHitObject previousHitObject && !(previousHitObject is BananaShower))
|
||||
{
|
||||
double spinnerStartDelta = bananaShower.StartTime - previousHitObject.GetEndTime();
|
||||
|
||||
if (spinnerStartDelta < expectedStartDelta)
|
||||
{
|
||||
yield return new IssueTemplateBananaShowerStartGap(this)
|
||||
.Create(spinnerStartDelta, expectedStartDelta, bananaShower, previousHitObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if the next hitobject is a banana shower, consecutive spinners are allowed
|
||||
if (hitObjects[i + 1] is CatchHitObject nextHitObject && !(nextHitObject is BananaShower))
|
||||
{
|
||||
double spinnerEndDelta = nextHitObject.StartTime - bananaShower.EndTime;
|
||||
|
||||
if (spinnerEndDelta < expectedEndDelta)
|
||||
{
|
||||
yield return new IssueTemplateBananaShowerEndGap(this)
|
||||
.Create(spinnerEndDelta, expectedEndDelta, bananaShower, nextHitObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class IssueTemplateBananaShowerGap : IssueTemplate
|
||||
{
|
||||
protected IssueTemplateBananaShowerGap(ICheck check, IssueType issueType, string unformattedMessage)
|
||||
: base(check, issueType, unformattedMessage)
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(double deltaTime, int expectedDeltaTime, params HitObject[] hitObjects)
|
||||
{
|
||||
return new Issue(hitObjects, this, Math.Floor(deltaTime), expectedDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateBananaShowerStartGap : IssueTemplateBananaShowerGap
|
||||
{
|
||||
public IssueTemplateBananaShowerStartGap(ICheck check)
|
||||
: base(check, IssueType.Problem, "There is only {0} ms between the start of the spinner and the last object, it should not be less than {1} ms.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateBananaShowerEndGap : IssueTemplateBananaShowerGap
|
||||
{
|
||||
public IssueTemplateBananaShowerEndGap(ICheck check)
|
||||
: base(check, IssueType.Problem, "There is only {0} ms between the end of the spinner and the next object, it should not be less than {1} ms.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
Normal file
40
osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModNoScope : ModNoScope, IUpdatableByPlayfield
|
||||
{
|
||||
public override string Description => "Where's the catcher?";
|
||||
|
||||
[SettingSource(
|
||||
"Hidden at combo",
|
||||
"The combo count at which the catcher becomes completely hidden",
|
||||
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
||||
)]
|
||||
public override BindableInt HiddenComboCount { get; } = new BindableInt
|
||||
{
|
||||
Default = 10,
|
||||
Value = 10,
|
||||
MinValue = 0,
|
||||
MaxValue = 50,
|
||||
};
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
var catchPlayfield = (CatchPlayfield)playfield;
|
||||
bool shouldAlwaysShowCatcher = IsBreakTime.Value;
|
||||
float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha;
|
||||
catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
[Cached]
|
||||
private EditorClipboard clipboard = new EditorClipboard();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
|
@ -0,0 +1,185 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public class TestSceneSliderStreamConversion : TestSceneOsuEditor
|
||||
{
|
||||
private BindableBeatDivisor beatDivisor => (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
|
||||
|
||||
[Test]
|
||||
public void TestSimpleConversion()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("select first slider", () =>
|
||||
{
|
||||
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
|
||||
EditorClock.Seek(slider.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
});
|
||||
|
||||
convertToStream();
|
||||
|
||||
AddAssert("stream created", () => streamCreatedFor(slider,
|
||||
(time: 0, pathPosition: 0),
|
||||
(time: 0.25, pathPosition: 0.25),
|
||||
(time: 0.5, pathPosition: 0.5),
|
||||
(time: 0.75, pathPosition: 0.75),
|
||||
(time: 1, pathPosition: 1)));
|
||||
|
||||
AddStep("undo", () => Editor.Undo());
|
||||
AddAssert("slider restored", () => sliderRestored(slider));
|
||||
|
||||
AddStep("select first slider", () =>
|
||||
{
|
||||
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
|
||||
EditorClock.Seek(slider.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
});
|
||||
AddStep("change beat divisor", () => beatDivisor.Value = 8);
|
||||
|
||||
convertToStream();
|
||||
AddAssert("stream created", () => streamCreatedFor(slider,
|
||||
(time: 0, pathPosition: 0),
|
||||
(time: 0.125, pathPosition: 0.125),
|
||||
(time: 0.25, pathPosition: 0.25),
|
||||
(time: 0.375, pathPosition: 0.375),
|
||||
(time: 0.5, pathPosition: 0.5),
|
||||
(time: 0.625, pathPosition: 0.625),
|
||||
(time: 0.75, pathPosition: 0.75),
|
||||
(time: 0.875, pathPosition: 0.875),
|
||||
(time: 1, pathPosition: 1)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConversionWithNonMatchingDivisor()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("select second slider", () =>
|
||||
{
|
||||
slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider).ElementAt(1);
|
||||
EditorClock.Seek(slider.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
});
|
||||
AddStep("change beat divisor", () => beatDivisor.Value = 3);
|
||||
|
||||
convertToStream();
|
||||
|
||||
AddAssert("stream created", () => streamCreatedFor(slider,
|
||||
(time: 0, pathPosition: 0),
|
||||
(time: 2 / 3d, pathPosition: 2 / 3d)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConversionWithRepeats()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("select first slider with repeats", () =>
|
||||
{
|
||||
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider s && s.RepeatCount > 0);
|
||||
EditorClock.Seek(slider.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
});
|
||||
AddStep("change beat divisor", () => beatDivisor.Value = 2);
|
||||
|
||||
convertToStream();
|
||||
|
||||
AddAssert("stream created", () => streamCreatedFor(slider,
|
||||
(time: 0, pathPosition: 0),
|
||||
(time: 0.25, pathPosition: 0.5),
|
||||
(time: 0.5, pathPosition: 1),
|
||||
(time: 0.75, pathPosition: 0.5),
|
||||
(time: 1, pathPosition: 0)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConversionPreservesSliderProperties()
|
||||
{
|
||||
Slider slider = null;
|
||||
|
||||
AddStep("select second new-combo-starting slider", () =>
|
||||
{
|
||||
slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider s && s.NewCombo).ElementAt(1);
|
||||
EditorClock.Seek(slider.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
});
|
||||
|
||||
convertToStream();
|
||||
|
||||
AddAssert("stream created", () => streamCreatedFor(slider,
|
||||
(time: 0, pathPosition: 0),
|
||||
(time: 0.25, pathPosition: 0.25),
|
||||
(time: 0.5, pathPosition: 0.5),
|
||||
(time: 0.75, pathPosition: 0.75),
|
||||
(time: 1, pathPosition: 1)));
|
||||
|
||||
AddStep("undo", () => Editor.Undo());
|
||||
AddAssert("slider restored", () => sliderRestored(slider));
|
||||
}
|
||||
|
||||
private void convertToStream()
|
||||
{
|
||||
AddStep("convert to stream", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LControl);
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.F);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
});
|
||||
}
|
||||
|
||||
private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles)
|
||||
{
|
||||
if (EditorBeatmap.HitObjects.Contains(slider))
|
||||
return false;
|
||||
|
||||
foreach ((double expectedTime, double expectedPathPosition) in expectedCircles)
|
||||
{
|
||||
double time = slider.StartTime + slider.Duration * expectedTime;
|
||||
Vector2 position = slider.Position + slider.Path.PositionAt(expectedPathPosition);
|
||||
|
||||
if (!EditorBeatmap.HitObjects.OfType<HitCircle>().Any(h => matches(h, time, position, slider.NewCombo && expectedTime == 0)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
bool matches(HitCircle circle, double time, Vector2 position, bool startsNewCombo) =>
|
||||
Precision.AlmostEquals(circle.StartTime, time, 1)
|
||||
&& Precision.AlmostEquals(circle.Position, position, 0.01f)
|
||||
&& circle.NewCombo == startsNewCombo
|
||||
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples)
|
||||
&& circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint);
|
||||
}
|
||||
|
||||
private bool sliderRestored(Slider slider)
|
||||
{
|
||||
var objects = EditorBeatmap.HitObjects.Where(h => h.StartTime >= slider.StartTime && h.GetEndTime() <= slider.EndTime).ToList();
|
||||
|
||||
if (objects.Count > 1)
|
||||
return false;
|
||||
|
||||
var hitObject = objects.Single();
|
||||
if (!(hitObject is Slider restoredSlider))
|
||||
return false;
|
||||
|
||||
return Precision.AlmostEquals(slider.StartTime, restoredSlider.StartTime)
|
||||
&& Precision.AlmostEquals(slider.GetEndTime(), restoredSlider.GetEndTime())
|
||||
&& Precision.AlmostEquals(slider.Position, restoredSlider.Position, 0.01f)
|
||||
&& Precision.AlmostEquals(slider.EndPosition, restoredSlider.EndPosition, 0.01f);
|
||||
}
|
||||
}
|
||||
}
|
@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
AddAssert("cursor must start visible", () => cursorAlphaAlmostEquals(1));
|
||||
AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2);
|
||||
AddAssert("cursor must dim after combo", () => !cursorAlphaAlmostEquals(1));
|
||||
AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0));
|
||||
AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0);
|
||||
AddUntilStep("wait for cursor to show", () => cursorAlphaAlmostEquals(1));
|
||||
}
|
||||
|
||||
|
@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.6975550434910005d, "diffcalc-test")]
|
||||
[TestCase(1.4670676815251105d, "zero-length-sliders")]
|
||||
[TestCase(6.6972307565739273d, "diffcalc-test")]
|
||||
[TestCase(1.4484754139145539d, "zero-length-sliders")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(8.9389769779826267d, "diffcalc-test")]
|
||||
[TestCase(1.7786917985891204d, "zero-length-sliders")]
|
||||
[TestCase(8.9382559208689809d, "diffcalc-test")]
|
||||
[TestCase(1.7548875851757628d, "zero-length-sliders")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new OsuModDoubleTime());
|
||||
|
||||
|
@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
public double AimStrain { get; set; }
|
||||
public double SpeedStrain { get; set; }
|
||||
public double FlashlightRating { get; set; }
|
||||
public double SliderFactor { get; set; }
|
||||
public double ApproachRate { get; set; }
|
||||
public double OverallDifficulty { get; set; }
|
||||
public double DrainRate { get; set; }
|
||||
|
@ -34,8 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return new OsuDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
|
||||
double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
|
||||
double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
|
||||
|
||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||
|
||||
if (mods.Any(h => h is OsuModRelax))
|
||||
speedRating = 0.0;
|
||||
@ -74,6 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
AimStrain = aimRating,
|
||||
SpeedStrain = speedRating,
|
||||
FlashlightRating = flashlightRating,
|
||||
SliderFactor = sliderFactor,
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||
DrainRate = drainRate,
|
||||
@ -108,7 +112,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
return new Skill[]
|
||||
{
|
||||
new Aim(mods),
|
||||
new Aim(mods, true),
|
||||
new Aim(mods, false),
|
||||
new Speed(mods, hitWindowGreat),
|
||||
new Flashlight(mods)
|
||||
};
|
||||
|
@ -125,6 +125,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||
}
|
||||
|
||||
// We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator.
|
||||
double estimateDifficultSliders = Attributes.SliderCount * 0.15;
|
||||
|
||||
if (Attributes.SliderCount > 0)
|
||||
{
|
||||
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
|
||||
double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor;
|
||||
aimValue *= sliderNerfFactor;
|
||||
}
|
||||
|
||||
aimValue *= accuracy;
|
||||
// It is important to also consider accuracy difficulty when doing that.
|
||||
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
private const int min_delta_time = 25;
|
||||
private const float maximum_slider_radius = normalized_radius * 2.4f;
|
||||
private const float assumed_slider_radius = normalized_radius * 1.65f;
|
||||
private const float assumed_slider_radius = normalized_radius * 1.8f;
|
||||
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
|
@ -14,11 +14,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Aim : OsuStrainSkill
|
||||
{
|
||||
public Aim(Mod[] mods)
|
||||
public Aim(Mod[] mods, bool withSliders)
|
||||
: base(mods)
|
||||
{
|
||||
this.withSliders = withSliders;
|
||||
}
|
||||
|
||||
private readonly bool withSliders;
|
||||
|
||||
protected override int HistoryLength => 2;
|
||||
|
||||
private const double wide_angle_multiplier = 1.5;
|
||||
@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
|
||||
|
||||
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
if (osuLastObj.BaseObject is Slider)
|
||||
if (osuLastObj.BaseObject is Slider && withSliders)
|
||||
{
|
||||
double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
|
||||
double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
@ -55,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
// As above, do the same for the previous hitobject.
|
||||
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
|
||||
|
||||
if (osuLastLastObj.BaseObject is Slider)
|
||||
if (osuLastLastObj.BaseObject is Slider && withSliders)
|
||||
{
|
||||
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
@ -135,7 +138,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier);
|
||||
|
||||
// Add in additional slider velocity bonus.
|
||||
aimStrain += sliderBonus * slider_multiplier;
|
||||
if (withSliders)
|
||||
aimStrain += sliderBonus * slider_multiplier;
|
||||
|
||||
return aimStrain;
|
||||
}
|
||||
|
@ -11,9 +11,12 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
@ -47,6 +50,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
|
||||
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
@ -173,6 +179,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (!IsSelected)
|
||||
return false;
|
||||
|
||||
if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed)
|
||||
{
|
||||
convertToStream();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int addControlPoint(Vector2 position)
|
||||
{
|
||||
position -= HitObject.Position;
|
||||
@ -234,9 +254,56 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
editorBeatmap?.Update(HitObject);
|
||||
}
|
||||
|
||||
private void convertToStream()
|
||||
{
|
||||
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
|
||||
return;
|
||||
|
||||
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
|
||||
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
|
||||
|
||||
changeHandler.BeginChange();
|
||||
|
||||
int i = 0;
|
||||
double time = HitObject.StartTime;
|
||||
|
||||
while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1))
|
||||
{
|
||||
// positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()]
|
||||
// and indicates how many fractional spans of a slider have passed up to time.
|
||||
double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount();
|
||||
double pathPosition = positionWithRepeats - (int)positionWithRepeats;
|
||||
// every second span is in the reverse direction - need to reverse the path position.
|
||||
if (Precision.AlmostBigger(positionWithRepeats % 2, 1))
|
||||
pathPosition = 1 - pathPosition;
|
||||
|
||||
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
||||
|
||||
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
|
||||
samplePoint.Time = time;
|
||||
|
||||
editorBeatmap.Add(new HitCircle
|
||||
{
|
||||
StartTime = time,
|
||||
Position = position,
|
||||
NewCombo = i == 0 && HitObject.NewCombo,
|
||||
SampleControlPoint = samplePoint,
|
||||
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
|
||||
});
|
||||
|
||||
i += 1;
|
||||
time = HitObject.StartTime + i * streamSpacing;
|
||||
}
|
||||
|
||||
editorBeatmap.Remove(HitObject);
|
||||
|
||||
changeHandler.EndChange();
|
||||
}
|
||||
|
||||
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
||||
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
|
||||
};
|
||||
|
||||
// Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions.
|
||||
|
@ -4,52 +4,29 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor, IApplicableToPlayer, IApplicableToBeatmap
|
||||
public class OsuModNoScope : ModNoScope, IUpdatableByPlayfield, IApplicableToBeatmap
|
||||
{
|
||||
/// <summary>
|
||||
/// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
|
||||
/// </summary>
|
||||
private const float min_alpha = 0.0002f;
|
||||
|
||||
private const float transition_duration = 100;
|
||||
|
||||
public override string Name => "No Scope";
|
||||
public override string Acronym => "NS";
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
|
||||
public override string Description => "Where's the cursor?";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
private BindableNumber<int> currentCombo;
|
||||
private IBindable<bool> isBreakTime;
|
||||
private PeriodTracker spinnerPeriods;
|
||||
|
||||
private float comboBasedAlpha;
|
||||
|
||||
[SettingSource(
|
||||
"Hidden at combo",
|
||||
"The combo count at which the cursor becomes completely hidden",
|
||||
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
||||
)]
|
||||
public BindableInt HiddenComboCount { get; } = new BindableInt
|
||||
public override BindableInt HiddenComboCount { get; } = new BindableInt
|
||||
{
|
||||
Default = 10,
|
||||
Value = 10,
|
||||
@ -57,39 +34,16 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
MaxValue = 50,
|
||||
};
|
||||
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
isBreakTime = player.IsBreakTime.GetBoundCopy();
|
||||
}
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType<Spinner>().Select(b => new Period(b.StartTime - transition_duration, b.EndTime)));
|
||||
spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType<Spinner>().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime)));
|
||||
}
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
if (HiddenComboCount.Value == 0) return;
|
||||
|
||||
currentCombo = scoreProcessor.Combo.GetBoundCopy();
|
||||
currentCombo.BindValueChanged(combo =>
|
||||
{
|
||||
comboBasedAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value);
|
||||
}, true);
|
||||
bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime);
|
||||
float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha;
|
||||
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
}
|
||||
|
||||
public virtual void Update(Playfield playfield)
|
||||
{
|
||||
bool shouldAlwaysShowCursor = isBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime);
|
||||
float targetAlpha = shouldAlwaysShowCursor ? 1 : comboBasedAlpha;
|
||||
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
public class HiddenComboSlider : OsuSliderBar<int>
|
||||
{
|
||||
public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||
});
|
||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty");
|
||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
||||
|
||||
checkMutations();
|
||||
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||
return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
|
||||
});
|
||||
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty");
|
||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,11 +113,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual("Soleily", metadata.Artist);
|
||||
Assert.AreEqual("Soleily", metadata.ArtistUnicode);
|
||||
Assert.AreEqual("Gamu", metadata.Author.Username);
|
||||
Assert.AreEqual("Insane", beatmapInfo.Version);
|
||||
Assert.AreEqual("Insane", beatmapInfo.DifficultyName);
|
||||
Assert.AreEqual(string.Empty, metadata.Source);
|
||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
||||
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
|
||||
Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||
Assert.AreEqual(557821, beatmapInfo.OnlineID);
|
||||
Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var beatmap = decodeAsJson(normal);
|
||||
var meta = beatmap.BeatmapInfo.Metadata;
|
||||
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
|
||||
Assert.AreEqual("Soleily", meta.Artist);
|
||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Scoring;
|
||||
@ -387,6 +388,41 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestModelCreationFailureDoesntReturn()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
var importer = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
var progressNotification = new ImportProgressNotification();
|
||||
|
||||
var zipStream = new MemoryStream();
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
|
||||
|
||||
var imported = await importer.Import(
|
||||
progressNotification,
|
||||
new ImportTask(zipStream, string.Empty)
|
||||
);
|
||||
|
||||
checkBeatmapSetCount(osu, 0);
|
||||
checkBeatmapCount(osu, 0);
|
||||
|
||||
Assert.IsEmpty(imported);
|
||||
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestRollbackOnFailure()
|
||||
{
|
||||
@ -506,7 +542,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
|
||||
foreach (var b in imported.Beatmaps)
|
||||
b.OnlineBeatmapID = null;
|
||||
b.OnlineID = null;
|
||||
|
||||
osu.Dependencies.Get<BeatmapManager>().Update(imported);
|
||||
|
||||
@ -545,19 +581,19 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var toImport = new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = 1,
|
||||
OnlineID = 1,
|
||||
Metadata = metadata,
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = 2,
|
||||
OnlineID = 2,
|
||||
Metadata = metadata,
|
||||
BaseDifficulty = difficulty
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = 2,
|
||||
OnlineID = 2,
|
||||
Metadata = metadata,
|
||||
Status = BeatmapSetOnlineStatus.Loved,
|
||||
BaseDifficulty = difficulty
|
||||
@ -570,8 +606,8 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
var imported = await manager.Import(toImport);
|
||||
|
||||
Assert.NotNull(imported);
|
||||
Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID);
|
||||
Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID);
|
||||
Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID);
|
||||
Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -790,12 +826,12 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
// Update via the beatmap, not the beatmap info, to ensure correct linking
|
||||
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
|
||||
Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
|
||||
beatmapToUpdate.BeatmapInfo.Version = "updated";
|
||||
beatmapToUpdate.BeatmapInfo.DifficultyName = "updated";
|
||||
|
||||
manager.Update(setToUpdate);
|
||||
|
||||
BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID);
|
||||
Assert.That(updatedInfo.Version, Is.EqualTo("updated"));
|
||||
Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -863,7 +899,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var beatmap = working.Beatmap;
|
||||
|
||||
beatmap.BeatmapInfo.Version = "difficulty";
|
||||
beatmap.BeatmapInfo.DifficultyName = "difficulty";
|
||||
beatmap.BeatmapInfo.Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "Artist/With\\Slashes",
|
||||
@ -1020,13 +1056,13 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
IEnumerable<BeatmapSetInfo> resultSets = null;
|
||||
var store = osu.Dependencies.Get<BeatmapManager>();
|
||||
waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(),
|
||||
waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(),
|
||||
@"BeatmapSet did not import to the database in allocated time.", timeout);
|
||||
|
||||
// ensure we were stored to beatmap database backing...
|
||||
Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
|
||||
IEnumerable<BeatmapInfo> queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0);
|
||||
IEnumerable<BeatmapSetInfo> queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526);
|
||||
IEnumerable<BeatmapInfo> queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0);
|
||||
IEnumerable<BeatmapSetInfo> queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526);
|
||||
|
||||
// if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
|
||||
waitForOrAssert(() => queryBeatmaps().Count() == 12,
|
||||
@ -1042,7 +1078,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var set = queryBeatmapSets().First();
|
||||
foreach (BeatmapInfo b in set.Beatmaps)
|
||||
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
|
||||
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
||||
Assert.IsTrue(set.Beatmaps.Count > 0);
|
||||
var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
|
||||
Assert.IsTrue(beatmap?.HitObjects.Any() == true);
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var meta = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
|
||||
Assert.AreEqual("Soleily", meta.Artist);
|
||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
Title = "title",
|
||||
Author = new APIUser { Username = "creator" }
|
||||
},
|
||||
Version = "difficulty"
|
||||
DifficultyName = "difficulty"
|
||||
};
|
||||
|
||||
Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator) [difficulty]"));
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Stores;
|
||||
using osu.Game.Tests.Resources;
|
||||
using Realms;
|
||||
@ -367,6 +368,34 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModelCreationFailureDoesntReturn()
|
||||
{
|
||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(realmFactory, storage);
|
||||
using var store = new RealmRulesetStore(realmFactory, storage);
|
||||
|
||||
var progressNotification = new ImportProgressNotification();
|
||||
|
||||
var zipStream = new MemoryStream();
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
|
||||
|
||||
var imported = await importer.Import(
|
||||
progressNotification,
|
||||
new ImportTask(zipStream, string.Empty)
|
||||
);
|
||||
|
||||
checkBeatmapSetCount(realmFactory.Context, 0);
|
||||
checkBeatmapCount(realmFactory.Context, 0);
|
||||
|
||||
Assert.IsEmpty(imported);
|
||||
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRollbackOnFailure()
|
||||
{
|
||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
// While this is a problem, it is out of scope for this check and is caught by a different one.
|
||||
beatmap.Metadata.BackgroundFile = string.Empty;
|
||||
var context = getContext(null, System.Array.Empty<byte>());
|
||||
var context = getContext(null, new MemoryStream(System.Array.Empty<byte>()));
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
@ -103,7 +103,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestTooUncompressed()
|
||||
{
|
||||
var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]);
|
||||
var context = getContext(new Texture(1920, 1080), new MemoryStream(new byte[1024 * 1024 * 3]));
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
@ -111,19 +111,32 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed);
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null)
|
||||
[Test]
|
||||
public void TestStreamClosed()
|
||||
{
|
||||
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object);
|
||||
var background = new Texture(1920, 1080);
|
||||
var stream = new Mock<MemoryStream>(new byte[1024 * 1024]);
|
||||
|
||||
var context = getContext(background, stream.Object);
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
|
||||
stream.Verify(x => x.Close(), Times.Once());
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Texture background, [CanBeNull] Stream stream = null)
|
||||
{
|
||||
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, stream).Object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mock of the working beatmap with the given background and filesize.
|
||||
/// Returns the mock of the working beatmap with the given background and its file stream.
|
||||
/// </summary>
|
||||
/// <param name="background">The texture of the background.</param>
|
||||
/// <param name="fileBytes">The bytes that represent the background file.</param>
|
||||
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null)
|
||||
/// <param name="stream">The stream representing the background file.</param>
|
||||
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(Texture background, [CanBeNull] Stream stream = null)
|
||||
{
|
||||
var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]);
|
||||
stream ??= new MemoryStream(new byte[1024 * 1024]);
|
||||
|
||||
var mock = new Mock<IWorkingBeatmap>();
|
||||
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
|
||||
|
133
osu.Game.Tests/Models/DisplayStringTest.cs
Normal file
133
osu.Game.Tests/Models/DisplayStringTest.cs
Normal file
@ -0,0 +1,133 @@
|
||||
// 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 Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Tests.Models
|
||||
{
|
||||
[TestFixture]
|
||||
public class DisplayStringTest
|
||||
{
|
||||
[Test]
|
||||
public void TestNull()
|
||||
{
|
||||
IBeatmapSetInfo? beatmap = null;
|
||||
Assert.That(beatmap.GetDisplayString(), Is.EqualTo("null"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapSet()
|
||||
{
|
||||
var mock = new Mock<IBeatmapSetInfo>();
|
||||
|
||||
mock.Setup(m => m.Metadata!.Artist).Returns("artist");
|
||||
mock.Setup(m => m.Metadata!.Title).Returns("title");
|
||||
mock.Setup(m => m.Metadata!.Author.Username).Returns("author");
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapSetWithNoAuthor()
|
||||
{
|
||||
var mock = new Mock<IBeatmapSetInfo>();
|
||||
|
||||
mock.Setup(m => m.Metadata!.Artist).Returns("artist");
|
||||
mock.Setup(m => m.Metadata!.Title).Returns("title");
|
||||
mock.Setup(m => m.Metadata!.Author.Username).Returns(string.Empty);
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapSetWithNoMetadata()
|
||||
{
|
||||
var mock = new Mock<IBeatmapSetInfo>();
|
||||
|
||||
mock.Setup(m => m.Metadata).Returns(new BeatmapMetadata());
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("unknown artist - unknown title"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmap()
|
||||
{
|
||||
var mock = new Mock<IBeatmapInfo>();
|
||||
|
||||
mock.Setup(m => m.Metadata.Artist).Returns("artist");
|
||||
mock.Setup(m => m.Metadata.Title).Returns("title");
|
||||
mock.Setup(m => m.Metadata.Author.Username).Returns("author");
|
||||
mock.Setup(m => m.DifficultyName).Returns("difficulty");
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author) [difficulty]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMetadata()
|
||||
{
|
||||
var mock = new Mock<IBeatmapMetadataInfo>();
|
||||
|
||||
mock.Setup(m => m.Artist).Returns("artist");
|
||||
mock.Setup(m => m.Title).Returns("title");
|
||||
mock.Setup(m => m.Author.Username).Returns("author");
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScore()
|
||||
{
|
||||
var mock = new Mock<IScoreInfo>();
|
||||
|
||||
mock.Setup(m => m.User).Returns(new APIUser { Username = "user" }); // TODO: temporary.
|
||||
mock.Setup(m => m.Beatmap.Metadata.Artist).Returns("artist");
|
||||
mock.Setup(m => m.Beatmap.Metadata.Title).Returns("title");
|
||||
mock.Setup(m => m.Beatmap.Metadata.Author.Username).Returns("author");
|
||||
mock.Setup(m => m.Beatmap.DifficultyName).Returns("difficulty");
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user playing artist - title (author) [difficulty]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRuleset()
|
||||
{
|
||||
var mock = new Mock<IRulesetInfo>();
|
||||
|
||||
mock.Setup(m => m.Name).Returns("ruleset");
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("ruleset"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUser()
|
||||
{
|
||||
var mock = new Mock<IUser>();
|
||||
|
||||
mock.Setup(m => m.Username).Returns("user");
|
||||
|
||||
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFallback()
|
||||
{
|
||||
var fallback = new Fallback();
|
||||
|
||||
Assert.That(fallback.GetDisplayString(), Is.EqualTo("fallback"));
|
||||
}
|
||||
|
||||
private class Fallback
|
||||
{
|
||||
public override string ToString() => "fallback";
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestOnlineWithOnline()
|
||||
{
|
||||
var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
|
||||
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
|
||||
var ourInfo = new BeatmapSetInfo { OnlineID = 123 };
|
||||
var otherInfo = new BeatmapSetInfo { OnlineID = 123 };
|
||||
|
||||
Assert.AreEqual(ourInfo, otherInfo);
|
||||
}
|
||||
@ -30,8 +30,8 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestDatabasedWithOnline()
|
||||
{
|
||||
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 };
|
||||
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 };
|
||||
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 };
|
||||
var otherInfo = new BeatmapSetInfo { OnlineID = 12 };
|
||||
|
||||
Assert.AreEqual(ourInfo, otherInfo);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
|
||||
{
|
||||
Ruleset = new RulesetInfo { ID = 5 },
|
||||
StarDifficulty = 4.0d,
|
||||
StarRating = 4.0d,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
ApproachRate = 5.0f,
|
||||
@ -34,7 +34,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Source = "unit tests",
|
||||
Tags = "look for tags too",
|
||||
},
|
||||
Version = "version as well",
|
||||
DifficultyName = "version as well",
|
||||
Length = 2500,
|
||||
BPM = 160,
|
||||
BeatDivisor = 12,
|
||||
@ -207,8 +207,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered)
|
||||
{
|
||||
var beatmap = getExampleBeatmap();
|
||||
beatmap.OnlineBeatmapID = 20201010;
|
||||
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 };
|
||||
beatmap.OnlineID = 20201010;
|
||||
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 };
|
||||
|
||||
var criteria = new FilterCriteria { SearchText = query };
|
||||
var carouselItem = new CarouselBeatmap(beatmap);
|
||||
|
@ -22,7 +22,8 @@ namespace osu.Game.Tests.NonVisual.Ranking
|
||||
|
||||
var unstableRate = new UnstableRate(events);
|
||||
|
||||
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10)));
|
||||
Assert.IsNotNull(unstableRate.Value);
|
||||
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@ -16,7 +17,30 @@ namespace osu.Game.Tests.Online
|
||||
private BeatmapManager beatmaps;
|
||||
private ProgressNotification recentNotification;
|
||||
|
||||
private static readonly BeatmapSetInfo test_model = new BeatmapSetInfo { OnlineBeatmapSetID = 1 };
|
||||
private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo
|
||||
{
|
||||
OnlineID = 1,
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "test author",
|
||||
Title = "test title",
|
||||
Author = new APIUser
|
||||
{
|
||||
Username = "mapper"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly APIBeatmapSet test_online_model = new APIBeatmapSet
|
||||
{
|
||||
OnlineID = 2,
|
||||
Artist = "test author",
|
||||
Title = "test title",
|
||||
Author = new APIUser
|
||||
{
|
||||
Username = "mapper"
|
||||
}
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapManager beatmaps)
|
||||
@ -26,25 +50,41 @@ namespace osu.Game.Tests.Online
|
||||
beatmaps.PostNotification = n => recentNotification = n as ProgressNotification;
|
||||
}
|
||||
|
||||
private static readonly object[][] notification_test_cases =
|
||||
{
|
||||
new object[] { test_db_model },
|
||||
new object[] { test_online_model }
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(notification_test_cases))]
|
||||
public void TestNotificationMessage(IBeatmapSetInfo model)
|
||||
{
|
||||
AddStep("clear recent notification", () => recentNotification = null);
|
||||
AddStep("download beatmap", () => beatmaps.Download(model));
|
||||
|
||||
AddUntilStep("wait for notification", () => recentNotification != null);
|
||||
AddUntilStep("notification text correct", () => recentNotification.Text.ToString() == "Downloading test author - test title (mapper)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancelDownloadFromRequest()
|
||||
{
|
||||
AddStep("download beatmap", () => beatmaps.Download(test_model));
|
||||
AddStep("download beatmap", () => beatmaps.Download(test_db_model));
|
||||
|
||||
AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_model).Cancel());
|
||||
AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model).Cancel());
|
||||
|
||||
AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null);
|
||||
AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null);
|
||||
AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancelDownloadFromNotification()
|
||||
{
|
||||
AddStep("download beatmap", () => beatmaps.Download(test_model));
|
||||
AddStep("download beatmap", () => beatmaps.Download(test_db_model));
|
||||
|
||||
AddStep("cancel download from notification", () => recentNotification.Close());
|
||||
|
||||
AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null);
|
||||
AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null);
|
||||
AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Online
|
||||
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
||||
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
||||
|
||||
var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID);
|
||||
var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID);
|
||||
if (existing != null)
|
||||
beatmaps.Delete(existing);
|
||||
|
||||
@ -101,10 +101,10 @@ namespace osu.Game.Tests.Online
|
||||
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
|
||||
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID)));
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
|
||||
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
|
||||
|
||||
AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID)));
|
||||
AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
|
||||
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,17 @@ namespace osu.Game.Tests.Visual.Components
|
||||
{
|
||||
public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner
|
||||
{
|
||||
private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager();
|
||||
private readonly IAdjustableAudioComponent gameTrackAudio = new AudioAdjustments();
|
||||
|
||||
private readonly TestPreviewTrackManager trackManager;
|
||||
|
||||
private AudioManager audio;
|
||||
|
||||
public TestScenePreviewTrackManager()
|
||||
{
|
||||
trackManager = new TestPreviewTrackManager(gameTrackAudio);
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
@ -151,19 +158,19 @@ namespace osu.Game.Tests.Visual.Components
|
||||
audio.VolumeTrack.Value = 1;
|
||||
});
|
||||
|
||||
AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
|
||||
AddAssert("game not muted", () => gameTrackAudio.AggregateVolume.Value != 0);
|
||||
|
||||
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
|
||||
AddUntilStep("wait loaded", () => track.IsLoaded);
|
||||
AddStep("start track", () => track.Start());
|
||||
AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0);
|
||||
AddAssert("game is muted", () => gameTrackAudio.AggregateVolume.Value == 0);
|
||||
|
||||
if (stopAnyPlaying)
|
||||
AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner));
|
||||
else
|
||||
AddStep("stop track", () => track.Stop());
|
||||
|
||||
AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0);
|
||||
AddAssert("game not muted", () => gameTrackAudio.AggregateVolume.Value != 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -224,6 +231,11 @@ namespace osu.Game.Tests.Visual.Components
|
||||
|
||||
public new PreviewTrack CurrentTrack => base.CurrentTrack;
|
||||
|
||||
public TestPreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments)
|
||||
: base(mainTrackAdjustments)
|
||||
{
|
||||
}
|
||||
|
||||
protected override TrackManagerPreviewTrack CreatePreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
|
||||
|
||||
public override bool UpdateSubTree()
|
||||
|
@ -26,6 +26,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
}
|
||||
});
|
||||
|
||||
[Cached]
|
||||
private EditorClipboard clipboard = new EditorClipboard();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
|
||||
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
|
||||
});
|
||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty");
|
||||
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
|
||||
|
||||
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
|
||||
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
|
||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty");
|
||||
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
123
osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
Normal file
123
osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
Normal file
@ -0,0 +1,123 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneEditorTestGameplay : EditorTestScene
|
||||
{
|
||||
protected override bool IsolateSavingFromDatabase => false;
|
||||
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
private BeatmapSetInfo importedBeatmapSet;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result);
|
||||
base.SetUpSteps();
|
||||
}
|
||||
|
||||
protected override void LoadEditor()
|
||||
{
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
|
||||
base.LoadEditor();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicGameplayTest()
|
||||
{
|
||||
AddStep("click test gameplay button", () =>
|
||||
{
|
||||
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(button);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
EditorPlayer editorPlayer = null;
|
||||
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||
AddStep("exit player", () => editorPlayer.Exit());
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancelGameplayTestWithUnsavedChanges()
|
||||
{
|
||||
AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList()));
|
||||
|
||||
AddStep("click test gameplay button", () =>
|
||||
{
|
||||
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(button);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog);
|
||||
|
||||
AddStep("dismiss prompt", () =>
|
||||
{
|
||||
var button = DialogOverlay.CurrentDialog.Buttons.Last();
|
||||
InputManager.MoveMouseTo(button);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("stayed in editor", () => Stack.CurrentScreen is Editor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSaveChangesBeforeGameplayTest()
|
||||
{
|
||||
AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList()));
|
||||
// bit of a hack to ensure this test can be ran multiple times without running into UNIQUE constraint failures
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = Guid.NewGuid().ToString());
|
||||
|
||||
AddStep("click test gameplay button", () =>
|
||||
{
|
||||
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(button);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog);
|
||||
|
||||
AddStep("save changes", () => DialogOverlay.CurrentDialog.PerformOkAction());
|
||||
|
||||
EditorPlayer editorPlayer = null;
|
||||
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||
AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1);
|
||||
|
||||
AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor);
|
||||
AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning);
|
||||
}
|
||||
|
||||
public override void TearDownSteps()
|
||||
{
|
||||
base.TearDownSteps();
|
||||
AddStep("delete imported", () =>
|
||||
{
|
||||
beatmaps.Delete(importedBeatmapSet);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
public TestSceneSetupScreen()
|
||||
{
|
||||
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||
|
@ -4,6 +4,7 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
@ -18,6 +19,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
protected override bool ScrollUsingMouseWheel => false;
|
||||
|
||||
public TestSceneTimingScreen()
|
||||
|
@ -1,9 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
@ -20,16 +21,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestScenePerformancePointsCounter : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private GameplayState gameplayState;
|
||||
private DependencyProvidingContainer dependencyContainer;
|
||||
|
||||
[Cached]
|
||||
private GameplayState gameplayState;
|
||||
private ScoreProcessor scoreProcessor;
|
||||
|
||||
private int iteration;
|
||||
private Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
|
||||
private PerformancePointsCounter counter;
|
||||
|
||||
public TestScenePerformancePointsCounter()
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps() => AddStep("create components", () =>
|
||||
{
|
||||
var ruleset = CreateRuleset();
|
||||
|
||||
@ -38,32 +40,43 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
|
||||
.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
lastJudgementResult = new Bindable<JudgementResult>();
|
||||
|
||||
gameplayState = new GameplayState(beatmap, ruleset);
|
||||
gameplayState.LastJudgementResult.BindTo(lastJudgementResult);
|
||||
|
||||
scoreProcessor = new ScoreProcessor();
|
||||
}
|
||||
|
||||
Child = dependencyContainer = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(GameplayState), gameplayState),
|
||||
(typeof(ScoreProcessor), scoreProcessor)
|
||||
}
|
||||
};
|
||||
|
||||
iteration = 0;
|
||||
});
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
private void createCounter() => AddStep("Create counter", () =>
|
||||
{
|
||||
AddStep("Create counter", () =>
|
||||
dependencyContainer.Child = counter = new PerformancePointsCounter
|
||||
{
|
||||
iteration = 0;
|
||||
|
||||
Child = counter = new PerformancePointsCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(5),
|
||||
};
|
||||
});
|
||||
}
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(5),
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBasicCounting()
|
||||
{
|
||||
int previousValue = 0;
|
||||
createCounter();
|
||||
|
||||
AddAssert("counter displaying zero", () => counter.Current.Value == 0);
|
||||
|
||||
@ -86,6 +99,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCounterUpdatesWithJudgementsBeforeCreation()
|
||||
{
|
||||
AddRepeatStep("Add judgement", applyOneJudgement, 10);
|
||||
|
||||
createCounter();
|
||||
|
||||
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
|
||||
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1);
|
||||
}
|
||||
|
||||
private void applyOneJudgement()
|
||||
{
|
||||
var scoreInfo = gameplayState.Score.ScoreInfo;
|
||||
@ -94,13 +118,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
scoreInfo.Accuracy = 1;
|
||||
scoreInfo.Statistics[HitResult.Great] = iteration * 1000;
|
||||
|
||||
scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject
|
||||
lastJudgementResult.Value = new OsuJudgementResult(new HitObject
|
||||
{
|
||||
StartTime = iteration * 10000,
|
||||
}, new OsuJudgement())
|
||||
{
|
||||
Type = HitResult.Perfect,
|
||||
});
|
||||
};
|
||||
scoreProcessor.ApplyResult(lastJudgementResult.Value);
|
||||
|
||||
iteration++;
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
createPlayerTest(false, r =>
|
||||
{
|
||||
var beatmap = createTestBeatmap(r);
|
||||
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
||||
beatmap.BeatmapInfo.OnlineID = null;
|
||||
return beatmap;
|
||||
});
|
||||
|
||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Id = 39828,
|
||||
Username = @"WubWoofWolf",
|
||||
}
|
||||
}.CreateScoreInfo(rulesets);
|
||||
}.CreateScoreInfo(rulesets, CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo);
|
||||
}
|
||||
|
||||
private class TestReplayDownloadButton : ReplayDownloadButton
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
|
||||
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1;
|
||||
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1;
|
||||
});
|
||||
}
|
||||
|
||||
|
108
osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs
Normal file
108
osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs
Normal file
@ -0,0 +1,108 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneUnstableRateCounter : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(ScoreProcessor))]
|
||||
private TestScoreProcessor scoreProcessor = new TestScoreProcessor();
|
||||
|
||||
private readonly OsuHitWindows hitWindows = new OsuHitWindows();
|
||||
|
||||
private UnstableRateCounter counter;
|
||||
|
||||
private double prev;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUp()
|
||||
{
|
||||
AddStep("Reset Score Processor", () => scoreProcessor.Reset());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("Create Display", recreateDisplay);
|
||||
|
||||
// Needs multiples 2 by the nature of UR, and went for 4 to be safe.
|
||||
// Creates a 250 UR by placing a +25ms then a -25ms judgement, which then results in a 250 UR
|
||||
AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4);
|
||||
|
||||
AddUntilStep("UR = 250", () => counter.Current.Value == 250.0);
|
||||
|
||||
AddRepeatStep("Revert UR", () =>
|
||||
{
|
||||
scoreProcessor.RevertResult(
|
||||
new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement())
|
||||
{
|
||||
TimeOffset = 25,
|
||||
Type = HitResult.Perfect,
|
||||
});
|
||||
}, 4);
|
||||
|
||||
AddUntilStep("UR is 0", () => counter.Current.Value == 0.0);
|
||||
AddUntilStep("Counter is invalid", () => counter.Child.Alpha == 0.3f);
|
||||
|
||||
//Sets a UR of 0 by creating 10 10ms offset judgements. Since average = offset, UR = 0
|
||||
AddRepeatStep("Set UR to 0", () => applyJudgement(10, false), 10);
|
||||
//Applies a UR of 100 by creating 10 -10ms offset judgements. At the 10th judgement, offset should be 100.
|
||||
AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCounterReceivesJudgementsBeforeCreation()
|
||||
{
|
||||
AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4);
|
||||
|
||||
AddStep("Create Display", recreateDisplay);
|
||||
|
||||
AddUntilStep("UR = 250", () => counter.Current.Value == 250.0);
|
||||
}
|
||||
|
||||
private void recreateDisplay()
|
||||
{
|
||||
Clear();
|
||||
|
||||
Add(counter = new UnstableRateCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(5),
|
||||
});
|
||||
}
|
||||
|
||||
private void applyJudgement(double offsetMs, bool alt)
|
||||
{
|
||||
double placement = offsetMs;
|
||||
|
||||
if (alt)
|
||||
{
|
||||
placement = prev > 0 ? -offsetMs : offsetMs;
|
||||
prev = placement;
|
||||
}
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement())
|
||||
{
|
||||
TimeOffset = placement,
|
||||
Type = HitResult.Perfect,
|
||||
});
|
||||
}
|
||||
|
||||
private class TestScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public void Reset() => base.Reset(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
StarDifficulty = 2.5
|
||||
StarRating = 2.5
|
||||
}
|
||||
}.BeatmapInfo,
|
||||
}
|
||||
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
StarDifficulty = 2.5,
|
||||
StarRating = 2.5,
|
||||
Metadata =
|
||||
{
|
||||
Artist = "very very very very very very very very very long artist",
|
||||
@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
StarDifficulty = 2.5
|
||||
StarRating = 2.5
|
||||
}
|
||||
}.BeatmapInfo,
|
||||
}
|
||||
@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
StarDifficulty = 4.5
|
||||
StarRating = 4.5
|
||||
}
|
||||
}.BeatmapInfo,
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
|
||||
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
|
||||
importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1;
|
||||
importedBeatmapId = importedBeatmap.OnlineID ?? -1;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
foreach (int user in users)
|
||||
{
|
||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0);
|
||||
multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true));
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
foreach (int user in users)
|
||||
{
|
||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0);
|
||||
var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true);
|
||||
|
||||
roomUser.MatchState = new TeamVersusUserState
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
beatmaps.Add(new BeatmapInfo
|
||||
{
|
||||
Ruleset = rulesets.GetRuleset(i % 4),
|
||||
OnlineBeatmapID = beatmapId,
|
||||
OnlineID = beatmapId,
|
||||
Length = length,
|
||||
BPM = bpm,
|
||||
BaseDifficulty = new BeatmapDifficulty()
|
||||
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
manager.Import(new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = 10,
|
||||
OnlineID = 10,
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
|
@ -54,8 +54,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
beatmaps.Add(new BeatmapInfo
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
OnlineBeatmapID = beatmapId,
|
||||
Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||
OnlineID = beatmapId,
|
||||
DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||
Length = length,
|
||||
BPM = bpm,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
manager.Import(new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = 10,
|
||||
OnlineID = 10,
|
||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
|
@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
SelectedRoom.Value.Playlist.AddRange(new[]
|
||||
{
|
||||
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } },
|
||||
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } },
|
||||
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = min } } },
|
||||
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = max } } },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
@ -97,16 +98,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
|
||||
AddStep("add another user", () => client.AddUser(new APIUser { Username = "otheruser", Id = 44 }));
|
||||
|
||||
AddStep("press button", () =>
|
||||
AddStep("press own button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType<TeamDisplay>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1);
|
||||
|
||||
AddStep("press button", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("press own button again", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
|
||||
|
||||
AddStep("press other user's button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType<TeamDisplay>().ElementAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("user still on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -127,9 +136,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
|
||||
|
||||
AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam == null));
|
||||
|
||||
AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus));
|
||||
|
||||
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||
|
||||
AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam != null));
|
||||
}
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
|
@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526);
|
||||
AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526);
|
||||
}
|
||||
|
||||
public class DialogBlockingScreen : OsuScreen
|
||||
|
@ -107,20 +107,20 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
imported = Game.BeatmapManager.Import(new BeatmapSetInfo
|
||||
{
|
||||
Hash = Guid.NewGuid().ToString(),
|
||||
OnlineBeatmapSetID = i,
|
||||
OnlineID = i,
|
||||
Metadata = metadata,
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = i * 1024,
|
||||
OnlineID = i * 1024,
|
||||
Metadata = metadata,
|
||||
BaseDifficulty = difficulty,
|
||||
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = i * 2048,
|
||||
OnlineID = i * 2048,
|
||||
Metadata = metadata,
|
||||
BaseDifficulty = difficulty,
|
||||
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
|
||||
@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
private void presentSecondDifficultyAndConfirm(Func<BeatmapSetInfo> getImport, int importedID)
|
||||
{
|
||||
Predicate<BeatmapInfo> pred = b => b.OnlineBeatmapID == importedID * 2048;
|
||||
Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 2048;
|
||||
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
|
||||
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
|
||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == importedID * 2048);
|
||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID == importedID * 2048);
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID);
|
||||
}
|
||||
}
|
||||
|
@ -39,20 +39,20 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo
|
||||
{
|
||||
Hash = Guid.NewGuid().ToString(),
|
||||
OnlineBeatmapSetID = 1,
|
||||
OnlineID = 1,
|
||||
Metadata = metadata,
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = 1 * 1024,
|
||||
OnlineID = 1 * 1024,
|
||||
Metadata = metadata,
|
||||
BaseDifficulty = difficulty,
|
||||
Ruleset = new OsuRuleset().RulesetInfo
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = 1 * 2048,
|
||||
OnlineID = 1 * 2048,
|
||||
Metadata = metadata,
|
||||
BaseDifficulty = difficulty,
|
||||
Ruleset = new OsuRuleset().RulesetInfo
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
|
||||
AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
|
||||
|
||||
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526));
|
||||
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526));
|
||||
AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);
|
||||
|
||||
createButtonWithBeatmap(createSoleily());
|
||||
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("remove soleily", () =>
|
||||
{
|
||||
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526);
|
||||
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526);
|
||||
|
||||
if (beatmap != null) beatmaps.Delete(beatmap);
|
||||
});
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
|
||||
{
|
||||
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
|
||||
BeatmapInfo = { OnlineID = hasOnlineId ? 1234 : (int?)null }
|
||||
});
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
|
@ -121,8 +121,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
|
||||
|
||||
// intentionally increment online IDs to clash with import below.
|
||||
beatmap.BeatmapInfo.OnlineBeatmapID++;
|
||||
beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++;
|
||||
beatmap.BeatmapInfo.OnlineID++;
|
||||
beatmap.BeatmapInfo.BeatmapSet.OnlineID++;
|
||||
|
||||
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
|
||||
});
|
||||
|
@ -337,7 +337,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
public UnrankedSoloResultsScreen(ScoreInfo score)
|
||||
: base(score, true)
|
||||
{
|
||||
Score.BeatmapInfo.OnlineBeatmapID = 0;
|
||||
Score.BeatmapInfo.OnlineID = 0;
|
||||
Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
OverallDifficulty = 5.7f,
|
||||
ApproachRate = 3.5f
|
||||
},
|
||||
StarDifficulty = 4.5f
|
||||
StarRating = 4.5f
|
||||
};
|
||||
|
||||
[Test]
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
OverallDifficulty = 4.5f,
|
||||
ApproachRate = 3.1f
|
||||
},
|
||||
StarDifficulty = 8
|
||||
StarRating = 8
|
||||
});
|
||||
|
||||
AddAssert("first bar text is Key Count", () => advancedStats.ChildrenOfType<SpriteText>().First().Text == "Key Count");
|
||||
|
@ -435,8 +435,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var set = createTestBeatmapSet(i);
|
||||
set.Beatmaps[0].StarDifficulty = 3 - i;
|
||||
set.Beatmaps[2].StarDifficulty = 6 + i;
|
||||
set.Beatmaps[0].StarRating = 3 - i;
|
||||
set.Beatmaps[2].StarRating = 6 + i;
|
||||
sets.Add(set);
|
||||
}
|
||||
|
||||
@ -684,9 +684,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
set.Beatmaps.Add(new BeatmapInfo
|
||||
{
|
||||
Version = $"Stars: {i}",
|
||||
DifficultyName = $"Stars: {i}",
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
StarDifficulty = i,
|
||||
StarRating = i,
|
||||
});
|
||||
}
|
||||
|
||||
@ -838,7 +838,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
return new BeatmapSetInfo
|
||||
{
|
||||
ID = id,
|
||||
OnlineBeatmapSetID = id,
|
||||
OnlineID = id,
|
||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
@ -867,9 +867,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
yield return new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = id++ * 10,
|
||||
Version = version,
|
||||
StarDifficulty = diff,
|
||||
OnlineID = id++ * 10,
|
||||
DifficultyName = version,
|
||||
StarRating = diff,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
@ -884,7 +884,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var toReturn = new BeatmapSetInfo
|
||||
{
|
||||
ID = id,
|
||||
OnlineBeatmapSetID = id,
|
||||
OnlineID = id,
|
||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
@ -900,11 +900,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
toReturn.Beatmaps.Add(new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = b * 10,
|
||||
OnlineID = b * 10,
|
||||
Path = $"extra{b}.osu",
|
||||
Version = $"Extra {b}",
|
||||
DifficultyName = $"Extra {b}",
|
||||
Ruleset = rulesets.GetRuleset((b - 1) % 4),
|
||||
StarDifficulty = 2,
|
||||
StarRating = 2,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = 3.5f,
|
||||
|
@ -198,8 +198,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Title = $"{ruleset.ShortName}Title"
|
||||
},
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = 6,
|
||||
Version = $"{ruleset.ShortName}Version",
|
||||
StarRating = 6,
|
||||
DifficultyName = $"{ruleset.ShortName}Version",
|
||||
BaseDifficulty = new BeatmapDifficulty()
|
||||
},
|
||||
HitObjects = objects
|
||||
@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Source = "Verrrrry long Source",
|
||||
Title = "Verrrrry long Title"
|
||||
},
|
||||
Version = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version",
|
||||
DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version",
|
||||
Status = BeatmapSetOnlineStatus.Graveyard,
|
||||
},
|
||||
};
|
||||
|
@ -388,7 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
leaderboard.BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = 1113057,
|
||||
OnlineID = 1113057,
|
||||
Status = status,
|
||||
};
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
Title = title,
|
||||
},
|
||||
Version = version,
|
||||
StarDifficulty = RNG.NextDouble(0, 10),
|
||||
DifficultyName = version,
|
||||
StarRating = RNG.NextDouble(0, 10),
|
||||
}
|
||||
}));
|
||||
}
|
||||
@ -64,8 +64,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
Title = "Heavy beatmap",
|
||||
},
|
||||
Version = "10k objects",
|
||||
StarDifficulty = 99.99f,
|
||||
DifficultyName = "10k objects",
|
||||
StarRating = 99.99f,
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -180,16 +180,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var beatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Hash = Guid.NewGuid().ToString(),
|
||||
OnlineBeatmapSetID = importID,
|
||||
OnlineID = importID,
|
||||
Metadata = metadata,
|
||||
Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = importID * 1024 + difficultyIndex,
|
||||
OnlineID = importID * 1024 + difficultyIndex,
|
||||
Metadata = metadata,
|
||||
BaseDifficulty = new BeatmapDifficulty(),
|
||||
Ruleset = ruleset,
|
||||
StarDifficulty = difficultyIndex + 1,
|
||||
Version = $"SR{difficultyIndex + 1}"
|
||||
StarRating = difficultyIndex + 1,
|
||||
DifficultyName = $"SR{difficultyIndex + 1}"
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
@ -205,8 +205,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
|
||||
AddUntilStep("recommended beatmap displayed", () =>
|
||||
{
|
||||
int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineBeatmapID;
|
||||
return Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == expectedID;
|
||||
int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineID;
|
||||
return Game.Beatmap.Value.BeatmapInfo.OnlineID == expectedID;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -507,13 +507,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0);
|
||||
});
|
||||
|
||||
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID);
|
||||
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
|
||||
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID);
|
||||
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID);
|
||||
|
||||
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
|
||||
|
||||
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
|
||||
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
|
||||
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineID == target.OnlineID);
|
||||
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineID == target.OnlineID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -544,8 +544,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
|
||||
|
||||
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID);
|
||||
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
|
||||
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID);
|
||||
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID);
|
||||
|
||||
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo");
|
||||
|
||||
@ -918,8 +918,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
beatmaps.Add(new BeatmapInfo
|
||||
{
|
||||
Ruleset = getRuleset(),
|
||||
OnlineBeatmapID = beatmapId,
|
||||
Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||
OnlineID = beatmapId,
|
||||
DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||
Length = length,
|
||||
BPM = bpm,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
@ -931,7 +931,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
return new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = setId,
|
||||
OnlineID = setId,
|
||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Username = "TestAuthor"
|
||||
},
|
||||
},
|
||||
Version = "Insane"
|
||||
DifficultyName = "Insane"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -1,13 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Mixing;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,27 +14,26 @@ namespace osu.Game.Audio
|
||||
{
|
||||
public class PreviewTrackManager : Component
|
||||
{
|
||||
private readonly IAdjustableAudioComponent mainTrackAdjustments;
|
||||
|
||||
private readonly BindableDouble muteBindable = new BindableDouble();
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audio { get; set; }
|
||||
|
||||
private PreviewTrackStore trackStore;
|
||||
private ITrackStore trackStore;
|
||||
|
||||
protected TrackManagerPreviewTrack CurrentTrack;
|
||||
|
||||
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(OsuGameBase.GLOBAL_TRACK_VOLUME_ADJUST);
|
||||
public PreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments)
|
||||
{
|
||||
this.mainTrackAdjustments = mainTrackAdjustments;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audioManager)
|
||||
{
|
||||
// this is a temporary solution to get around muting ourselves.
|
||||
// todo: update this once we have a BackgroundTrackManager or similar.
|
||||
trackStore = new PreviewTrackStore(audioManager.TrackMixer, new OnlineStore());
|
||||
|
||||
audio.AddItem(trackStore);
|
||||
trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);
|
||||
trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack);
|
||||
trackStore = audioManager.GetTrackStore(new OnlineStore());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -55,7 +49,7 @@ namespace osu.Game.Audio
|
||||
{
|
||||
CurrentTrack?.Stop();
|
||||
CurrentTrack = track;
|
||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
mainTrackAdjustments.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
});
|
||||
|
||||
track.Stopped += () => Schedule(() =>
|
||||
@ -64,7 +58,7 @@ namespace osu.Game.Audio
|
||||
return;
|
||||
|
||||
CurrentTrack = null;
|
||||
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
mainTrackAdjustments.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
});
|
||||
|
||||
return track;
|
||||
@ -116,52 +110,5 @@ namespace osu.Game.Audio
|
||||
|
||||
protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo.OnlineID}.mp3");
|
||||
}
|
||||
|
||||
private class PreviewTrackStore : AudioCollectionManager<AdjustableAudioComponent>, ITrackStore
|
||||
{
|
||||
private readonly AudioMixer defaultMixer;
|
||||
private readonly IResourceStore<byte[]> store;
|
||||
|
||||
internal PreviewTrackStore(AudioMixer defaultMixer, IResourceStore<byte[]> store)
|
||||
{
|
||||
this.defaultMixer = defaultMixer;
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public Track GetVirtual(double length = double.PositiveInfinity)
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}");
|
||||
|
||||
var track = new TrackVirtual(length);
|
||||
AddItem(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
public Track Get(string name)
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}");
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return null;
|
||||
|
||||
var dataStream = store.GetStream(name);
|
||||
|
||||
if (dataStream == null)
|
||||
return null;
|
||||
|
||||
// Todo: This is quite unsafe. TrackBass shouldn't be exposed as public.
|
||||
Track track = new TrackBass(dataStream);
|
||||
|
||||
defaultMixer.Add(track);
|
||||
AddItem(track);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
public Task<Track> GetAsync(string name) => Task.Run(() => Get(name));
|
||||
|
||||
public Stream GetStream(string name) => store.GetStream(name);
|
||||
|
||||
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps
|
||||
Title = @"Unknown",
|
||||
AuthorString = @"Unknown Creator",
|
||||
},
|
||||
Version = @"Normal",
|
||||
DifficultyName = @"Normal",
|
||||
BaseDifficulty = Difficulty,
|
||||
};
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ namespace osu.Game.Beatmaps
|
||||
catch (BeatmapInvalidForRulesetException e)
|
||||
{
|
||||
if (rulesetInfo.Equals(beatmapInfo.Ruleset))
|
||||
Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineBeatmapID} to the beatmap's default ruleset ({beatmapInfo.Ruleset}).");
|
||||
Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset}).");
|
||||
|
||||
return new StarDifficulty();
|
||||
}
|
||||
|
@ -23,13 +23,14 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public int BeatmapVersion;
|
||||
|
||||
private int? onlineBeatmapID;
|
||||
private int? onlineID;
|
||||
|
||||
[JsonProperty("id")]
|
||||
public int? OnlineBeatmapID
|
||||
[Column("OnlineBeatmapID")]
|
||||
public int? OnlineID
|
||||
{
|
||||
get => onlineBeatmapID;
|
||||
set => onlineBeatmapID = value > 0 ? value : null;
|
||||
get => onlineID;
|
||||
set => onlineID = value > 0 ? value : null;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
@ -134,12 +135,12 @@ namespace osu.Game.Beatmaps
|
||||
public double TimelineZoom { get; set; }
|
||||
|
||||
// Metadata
|
||||
public string Version { get; set; }
|
||||
|
||||
private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]";
|
||||
[Column("Version")]
|
||||
public string DifficultyName { get; set; }
|
||||
|
||||
[JsonProperty("difficulty_rating")]
|
||||
public double StarDifficulty { get; set; }
|
||||
[Column("StarDifficulty")]
|
||||
public double StarRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently only populated for beatmap deletion. Use <see cref="ScoreManager"/> to query scores.
|
||||
@ -147,7 +148,7 @@ namespace osu.Game.Beatmaps
|
||||
public List<ScoreInfo> Scores { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty);
|
||||
public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating);
|
||||
|
||||
public override string ToString() => this.GetDisplayTitle();
|
||||
|
||||
@ -176,15 +177,12 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
#region Implementation of IHasOnlineID
|
||||
|
||||
public int OnlineID => OnlineBeatmapID ?? -1;
|
||||
int IHasOnlineID<int>.OnlineID => OnlineID ?? -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IBeatmapInfo
|
||||
|
||||
[JsonIgnore]
|
||||
string IBeatmapInfo.DifficultyName => Version;
|
||||
|
||||
[JsonIgnore]
|
||||
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata();
|
||||
|
||||
@ -197,9 +195,6 @@ namespace osu.Game.Beatmaps
|
||||
[JsonIgnore]
|
||||
IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
|
||||
|
||||
[JsonIgnore]
|
||||
double IBeatmapInfo.StarRating => StarDifficulty;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// A user-presentable display title representing this beatmap.
|
||||
/// </summary>
|
||||
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata} {getVersionString(beatmapInfo)}".Trim();
|
||||
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata.GetDisplayTitle()} {getVersionString(beatmapInfo)}".Trim();
|
||||
|
||||
/// <summary>
|
||||
/// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
|
||||
|
@ -10,6 +10,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Mixing;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@ -30,18 +32,23 @@ namespace osu.Game.Beatmaps
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class BeatmapManager : IModelDownloader<IBeatmapSetInfo>, IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, BeatmapSetFileInfo>, IModelImporter<BeatmapSetInfo>, IWorkingBeatmapCache, IDisposable
|
||||
{
|
||||
public ITrackStore BeatmapTrackStore { get; }
|
||||
|
||||
private readonly BeatmapModelManager beatmapModelManager;
|
||||
private readonly BeatmapModelDownloader beatmapModelDownloader;
|
||||
|
||||
private readonly WorkingBeatmapCache workingBeatmapCache;
|
||||
private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue;
|
||||
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null,
|
||||
WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false, AudioMixer mainTrackMixer = null)
|
||||
{
|
||||
var userResources = new FileStore(contextFactory, storage).Store;
|
||||
|
||||
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
||||
|
||||
beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host);
|
||||
beatmapModelDownloader = CreateBeatmapModelDownloader(beatmapModelManager, api, host);
|
||||
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, resources, new FileStore(contextFactory, storage).Store, defaultBeatmap, host);
|
||||
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
||||
|
||||
workingBeatmapCache.BeatmapManager = beatmapModelManager;
|
||||
beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache;
|
||||
@ -58,8 +65,10 @@ namespace osu.Game.Beatmaps
|
||||
return new BeatmapModelDownloader(modelManager, api, host);
|
||||
}
|
||||
|
||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap defaultBeatmap, GameHost host) =>
|
||||
new WorkingBeatmapCache(audioManager, resources, storage, defaultBeatmap, host);
|
||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap defaultBeatmap, GameHost host)
|
||||
{
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||
}
|
||||
|
||||
protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) =>
|
||||
new BeatmapModelManager(storage, contextFactory, rulesets, host);
|
||||
|
@ -27,8 +27,12 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo)
|
||||
{
|
||||
string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author})";
|
||||
return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim();
|
||||
string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $" ({metadataInfo.Author.Username})";
|
||||
|
||||
string artist = string.IsNullOrEmpty(metadataInfo.Artist) ? "unknown artist" : metadataInfo.Artist;
|
||||
string title = string.IsNullOrEmpty(metadataInfo.Title) ? "unknown title" : metadataInfo.Title;
|
||||
|
||||
return $"{artist} - {title}{author}".Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -94,17 +94,17 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
validateOnlineIds(beatmapSet);
|
||||
|
||||
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
|
||||
bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0);
|
||||
|
||||
if (OnlineLookupQueue != null)
|
||||
await OnlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
|
||||
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
|
||||
if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0))
|
||||
{
|
||||
if (beatmapSet.OnlineBeatmapSetID != null)
|
||||
if (beatmapSet.OnlineID != null)
|
||||
{
|
||||
beatmapSet.OnlineBeatmapSetID = null;
|
||||
beatmapSet.OnlineID = null;
|
||||
LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs");
|
||||
}
|
||||
}
|
||||
@ -116,27 +116,27 @@ namespace osu.Game.Beatmaps
|
||||
throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}.");
|
||||
|
||||
// check if a set already exists with the same online id, delete if it does.
|
||||
if (beatmapSet.OnlineBeatmapSetID != null)
|
||||
if (beatmapSet.OnlineID != null)
|
||||
{
|
||||
var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID);
|
||||
var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineID == beatmapSet.OnlineID);
|
||||
|
||||
if (existingSetWithSameOnlineID != null)
|
||||
{
|
||||
Delete(existingSetWithSameOnlineID);
|
||||
|
||||
// in order to avoid a unique key constraint, immediately remove the online ID from the previous set.
|
||||
existingSetWithSameOnlineID.OnlineBeatmapSetID = null;
|
||||
existingSetWithSameOnlineID.OnlineID = null;
|
||||
foreach (var b in existingSetWithSameOnlineID.Beatmaps)
|
||||
b.OnlineBeatmapID = null;
|
||||
b.OnlineID = null;
|
||||
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted.");
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineID}). It has been deleted.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateOnlineIds(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
|
||||
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID.HasValue).Select(b => b.OnlineID).ToList();
|
||||
|
||||
// ensure all IDs are unique
|
||||
if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1))
|
||||
@ -147,7 +147,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
// find any existing beatmaps in the database that have matching online ids
|
||||
var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList();
|
||||
var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineID)).ToList();
|
||||
|
||||
if (existingBeatmaps.Count > 0)
|
||||
{
|
||||
@ -162,7 +162,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null);
|
||||
void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps
|
||||
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
|
||||
|
||||
// metadata may have changed; update the path with the standard format.
|
||||
beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu");
|
||||
beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu");
|
||||
|
||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||
|
||||
@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps
|
||||
if (!base.CanSkipImport(existing, import))
|
||||
return false;
|
||||
|
||||
return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null);
|
||||
return existing.Beatmaps.Any(b => b.OnlineID != null);
|
||||
}
|
||||
|
||||
protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import)
|
||||
@ -250,11 +250,11 @@ namespace osu.Game.Beatmaps
|
||||
if (!base.CanReuseExisting(existing, import))
|
||||
return false;
|
||||
|
||||
var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);
|
||||
var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);
|
||||
var existingIds = existing.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i);
|
||||
var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i);
|
||||
|
||||
// force re-import if we are not in a sane state.
|
||||
return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds);
|
||||
return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -349,7 +349,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable<BeatmapSetInfo> items)
|
||||
=> base.CheckLocalAvailability(model, items)
|
||||
|| (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID));
|
||||
|| (model.OnlineID != null && items.Any(b => b.OnlineID == model.OnlineID));
|
||||
|
||||
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
||||
{
|
||||
@ -368,7 +368,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
return new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
|
||||
OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID,
|
||||
Beatmaps = new List<BeatmapInfo>(),
|
||||
Metadata = beatmap.Metadata,
|
||||
DateAdded = DateTimeOffset.UtcNow
|
||||
@ -407,7 +407,7 @@ namespace osu.Game.Beatmaps
|
||||
beatmap.BeatmapInfo.Ruleset = ruleset;
|
||||
|
||||
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
||||
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
|
||||
beatmap.BeatmapInfo.StarRating = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
|
||||
beatmap.BeatmapInfo.Length = calculateLength(beatmap);
|
||||
beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength();
|
||||
|
||||
|
@ -84,8 +84,8 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
beatmapInfo.Status = res.Status;
|
||||
beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None;
|
||||
beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||
beatmapInfo.OnlineBeatmapID = res.OnlineID;
|
||||
beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID;
|
||||
beatmapInfo.OnlineID = res.OnlineID;
|
||||
|
||||
if (beatmapInfo.Metadata != null)
|
||||
beatmapInfo.Metadata.AuthorID = res.AuthorID;
|
||||
@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
void fail(Exception e)
|
||||
{
|
||||
beatmapInfo.OnlineBeatmapID = null;
|
||||
beatmapInfo.OnlineID = null;
|
||||
logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})");
|
||||
}
|
||||
}
|
||||
@ -161,7 +161,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
|
||||
&& string.IsNullOrEmpty(beatmapInfo.Path)
|
||||
&& beatmapInfo.OnlineBeatmapID == null)
|
||||
&& beatmapInfo.OnlineID == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
@ -172,10 +172,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
using (var cmd = db.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path";
|
||||
cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";
|
||||
|
||||
cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash));
|
||||
cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmapInfo.OnlineBeatmapID ?? (object)DBNull.Value));
|
||||
cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID ?? (object)DBNull.Value));
|
||||
cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path));
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
@ -186,8 +186,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
beatmapInfo.Status = status;
|
||||
beatmapInfo.BeatmapSet.Status = status;
|
||||
beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0);
|
||||
beatmapInfo.OnlineBeatmapID = reader.GetInt32(1);
|
||||
beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0);
|
||||
beatmapInfo.OnlineID = reader.GetInt32(1);
|
||||
|
||||
if (beatmapInfo.Metadata != null)
|
||||
beatmapInfo.Metadata.AuthorID = reader.GetInt32(3);
|
||||
|
@ -16,12 +16,13 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
private int? onlineBeatmapSetID;
|
||||
private int? onlineID;
|
||||
|
||||
public int? OnlineBeatmapSetID
|
||||
[Column("OnlineBeatmapSetID")]
|
||||
public int? OnlineID
|
||||
{
|
||||
get => onlineBeatmapSetID;
|
||||
set => onlineBeatmapSetID = value > 0 ? value : null;
|
||||
get => onlineID;
|
||||
set => onlineID = value > 0 ? value : null;
|
||||
}
|
||||
|
||||
public DateTimeOffset DateAdded { get; set; }
|
||||
@ -38,7 +39,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// The maximum star difficulty of all beatmaps in this set.
|
||||
/// </summary>
|
||||
public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0;
|
||||
public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarRating) ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum playable length in milliseconds of all beatmaps in this set.
|
||||
@ -74,8 +75,8 @@ namespace osu.Game.Beatmaps
|
||||
if (ID != 0 && other.ID != 0)
|
||||
return ID == other.ID;
|
||||
|
||||
if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue)
|
||||
return OnlineBeatmapSetID == other.OnlineBeatmapSetID;
|
||||
if (OnlineID.HasValue && other.OnlineID.HasValue)
|
||||
return OnlineID == other.OnlineID;
|
||||
|
||||
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
|
||||
return Hash == other.Hash;
|
||||
@ -85,7 +86,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
#region Implementation of IHasOnlineID
|
||||
|
||||
public int OnlineID => OnlineBeatmapSetID ?? -1;
|
||||
int IHasOnlineID<int>.OnlineID => OnlineID ?? -1;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b =>
|
||||
{
|
||||
double difference = b.StarDifficulty - recommendation;
|
||||
double difference = b.StarRating - recommendation;
|
||||
return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder
|
||||
}).FirstOrDefault();
|
||||
|
||||
|
@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"Version":
|
||||
beatmap.BeatmapInfo.Version = pair.Value;
|
||||
beatmap.BeatmapInfo.DifficultyName = pair.Value;
|
||||
break;
|
||||
|
||||
case @"Source":
|
||||
@ -263,11 +263,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case @"BeatmapID":
|
||||
beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value);
|
||||
beatmap.BeatmapInfo.OnlineID = Parsing.ParseInt(pair.Value);
|
||||
break;
|
||||
|
||||
case @"BeatmapSetID":
|
||||
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) };
|
||||
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineID = Parsing.ParseInt(pair.Value) };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -130,11 +130,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
|
||||
if (!string.IsNullOrEmpty(beatmap.Metadata.ArtistUnicode)) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.Author.Username}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}"));
|
||||
if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
|
||||
if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
|
||||
if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}"));
|
||||
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}"));
|
||||
if (beatmap.BeatmapInfo.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}"));
|
||||
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineID}"));
|
||||
}
|
||||
|
||||
private void handleDifficulty(TextWriter writer)
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Beatmaps
|
||||
[CanBeNull]
|
||||
private readonly GameHost host;
|
||||
|
||||
public WorkingBeatmapCache([NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> files, WorkingBeatmap defaultBeatmap = null, GameHost host = null)
|
||||
public WorkingBeatmapCache(ITrackStore trackStore, AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> files, WorkingBeatmap defaultBeatmap = null, GameHost host = null)
|
||||
{
|
||||
DefaultBeatmap = defaultBeatmap;
|
||||
|
||||
@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps
|
||||
this.host = host;
|
||||
this.files = files;
|
||||
largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(files));
|
||||
trackStore = audioManager.GetTrackStore(files);
|
||||
this.trackStore = trackStore;
|
||||
}
|
||||
|
||||
public void Invalidate(BeatmapSetInfo info)
|
||||
|
@ -15,6 +15,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.IPC;
|
||||
@ -192,7 +193,7 @@ namespace osu.Game.Database
|
||||
else
|
||||
{
|
||||
notification.CompletionText = imported.Count == 1
|
||||
? $"Imported {imported.First().Value}!"
|
||||
? $"Imported {imported.First().Value.GetDisplayString()}!"
|
||||
: $"Imported {imported.Count} {HumanisedModelName}s!";
|
||||
|
||||
if (imported.Count > 0 && PostImport != null)
|
||||
@ -263,7 +264,7 @@ namespace osu.Game.Database
|
||||
model = CreateModel(archive);
|
||||
|
||||
if (model == null)
|
||||
return Task.FromResult<ILive<TModel>>(new EntityFrameworkLive<TModel>(null));
|
||||
return Task.FromResult<ILive<TModel>>(null);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
|
@ -7,6 +7,8 @@ using System.Threading.Tasks;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
/// <summary>
|
||||
@ -26,7 +28,7 @@ namespace osu.Game.Database
|
||||
/// <param name="lowPriority">Whether this is a low priority import.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The imported model, if successful.</returns>
|
||||
Task<ILive<TModel>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
Task<ILive<TModel>?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Silently import an item from an <see cref="ArchiveReader"/>.
|
||||
@ -34,7 +36,7 @@ namespace osu.Game.Database
|
||||
/// <param name="archive">The archive to be imported.</param>
|
||||
/// <param name="lowPriority">Whether this is a low priority import.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
Task<ILive<TModel>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
Task<ILive<TModel>?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Silently import an item from a <typeparamref name="TModel"/>.
|
||||
@ -43,7 +45,7 @@ namespace osu.Game.Database
|
||||
/// <param name="archive">An optional archive to use for model population.</param>
|
||||
/// <param name="lowPriority">Whether this is a low priority import.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
Task<ILive<TModel>> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
Task<ILive<TModel>?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// A user displayable name for the model type associated with this manager.
|
||||
|
@ -47,10 +47,30 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
public ArchiveReader GetReader()
|
||||
{
|
||||
if (Stream != null)
|
||||
return new ZipArchiveReader(Stream, Path);
|
||||
return Stream != null
|
||||
? getReaderFrom(Stream)
|
||||
: getReaderFrom(Path);
|
||||
}
|
||||
|
||||
return getReaderFrom(Path);
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ArchiveReader"/> from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A seekable stream containing the archive content.</param>
|
||||
/// <returns>A reader giving access to the archive's content.</returns>
|
||||
private ArchiveReader getReaderFrom(Stream stream)
|
||||
{
|
||||
if (!(stream is MemoryStream memoryStream))
|
||||
{
|
||||
// This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out).
|
||||
byte[] buffer = new byte[stream.Length];
|
||||
stream.Read(buffer, 0, (int)stream.Length);
|
||||
memoryStream = new MemoryStream(buffer);
|
||||
}
|
||||
|
||||
if (ZipUtils.IsZipArchive(memoryStream))
|
||||
return new ZipArchiveReader(memoryStream, Path);
|
||||
|
||||
return new LegacyByteArrayReader(memoryStream.ToArray(), Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
@ -50,7 +51,7 @@ namespace osu.Game.Database
|
||||
|
||||
DownloadNotification notification = new DownloadNotification
|
||||
{
|
||||
Text = $"Downloading {request.Model}",
|
||||
Text = $"Downloading {request.Model.GetDisplayString()}",
|
||||
};
|
||||
|
||||
request.DownloadProgressed += progress =>
|
||||
|
@ -125,11 +125,11 @@ namespace osu.Game.Database
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineBeatmapID).IsUnique();
|
||||
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineID).IsUnique();
|
||||
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.MD5Hash);
|
||||
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.Hash);
|
||||
|
||||
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineBeatmapSetID).IsUnique();
|
||||
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineID).IsUnique();
|
||||
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending);
|
||||
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique();
|
||||
|
||||
|
@ -209,7 +209,13 @@ namespace osu.Game.Database
|
||||
|
||||
case 9:
|
||||
// Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well.
|
||||
var oldMetadata = migration.OldRealm.DynamicApi.All(getMappedOrOriginalName(typeof(RealmBeatmapMetadata)));
|
||||
string metadataClassName = getMappedOrOriginalName(typeof(RealmBeatmapMetadata));
|
||||
|
||||
// May be coming from a version before `RealmBeatmapMetadata` existed.
|
||||
if (!migration.OldRealm.Schema.TryFindObjectSchema(metadataClassName, out _))
|
||||
return;
|
||||
|
||||
var oldMetadata = migration.OldRealm.DynamicApi.All(metadataClassName);
|
||||
var newMetadata = migration.NewRealm.All<RealmBeatmapMetadata>();
|
||||
|
||||
int metadataCount = newMetadata.Count();
|
||||
|
61
osu.Game/Extensions/ModelExtensions.cs
Normal file
61
osu.Game/Extensions/ModelExtensions.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
{
|
||||
public static class ModelExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a user-facing string representing the <paramref name="model"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Non-interface types without special handling will fall back to <see cref="object.ToString()"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Warning: This method is _purposefully_ not called <c>GetDisplayTitle()</c> like the others, because otherwise
|
||||
/// extension method type inference rules cause this method to call itself and cause a stack overflow.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static string GetDisplayString(this object model)
|
||||
{
|
||||
string result = null;
|
||||
|
||||
switch (model)
|
||||
{
|
||||
case IBeatmapSetInfo beatmapSetInfo:
|
||||
result = beatmapSetInfo.Metadata?.GetDisplayTitle();
|
||||
break;
|
||||
|
||||
case IBeatmapInfo beatmapInfo:
|
||||
result = beatmapInfo.GetDisplayTitle();
|
||||
break;
|
||||
|
||||
case IBeatmapMetadataInfo metadataInfo:
|
||||
result = metadataInfo.GetDisplayTitle();
|
||||
break;
|
||||
|
||||
case IScoreInfo scoreInfo:
|
||||
result = scoreInfo.GetDisplayTitle();
|
||||
break;
|
||||
|
||||
case IRulesetInfo rulesetInfo:
|
||||
result = rulesetInfo.Name;
|
||||
break;
|
||||
|
||||
case IUser user:
|
||||
result = user.Username;
|
||||
break;
|
||||
}
|
||||
|
||||
// fallback in case none of the above happens to match.
|
||||
result ??= model?.ToString() ?? @"null";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -52,15 +52,18 @@ namespace osu.Game.Graphics
|
||||
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[]
|
||||
{
|
||||
(1.5f, Color4Extensions.FromHex("4fc0ff")),
|
||||
(0.1f, Color4Extensions.FromHex("aaaaaa")),
|
||||
(0.1f, Color4Extensions.FromHex("4290fb")),
|
||||
(1.25f, Color4Extensions.FromHex("4fc0ff")),
|
||||
(2.0f, Color4Extensions.FromHex("4fffd5")),
|
||||
(2.5f, Color4Extensions.FromHex("7cff4f")),
|
||||
(3.25f, Color4Extensions.FromHex("f6f05c")),
|
||||
(4.5f, Color4Extensions.FromHex("ff8068")),
|
||||
(6.0f, Color4Extensions.FromHex("ff3c71")),
|
||||
(7.0f, Color4Extensions.FromHex("6563de")),
|
||||
(8.0f, Color4Extensions.FromHex("18158e")),
|
||||
(8.0f, Color4.Black),
|
||||
(3.3f, Color4Extensions.FromHex("f6f05c")),
|
||||
(4.2f, Color4Extensions.FromHex("ff8068")),
|
||||
(4.9f, Color4Extensions.FromHex("ff4e6f")),
|
||||
(5.8f, Color4Extensions.FromHex("c645b8")),
|
||||
(6.7f, Color4Extensions.FromHex("6563de")),
|
||||
(7.7f, Color4Extensions.FromHex("18158e")),
|
||||
(9.0f, Color4.Black),
|
||||
}, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
|
||||
/// <summary>
|
||||
|
@ -7,7 +7,7 @@ using System.IO;
|
||||
namespace osu.Game.IO.Archives
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows reading a single file from the provided stream.
|
||||
/// Allows reading a single file from the provided byte array.
|
||||
/// </summary>
|
||||
public class LegacyByteArrayReader : ArchiveReader
|
||||
{
|
||||
|
@ -76,6 +76,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
|
||||
};
|
||||
|
||||
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
||||
@ -288,6 +289,9 @@ namespace osu.Game.Input.Bindings
|
||||
ToggleChatFocus,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))]
|
||||
EditorCycleGridDisplayMode
|
||||
EditorCycleGridDisplayMode,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))]
|
||||
EditorTestGameplay
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +169,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode");
|
||||
|
||||
/// <summary>
|
||||
/// "Test gameplay"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTestGameplay => new TranslatableString(getKey(@"editor_test_gameplay"), @"Test gameplay");
|
||||
|
||||
/// <summary>
|
||||
/// "Hold for HUD"
|
||||
/// </summary>
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Online
|
||||
return;
|
||||
|
||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||
var beatmapSetInfo = new BeatmapSetInfo { OnlineBeatmapSetID = TrackedItem.OnlineID };
|
||||
var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID };
|
||||
|
||||
if (Manager.IsAvailableLocally(beatmapSetInfo))
|
||||
UpdateState(DownloadState.LocallyAvailable);
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat
|
||||
break;
|
||||
}
|
||||
|
||||
string beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString();
|
||||
string beatmapString = beatmapInfo.OnlineID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString();
|
||||
|
||||
channelManager.PostMessage($"is {verb} {beatmapString}", true, target);
|
||||
Expire();
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
@ -161,9 +162,10 @@ namespace osu.Game.Online
|
||||
builder.AddNewtonsoftJsonProtocol(options =>
|
||||
{
|
||||
options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
// TODO: This should only be required to be `TypeNameHandling.Auto`.
|
||||
// See usage in osu-server-spectator for further documentation as to why this is required.
|
||||
options.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.All;
|
||||
options.PayloadSerializerSettings.Converters = new List<JsonConverter>
|
||||
{
|
||||
new SignalRDerivedTypeWorkaroundJsonConverter(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
// TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
|
||||
public abstract class MatchRoomState
|
||||
{
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
// TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
|
||||
public abstract class MatchUserState
|
||||
{
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
Value = new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = Room.Settings.BeatmapID,
|
||||
OnlineID = Room.Settings.BeatmapID,
|
||||
MD5Hash = Room.Settings.BeatmapChecksum
|
||||
}
|
||||
},
|
||||
|
@ -113,7 +113,7 @@ namespace osu.Game.Online.Rooms
|
||||
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
|
||||
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||
|
||||
return beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null;
|
||||
return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user