mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 21:12:55 +08:00
Merge branch 'master' into move-already-placed-objects-when-adjusting-offset-bpm
This commit is contained in:
commit
f176383dd0
@ -21,7 +21,7 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2024.517.0",
|
||||
"version": "2024.802.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
|
@ -196,6 +196,9 @@ csharp_style_prefer_switch_expression = false:none
|
||||
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
|
||||
#Style - C# 12 features
|
||||
csharp_style_prefer_primary_constructors = false
|
||||
|
||||
[*.{yaml,yml}]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
|
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -64,10 +64,11 @@ jobs:
|
||||
matrix:
|
||||
os:
|
||||
- { prettyname: Windows, fullname: windows-latest }
|
||||
- { prettyname: macOS, fullname: macos-latest }
|
||||
# macOS runner performance has gotten unbearably slow so let's turn them off temporarily.
|
||||
# - { prettyname: macOS, fullname: macos-latest }
|
||||
- { prettyname: Linux, fullname: ubuntu-latest }
|
||||
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -120,9 +121,7 @@ jobs:
|
||||
|
||||
build-only-ios:
|
||||
name: Build only (iOS)
|
||||
# `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3.
|
||||
# TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images)
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -136,8 +135,5 @@ jobs:
|
||||
- name: Install .NET Workloads
|
||||
run: dotnet workload install maui-ios
|
||||
|
||||
- name: Select Xcode 15.2
|
||||
run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS
|
||||
|
2
.github/workflows/diffcalc.yml
vendored
2
.github/workflows/diffcalc.yml
vendored
@ -111,7 +111,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check permissions
|
||||
run: |
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach)
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte)
|
||||
for i in "${ALLOWED_USERS[@]}"; do
|
||||
if [[ "${{ github.actor }}" == "$i" ]]; then
|
||||
exit 0
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -265,6 +265,8 @@ __pycache__/
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/*/.idea/projectSettingsUpdater.xml
|
||||
.idea/*/.idea/encodings.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
</project>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
@ -55,7 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca
|
||||
|
||||
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
|
||||
|
||||
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
|
||||
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
|
||||
|
||||
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
||||
|
||||
|
@ -7,7 +7,6 @@ T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> ins
|
||||
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
||||
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
||||
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
|
||||
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.
|
||||
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
|
||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.528.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.809.2" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Security.Principal;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -21,48 +20,14 @@ namespace osu.Desktop.Security
|
||||
[Resolved]
|
||||
private INotificationOverlay notifications { get; set; } = null!;
|
||||
|
||||
private bool elevated;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
elevated = checkElevated();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (elevated)
|
||||
if (Environment.IsPrivilegedProcess)
|
||||
notifications.Post(new ElevatedPrivilegesNotification());
|
||||
}
|
||||
|
||||
private bool checkElevated()
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
if (!OperatingSystem.IsWindows()) return false;
|
||||
|
||||
var windowsIdentity = WindowsIdentity.GetCurrent();
|
||||
var windowsPrincipal = new WindowsPrincipal(windowsIdentity);
|
||||
|
||||
return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
|
||||
case RuntimeInfo.Platform.macOS:
|
||||
case RuntimeInfo.Platform.Linux:
|
||||
return Mono.Unix.Native.Syscall.geteuid() == 0;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private partial class ElevatedPrivilegesNotification : SimpleNotification
|
||||
{
|
||||
public override bool IsImportant => true;
|
||||
|
@ -24,7 +24,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Clowd.Squirrel" Version="2.11.1" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
</ItemGroup>
|
||||
|
@ -0,0 +1,66 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneCatchEditorSaving : EditorSavingTestScene
|
||||
{
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestCatchJuiceStreamTickCorrect()
|
||||
{
|
||||
AddStep("enter timing mode", () => InputManager.Key(Key.F3));
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
|
||||
|
||||
Vector2 startPoint = Vector2.Zero;
|
||||
float increment = 0;
|
||||
|
||||
AddUntilStep("wait for playfield", () => this.ChildrenOfType<CatchPlayfield>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddStep("move to centre", () =>
|
||||
{
|
||||
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
|
||||
startPoint = playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Height / 3);
|
||||
increment = playfield.ScreenSpaceDrawQuad.Height / 10;
|
||||
InputManager.MoveMouseTo(startPoint);
|
||||
});
|
||||
AddStep("choose juice stream placing tool", () => InputManager.Key(Key.Number3));
|
||||
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(2 * increment, -increment)));
|
||||
AddStep("add node", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(-2 * increment, -2 * increment)));
|
||||
AddStep("add node", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(0, -3 * increment)));
|
||||
AddStep("end placement", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddUntilStep("juice stream placed", () => EditorBeatmap.HitObjects, () => Has.Count.EqualTo(1));
|
||||
|
||||
int largeDropletCount = 0, tinyDropletCount = 0;
|
||||
AddStep("store droplet count", () =>
|
||||
{
|
||||
largeDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet));
|
||||
tinyDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet));
|
||||
});
|
||||
|
||||
SaveEditor();
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("large droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet)), () => Is.EqualTo(largeDropletCount));
|
||||
AddAssert("tiny droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet)), () => Is.EqualTo(tinyDropletCount));
|
||||
}
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
|
||||
AddMouseMoveStep(-100, 100);
|
||||
addVertexCheckStep(3, 1, times[0], positions[0]);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -100,6 +101,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddMouseMoveStep(times[2] - 50, positions[2] - 50);
|
||||
addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50);
|
||||
addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50);
|
||||
|
||||
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addDragStartStep(times[1], positions[1]);
|
||||
AddMouseMoveStep(times[1], 400);
|
||||
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -129,6 +134,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddStep("scroll playfield", () => manualClock.CurrentTime += 200);
|
||||
AddMouseMoveStep(times[1] + 200, positions[1] + 100);
|
||||
addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -161,18 +167,18 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addAddVertexSteps(500, 150);
|
||||
addVertexCheckStep(3, 1, 500, 150);
|
||||
|
||||
addAddVertexSteps(90, 200);
|
||||
addVertexCheckStep(4, 1, times[0], positions[0]);
|
||||
addAddVertexSteps(160, 200);
|
||||
addVertexCheckStep(4, 1, 160, 200);
|
||||
|
||||
addAddVertexSteps(750, 180);
|
||||
addVertexCheckStep(5, 4, 750, 180);
|
||||
addVertexCheckStep(5, 4, 800, 160);
|
||||
AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteVertex()
|
||||
{
|
||||
double[] times = { 100, 300, 500 };
|
||||
double[] times = { 100, 300, 400 };
|
||||
float[] positions = { 100, 200, 150 };
|
||||
addBlueprintStep(times, positions);
|
||||
|
||||
@ -265,7 +271,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddStep("delete vertex", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Skinning;
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
||||
public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
|
||||
{
|
||||
if (withModifiedSkin)
|
||||
{
|
||||
@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
CreateTest();
|
||||
}
|
||||
|
||||
AddAssert("legacy HUD combo counter hidden", () =>
|
||||
{
|
||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
||||
});
|
||||
AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType<LegacyDefaultComboCounter>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public partial class TestSceneCatchReplayHandling : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestReplayDetach()
|
||||
{
|
||||
DrawableCatchRuleset drawableRuleset = null!;
|
||||
float catcherPosition = 0;
|
||||
|
||||
AddStep("create drawable ruleset", () => Child = drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), []));
|
||||
AddStep("attach replay", () => drawableRuleset.SetReplayScore(new Score()));
|
||||
AddStep("store catcher position", () => catcherPosition = drawableRuleset.ChildrenOfType<Catcher>().Single().X);
|
||||
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||
AddAssert("catcher didn't move", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.EqualTo(catcherPosition));
|
||||
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||
|
||||
AddStep("detach replay", () => drawableRuleset.SetReplayScore(null));
|
||||
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||
AddUntilStep("catcher moved", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.Not.EqualTo(catcherPosition));
|
||||
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||
}
|
||||
}
|
||||
}
|
@ -248,7 +248,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||
AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
||||
AddAssert("correct hit lighting colour",
|
||||
() => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -259,6 +260,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllExplodedObjectsAtUniquePositions()
|
||||
{
|
||||
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
|
||||
AddStep("catch normal fruit", () => attemptCatch(new Fruit { IndexInBeatmap = 2, LastInCombo = true }));
|
||||
AddAssert("two fruit at distinct x coordinates",
|
||||
() => this.ChildrenOfType<CaughtFruit>().Select(f => f.DrawPosition.X).Distinct(),
|
||||
() => Has.Exactly(2).Items);
|
||||
}
|
||||
|
||||
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||
|
||||
private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1 : ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity,
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity : 1,
|
||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||
}.Yield();
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@ -29,6 +28,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -62,43 +62,43 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
yield return new CatchModNightcore();
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
yield return new CatchModDoubleTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
yield return new CatchModPerfect();
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new CatchModSuddenDeath();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new CatchModCinema();
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new CatchModAutoplay();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
yield return new CatchModEasy();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
yield return new CatchModFlashlight();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
yield return new CatchModHalfTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
yield return new CatchModHardRock();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
yield return new CatchModHidden();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new CatchModNoFail();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
yield return new CatchModRelax();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@ -223,6 +223,12 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||
|
||||
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||
[
|
||||
new DifficultySection(),
|
||||
new ColoursSection(),
|
||||
];
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||
|
||||
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
value *= Math.Pow(accuracy(), 5.5);
|
||||
|
||||
if (score.Mods.Any(m => m is ModNoFail))
|
||||
value *= 0.90;
|
||||
value *= Math.Max(0.90, 1.0 - 0.02 * numMiss);
|
||||
|
||||
return new CatchPerformanceAttributes
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
@ -42,6 +43,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
[Resolved]
|
||||
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap? EditorBeatmap { get; private set; }
|
||||
|
||||
protected EditablePath(Func<float, double> positionToTime)
|
||||
{
|
||||
PositionToTime = positionToTime;
|
||||
@ -103,15 +107,23 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
//
|
||||
// The value is clamped here by the bindable min and max values.
|
||||
// In case the required velocity is too large, the path is not preserved.
|
||||
double previousVelocity = svBindable.Value;
|
||||
svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor);
|
||||
|
||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity);
|
||||
// adjust velocity locally, so that once the SV change is applied by applying defaults
|
||||
// (triggered by `EditorBeatmap.Update()` call at end of method),
|
||||
// it results in the outcome desired by the user.
|
||||
double relativeChange = svBindable.Value / previousVelocity;
|
||||
double localVelocity = hitObject.Velocity * relativeChange;
|
||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, localVelocity);
|
||||
|
||||
if (beatSnapProvider == null) return;
|
||||
|
||||
double endTime = hitObject.StartTime + path.Duration;
|
||||
double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime);
|
||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity;
|
||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * localVelocity;
|
||||
|
||||
EditorBeatmap?.Update(hitObject);
|
||||
}
|
||||
|
||||
public Vector2 ToRelativePosition(Vector2 screenSpacePosition)
|
||||
|
@ -4,12 +4,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -19,22 +18,27 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray();
|
||||
|
||||
private readonly JuiceStream juiceStream;
|
||||
|
||||
// To handle when the editor is scrolled while dragging.
|
||||
private Vector2 dragStartPosition;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
public SelectionEditablePath(Func<float, double> positionToTime)
|
||||
public SelectionEditablePath(JuiceStream juiceStream, Func<float, double> positionToTime)
|
||||
: base(positionToTime)
|
||||
{
|
||||
this.juiceStream = juiceStream;
|
||||
}
|
||||
|
||||
public void AddVertex(Vector2 relativePosition)
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
|
||||
double time = Math.Max(0, PositionToTime(relativePosition.Y));
|
||||
int index = AddVertex(time, relativePosition.X);
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
selectOnly(index);
|
||||
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||
@ -45,9 +49,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
if (index == -1 || VertexStates[index].IsFixed)
|
||||
return false;
|
||||
|
||||
if (e.Button == MouseButton.Left && e.ShiftPressed)
|
||||
if (e.Button == MouseButton.Right && e.ShiftPressed)
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
RemoveVertex(index);
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
EditorBeatmap?.EndChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -74,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
for (int i = 0; i < VertexCount; i++)
|
||||
VertexStates[i].VertexBeforeChange = Vertices[i];
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
EditorBeatmap?.BeginChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -88,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
changeHandler?.EndChange();
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
|
||||
private int getMouseTargetVertex(Vector2 screenSpacePosition)
|
||||
@ -118,11 +126,17 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
private void deleteSelectedVertices()
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
|
||||
for (int i = VertexCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (VertexStates[i].IsSelected)
|
||||
RemoveVertex(i);
|
||||
}
|
||||
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
|
||||
@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public partial class VertexPiece : Circle
|
||||
{
|
||||
private VertexState state = new VertexState();
|
||||
|
||||
[Resolved]
|
||||
private OsuColour osuColour { get; set; } = null!;
|
||||
|
||||
@ -24,7 +27,32 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
public void UpdateFrom(VertexState state)
|
||||
{
|
||||
Colour = state.IsSelected ? osuColour.Yellow.Lighten(1) : osuColour.Yellow;
|
||||
this.state = state;
|
||||
updateMarkerDisplay();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateMarkerDisplay();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateMarkerDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the circular control point marker.
|
||||
/// </summary>
|
||||
private void updateMarkerDisplay()
|
||||
{
|
||||
var colour = osuColour.Yellow;
|
||||
|
||||
if (IsHovered || state.IsSelected)
|
||||
colour = colour.Lighten(1);
|
||||
|
||||
Colour = colour;
|
||||
Alpha = state.IsFixed ? 0.5f : 1;
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
inputManager = GetContainingInputManager()!;
|
||||
|
||||
BeginPlacement();
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
scrollingPath = new ScrollingPath(),
|
||||
nestedOutlineContainer = new NestedOutlineContainer(),
|
||||
editablePath = new SelectionEditablePath(positionToTime)
|
||||
editablePath = new SelectionEditablePath(hitObject, positionToTime)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -121,7 +120,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
||||
{
|
||||
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
||||
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
||||
|
@ -15,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
|
||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt
|
||||
{
|
||||
public const float OBJECT_RADIUS = 64;
|
||||
|
||||
|
@ -21,11 +21,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||
|
||||
public Vector2 DisplayPosition => DrawPosition;
|
||||
public Vector2 DisplaySize => Size * Scale;
|
||||
|
||||
public float DisplayRotation => Rotation;
|
||||
|
||||
public double DisplayStartTime => HitObject.StartTime;
|
||||
|
||||
/// <summary>
|
||||
@ -44,19 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the hit object visual state from another <see cref="IHasCatchObjectState"/> object.
|
||||
/// </summary>
|
||||
public virtual void CopyStateFrom(IHasCatchObjectState objectState)
|
||||
{
|
||||
HitObject = objectState.HitObject;
|
||||
Scale = Vector2.Divide(objectState.DisplaySize, Size);
|
||||
Rotation = objectState.DisplayRotation;
|
||||
AccentColour.Value = objectState.AccentColour.Value;
|
||||
HyperDash.Value = objectState.HyperDash.Value;
|
||||
IndexInBeatmap.Value = objectState.IndexInBeatmap.Value;
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
ClearTransforms();
|
||||
@ -64,5 +49,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
base.FreeAfterUse();
|
||||
}
|
||||
|
||||
public void RestoreState(CatchObjectState state)
|
||||
{
|
||||
HitObject = state.HitObject;
|
||||
AccentColour.Value = state.AccentColour;
|
||||
HyperDash.Value = state.HyperDash;
|
||||
IndexInBeatmap.Value = state.IndexInBeatmap;
|
||||
Position = state.DisplayPosition;
|
||||
Scale = Vector2.Divide(state.DisplaySize, Size);
|
||||
Rotation = state.DisplayRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
/// </summary>
|
||||
protected readonly Container ScalingContainer;
|
||||
|
||||
public Vector2 DisplayPosition => DrawPosition;
|
||||
|
||||
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
||||
|
||||
public float DisplayRotation => ScalingContainer.Rotation;
|
||||
@ -95,5 +97,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
base.OnFree();
|
||||
}
|
||||
|
||||
public void RestoreState(CatchObjectState state) => throw new NotSupportedException("Cannot restore state into a drawable catch hitobject.");
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,35 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
public interface IHasCatchObjectState
|
||||
{
|
||||
PalpableCatchHitObject HitObject { get; }
|
||||
|
||||
double DisplayStartTime { get; }
|
||||
|
||||
Bindable<Color4> AccentColour { get; }
|
||||
|
||||
Bindable<bool> HyperDash { get; }
|
||||
|
||||
Bindable<int> IndexInBeatmap { get; }
|
||||
|
||||
double DisplayStartTime { get; }
|
||||
Vector2 DisplayPosition { get; }
|
||||
Vector2 DisplaySize { get; }
|
||||
|
||||
float DisplayRotation { get; }
|
||||
|
||||
void RestoreState(CatchObjectState state);
|
||||
}
|
||||
|
||||
public static class HasCatchObjectStateExtensions
|
||||
{
|
||||
public static CatchObjectState SaveState(this IHasCatchObjectState target) => new CatchObjectState(
|
||||
target.HitObject,
|
||||
target.AccentColour.Value,
|
||||
target.HyperDash.Value,
|
||||
target.IndexInBeatmap.Value,
|
||||
target.DisplayPosition,
|
||||
target.DisplaySize,
|
||||
target.DisplayRotation);
|
||||
}
|
||||
|
||||
public readonly record struct CatchObjectState(
|
||||
PalpableCatchHitObject HitObject,
|
||||
Color4 AccentColour,
|
||||
bool HyperDash,
|
||||
int IndexInBeatmap,
|
||||
Vector2 DisplayPosition,
|
||||
Vector2 DisplaySize,
|
||||
float DisplayRotation);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
@ -46,16 +47,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
public double TickDistanceMultiplier = 1;
|
||||
|
||||
[JsonIgnore]
|
||||
private double velocityFactor;
|
||||
public double Velocity { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
private double tickDistanceFactor;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Velocity => velocityFactor * SliderVelocityMultiplier;
|
||||
|
||||
[JsonIgnore]
|
||||
public double TickDistance => tickDistanceFactor * TickDistanceMultiplier;
|
||||
public double TickDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||
@ -68,14 +63,21 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
|
||||
velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength;
|
||||
tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate;
|
||||
Velocity = base_scoring_distance * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, CatchRuleset.SHORT_NAME);
|
||||
|
||||
// WARNING: this is intentionally not computed as `BASE_SCORING_DISTANCE * difficulty.SliderMultiplier`
|
||||
// for backwards compatibility reasons (intentionally introducing floating point errors to match stable).
|
||||
double scoringDistance = Velocity * timingPoint.BeatLength;
|
||||
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
this.PopulateNodeSamples();
|
||||
|
||||
var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
|
||||
|
||||
int nodeIndex = 0;
|
||||
|
@ -4,8 +4,8 @@
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
@ -28,76 +28,100 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
||||
switch (lookup)
|
||||
{
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
var components = base.GetDrawableComponent(lookup) as Container;
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle per ruleset defaults here.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
if (providesComboCounter && components != null)
|
||||
{
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
if (lookup is CatchSkinComponentLookup catchSkinComponent)
|
||||
{
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (hasPear)
|
||||
return new LegacyFruitPiece();
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||
if (!IsProvidingLegacyResources)
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
// Our own ruleset components default.
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
return null;
|
||||
if (keyCounter != null)
|
||||
{
|
||||
// set the anchor to top right so that it won't squash to the return button to the top
|
||||
keyCounter.Anchor = Anchor.CentreRight;
|
||||
keyCounter.Origin = Anchor.CentreRight;
|
||||
keyCounter.X = 0;
|
||||
// 340px is the default height inherit from stable
|
||||
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyKeyCounterDisplay(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case CatchSkinComponentLookup catchSkinComponent:
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (hasPear)
|
||||
return new LegacyFruitPiece();
|
||||
|
||||
case CatchSkinComponents.Catcher:
|
||||
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
return null;
|
||||
|
||||
if (version < 2.3m)
|
||||
{
|
||||
if (hasOldStyleCatcherSprite())
|
||||
return new LegacyCatcherOld();
|
||||
}
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
|
||||
if (hasNewStyleCatcherSprite())
|
||||
return new LegacyCatcherNew();
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter();
|
||||
return null;
|
||||
|
||||
return null;
|
||||
case CatchSkinComponents.Catcher:
|
||||
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||
|
||||
case CatchSkinComponents.HitExplosion:
|
||||
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||
return new LegacyHitExplosion();
|
||||
if (version < 2.3m)
|
||||
{
|
||||
if (hasOldStyleCatcherSprite())
|
||||
return new LegacyCatcherOld();
|
||||
}
|
||||
|
||||
return null;
|
||||
if (hasNewStyleCatcherSprite())
|
||||
return new LegacyCatcherNew();
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter();
|
||||
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.HitExplosion:
|
||||
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||
return new LegacyHitExplosion();
|
||||
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -362,7 +363,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (caughtObject == null) return;
|
||||
|
||||
caughtObject.CopyStateFrom(drawableObject);
|
||||
caughtObject.RestoreState(drawableObject.SaveState());
|
||||
caughtObject.Anchor = Anchor.TopCentre;
|
||||
caughtObject.Position = position;
|
||||
caughtObject.Scale *= caught_fruit_scale_adjust;
|
||||
@ -411,41 +412,50 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
private CaughtObject getDroppedObject(CaughtObject caughtObject)
|
||||
private CaughtObject getDroppedObject(CatchObjectState state)
|
||||
{
|
||||
var droppedObject = getCaughtObject(caughtObject.HitObject);
|
||||
var droppedObject = getCaughtObject(state.HitObject);
|
||||
Debug.Assert(droppedObject != null);
|
||||
|
||||
droppedObject.CopyStateFrom(caughtObject);
|
||||
droppedObject.RestoreState(state);
|
||||
droppedObject.Anchor = Anchor.TopLeft;
|
||||
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
|
||||
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget);
|
||||
|
||||
return droppedObject;
|
||||
}
|
||||
|
||||
private void clearPlate(DroppedObjectAnimation animation)
|
||||
{
|
||||
var caughtObjects = caughtObjectContainer.Children.ToArray();
|
||||
int caughtCount = caughtObjectContainer.Children.Count;
|
||||
CatchObjectState[] states = ArrayPool<CatchObjectState>.Shared.Rent(caughtCount);
|
||||
|
||||
caughtObjectContainer.Clear(false);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < caughtCount; i++)
|
||||
states[i] = caughtObjectContainer.Children[i].SaveState();
|
||||
|
||||
// Use the already returned PoolableDrawables for new objects
|
||||
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
|
||||
caughtObjectContainer.Clear(false);
|
||||
|
||||
droppedObjectTarget.AddRange(droppedObjects);
|
||||
|
||||
foreach (var droppedObject in droppedObjects)
|
||||
applyDropAnimation(droppedObject, animation);
|
||||
for (int i = 0; i < caughtCount; i++)
|
||||
{
|
||||
CaughtObject obj = getDroppedObject(states[i]);
|
||||
droppedObjectTarget.Add(obj);
|
||||
applyDropAnimation(obj, animation);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<CatchObjectState>.Shared.Return(states);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||
{
|
||||
CatchObjectState state = caughtObject.SaveState();
|
||||
caughtObjectContainer.Remove(caughtObject, false);
|
||||
|
||||
var droppedObject = getDroppedObject(caughtObject);
|
||||
|
||||
var droppedObject = getDroppedObject(state);
|
||||
droppedObjectTarget.Add(droppedObject);
|
||||
|
||||
applyDropAnimation(droppedObject, animation);
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
var replayState = (GetContainingInputManager()!.CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
|
||||
SetCatcherPosition(
|
||||
replayState?.CatcherX ??
|
||||
|
@ -1,13 +1,18 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
@ -30,5 +35,43 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReloadOnBPMChange()
|
||||
{
|
||||
HitObjectComposer oldComposer = null!;
|
||||
|
||||
AddStep("store composer", () => oldComposer = this.ChildrenOfType<HitObjectComposer>().Single());
|
||||
AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null);
|
||||
AddStep("switch to timing tab", () => InputManager.Key(Key.F3));
|
||||
AddUntilStep("wait for loaded", () => this.ChildrenOfType<TimingAdjustButton>().ElementAtOrDefault(1), () => Is.Not.Null);
|
||||
AddStep("change timing point BPM", () =>
|
||||
{
|
||||
var bpmControl = this.ChildrenOfType<TimingAdjustButton>().ElementAt(1);
|
||||
InputManager.MoveMouseTo(bpmControl);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("switch back to composer", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("composer reloaded", () =>
|
||||
{
|
||||
var composer = this.ChildrenOfType<HitObjectComposer>().SingleOrDefault();
|
||||
return composer != null && composer != oldComposer;
|
||||
});
|
||||
|
||||
AddStep("store composer", () => oldComposer = this.ChildrenOfType<HitObjectComposer>().Single());
|
||||
AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null);
|
||||
AddStep("undo", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.Z);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
AddUntilStep("composer reloaded", () =>
|
||||
{
|
||||
var composer = this.ChildrenOfType<HitObjectComposer>().SingleOrDefault();
|
||||
return composer != null && composer != oldComposer;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -17,6 +18,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
@ -84,6 +86,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
public partial class TestHitObjectComposer : HitObjectComposer
|
||||
{
|
||||
public override Playfield Playfield { get; }
|
||||
public override ComposeBlueprintContainer BlueprintContainer => throw new NotImplementedException();
|
||||
public override IEnumerable<DrawableHitObject> HitObjects => Enumerable.Empty<DrawableHitObject>();
|
||||
public override bool CursorInPlacementArea => false;
|
||||
|
||||
@ -100,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
|
||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneManiaEditorSaving : EditorSavingTestScene
|
||||
{
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestKeyCountChange()
|
||||
{
|
||||
LabelledSliderBar<float> keyCount = null!;
|
||||
|
||||
AddStep("go to setup screen", () => InputManager.Key(Key.F4));
|
||||
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<LabelledSliderBar<float>>().First(), () => Is.Not.Null);
|
||||
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
|
||||
AddStep("change key count to 8", () =>
|
||||
{
|
||||
keyCount.Current.Value = 8;
|
||||
});
|
||||
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().SingleOrDefault()?.CurrentDialog, Is.InstanceOf<ReloadEditorDialog>);
|
||||
AddStep("refuse", () => InputManager.Key(Key.Number2));
|
||||
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
|
||||
|
||||
AddStep("change key count to 8 again", () =>
|
||||
{
|
||||
keyCount.Current.Value = 8;
|
||||
});
|
||||
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().Single().CurrentDialog, Is.InstanceOf<ReloadEditorDialog>);
|
||||
AddStep("acquiesce", () => InputManager.Key(Key.Number1));
|
||||
AddUntilStep("beatmap became 8K", () => Game.Beatmap.Value.BeatmapInfo.Difficulty.CircleSize, () => Is.EqualTo(8));
|
||||
}
|
||||
}
|
||||
}
|
@ -186,8 +186,106 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragHoldNoteHead()
|
||||
{
|
||||
setScrollStep(ScrollingDirection.Down);
|
||||
|
||||
HoldNote holdNote = null;
|
||||
AddStep("setup beatmap", () =>
|
||||
{
|
||||
composer.EditorBeatmap.Clear();
|
||||
composer.EditorBeatmap.Add(holdNote = new HoldNote
|
||||
{
|
||||
Column = 1,
|
||||
StartTime = 250,
|
||||
EndTime = 750,
|
||||
});
|
||||
});
|
||||
|
||||
DrawableHoldNote drawableHoldNote = null;
|
||||
EditHoldNoteEndPiece headPiece = null;
|
||||
|
||||
AddStep("select blueprint", () =>
|
||||
{
|
||||
drawableHoldNote = this.ChildrenOfType<DrawableHoldNote>().Single();
|
||||
InputManager.MoveMouseTo(drawableHoldNote);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddStep("grab hold note head", () =>
|
||||
{
|
||||
headPiece = this.ChildrenOfType<EditHoldNoteEndPiece>().First();
|
||||
InputManager.MoveMouseTo(headPiece);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("drag head downwards", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(headPiece, new Vector2(0, 100));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("start time moved back", () => holdNote!.StartTime, () => Is.LessThan(250));
|
||||
AddAssert("end time unchanged", () => holdNote.EndTime, () => Is.EqualTo(750));
|
||||
|
||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.BottomLeft, drawableHoldNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.TopLeft, drawableHoldNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == drawableHoldNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(1).DrawPosition == drawableHoldNote.Tail.DrawPosition);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragHoldNoteTail()
|
||||
{
|
||||
setScrollStep(ScrollingDirection.Down);
|
||||
|
||||
HoldNote holdNote = null;
|
||||
AddStep("setup beatmap", () =>
|
||||
{
|
||||
composer.EditorBeatmap.Clear();
|
||||
composer.EditorBeatmap.Add(holdNote = new HoldNote
|
||||
{
|
||||
Column = 1,
|
||||
StartTime = 250,
|
||||
EndTime = 750,
|
||||
});
|
||||
});
|
||||
|
||||
DrawableHoldNote drawableHoldNote = null;
|
||||
EditHoldNoteEndPiece tailPiece = null;
|
||||
|
||||
AddStep("select blueprint", () =>
|
||||
{
|
||||
drawableHoldNote = this.ChildrenOfType<DrawableHoldNote>().Single();
|
||||
InputManager.MoveMouseTo(drawableHoldNote);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddStep("grab hold note tail", () =>
|
||||
{
|
||||
tailPiece = this.ChildrenOfType<EditHoldNoteEndPiece>().Last();
|
||||
InputManager.MoveMouseTo(tailPiece);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("drag tail upwards", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(tailPiece, new Vector2(0, -100));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("start time unchanged", () => holdNote!.StartTime, () => Is.EqualTo(250));
|
||||
AddAssert("end time moved forward", () => holdNote.EndTime, () => Is.GreaterThan(750));
|
||||
|
||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.BottomLeft, drawableHoldNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.TopLeft, drawableHoldNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == drawableHoldNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(1).DrawPosition == drawableHoldNote.Tail.DrawPosition);
|
||||
}
|
||||
|
||||
private void setScrollStep(ScrollingDirection direction)
|
||||
|
@ -0,0 +1,96 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneManiaSelectionHandler : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[Test]
|
||||
public void TestHorizontalFlipOverSelection()
|
||||
{
|
||||
ManiaHitObject first = null!, second = null!, third = null!;
|
||||
|
||||
AddStep("create objects", () =>
|
||||
{
|
||||
EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 });
|
||||
EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 });
|
||||
EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 });
|
||||
});
|
||||
|
||||
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||
AddStep("flip horizontally over selection", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxButton>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("first object stayed in place", () => first.Column, () => Is.EqualTo(2));
|
||||
AddAssert("second object flipped", () => second.Column, () => Is.EqualTo(3));
|
||||
AddAssert("third object flipped", () => third.Column, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHorizontalFlipOverPlayfield()
|
||||
{
|
||||
ManiaHitObject first = null!, second = null!, third = null!;
|
||||
|
||||
AddStep("create objects", () =>
|
||||
{
|
||||
EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 });
|
||||
EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 });
|
||||
EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 });
|
||||
});
|
||||
|
||||
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||
AddStep("flip horizontally", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.H);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
|
||||
AddAssert("first object flipped", () => first.Column, () => Is.EqualTo(1));
|
||||
AddAssert("second object flipped", () => second.Column, () => Is.EqualTo(2));
|
||||
AddAssert("third object flipped", () => third.Column, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVerticalFlip()
|
||||
{
|
||||
ManiaHitObject first = null!, second = null!, third = null!;
|
||||
|
||||
AddStep("create objects", () =>
|
||||
{
|
||||
EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 });
|
||||
EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 });
|
||||
EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 });
|
||||
});
|
||||
|
||||
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||
AddStep("flip vertically", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.J);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
|
||||
AddAssert("first object flipped", () => first.StartTime, () => Is.EqualTo(2250));
|
||||
AddAssert("second object flipped", () => second.StartTime, () => Is.EqualTo(250));
|
||||
AddAssert("third object flipped", () => third.StartTime, () => Is.EqualTo(1250));
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestCase(ManiaAction.Key1)]
|
||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||
[TestCase(ManiaAction.Special1)]
|
||||
[TestCase(ManiaAction.Key8)]
|
||||
[TestCase(ManiaAction.Key5)]
|
||||
[TestCase(ManiaAction.Key9)]
|
||||
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
||||
{
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(9));
|
||||
@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
[TestCase(ManiaAction.Key1)]
|
||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||
[TestCase(ManiaAction.Special1)]
|
||||
[TestCase(ManiaAction.Special2)]
|
||||
[TestCase(ManiaAction.Special1, ManiaAction.Special2)]
|
||||
[TestCase(ManiaAction.Special1, ManiaAction.Key5)]
|
||||
[TestCase(ManiaAction.Key3)]
|
||||
[TestCase(ManiaAction.Key8)]
|
||||
[TestCase(ManiaAction.Key3, ManiaAction.Key8)]
|
||||
[TestCase(ManiaAction.Key3, ManiaAction.Key6)]
|
||||
[TestCase(ManiaAction.Key10)]
|
||||
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
||||
{
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(5));
|
||||
|
@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@ -17,5 +19,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
Mod = new ManiaModInvert(),
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBreaksPreservedOnOriginalBeatmap()
|
||||
{
|
||||
var beatmap = CreateBeatmap(new ManiaRuleset().RulesetInfo);
|
||||
beatmap.Breaks.Clear();
|
||||
beatmap.Breaks.Add(new BreakPeriod(0, 1000));
|
||||
|
||||
var workingBeatmap = new FlatWorkingBeatmap(beatmap);
|
||||
|
||||
var playableWithInvert = workingBeatmap.GetPlayableBeatmap(new ManiaRuleset().RulesetInfo, new[] { new ManiaModInvert() });
|
||||
Assert.That(playableWithInvert.Breaks.Count, Is.Zero);
|
||||
|
||||
var playableWithoutInvert = workingBeatmap.GetPlayableBeatmap(new ManiaRuleset().RulesetInfo);
|
||||
Assert.That(playableWithoutInvert.Breaks.Count, Is.Not.Zero);
|
||||
Assert.That(playableWithoutInvert.Breaks[0], Is.EqualTo(new BreakPeriod(0, 1000)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
643
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Normal file
643
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Normal file
@ -0,0 +1,643 @@
|
||||
// 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.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModNoRelease : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
private const double time_before_head = 250;
|
||||
private const double time_head = 1500;
|
||||
private const double time_during_hold_1 = 2500;
|
||||
private const double time_tail = 4000;
|
||||
private const double time_after_tail = 5250;
|
||||
|
||||
private List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// o o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestNoInput()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCorrectInput()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
assertNoteJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLateRelease()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
assertNoteJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_before_head + 10),
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_before_head + 10),
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartAndBreak()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 10),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xox o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartThenReleaseAndImmediatelyRepress()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 1),
|
||||
new ManiaReplayFrame(time_head + 2, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertComboAtJudgement(0, 1);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
assertComboAtJudgement(1, 0);
|
||||
assertComboAtJudgement(3, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 10),
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 10),
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressDuringNoteAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressDuringNoteAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]--------------
|
||||
/// xo
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseAfterTailWithCloseByHead()
|
||||
{
|
||||
const int duration = 30;
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
// hold note is very short, to make the head still in range
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = duration,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head + duration + 60, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + duration + 70),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Ok);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-O-------------
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustBeforeTailWithNearbyNoteAndCloseByHead()
|
||||
{
|
||||
Note note;
|
||||
|
||||
const int duration = 50;
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
// hold note is very short, to make the head still in range
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = duration,
|
||||
Column = 0,
|
||||
},
|
||||
{
|
||||
// Next note within tail lenience
|
||||
note = new Note
|
||||
{
|
||||
StartTime = time_head + duration + 10
|
||||
}
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head + duration, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + duration + 10),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Good);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
|
||||
assertHitObjectJudgement(note, HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]--O--
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustBeforeTailWithNearbyNote()
|
||||
{
|
||||
Note note;
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = time_tail - time_head,
|
||||
Column = 0,
|
||||
},
|
||||
{
|
||||
// Next note within tail lenience
|
||||
note = new Note
|
||||
{
|
||||
StartTime = time_tail + 50
|
||||
}
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail - 10, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
|
||||
assertHitObjectJudgement(note, HitResult.Good);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail + 20, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail + 30),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]--O--
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustAfterTailWithNearbyNote()
|
||||
{
|
||||
// Next note within tail lenience
|
||||
Note note = new Note { StartTime = time_tail + 50 };
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = time_tail - time_head,
|
||||
Column = 0,
|
||||
},
|
||||
note
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail + 10, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail + 20),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
|
||||
assertHitObjectJudgement(note, HitResult.Great);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail + 10),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissReleaseAndHitSecondRelease()
|
||||
{
|
||||
var windows = new ManiaHitWindows();
|
||||
windows.SetDifficulty(10);
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 500,
|
||||
Column = 0,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10,
|
||||
Duration = 500,
|
||||
Column = 0,
|
||||
},
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderTickRate = 4,
|
||||
OverallDifficulty = 10,
|
||||
},
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(beatmap.HitObjects[1].StartTime, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(beatmap.HitObjects[1].GetEndTime()),
|
||||
}, beatmap);
|
||||
|
||||
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => !j.Type.IsHit()));
|
||||
|
||||
AddAssert("second hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestZeroLength()
|
||||
{
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 0,
|
||||
Column = 0,
|
||||
},
|
||||
},
|
||||
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(beatmap.HitObjects[0].StartTime, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(beatmap.HitObjects[0].GetEndTime() + 1),
|
||||
}, beatmap);
|
||||
|
||||
AddAssert("hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
private void assertHitObjectJudgement(HitObject hitObject, HitResult result)
|
||||
=> AddAssert($"object judged as {result}", () => judgementResults.First(j => j.HitObject == hitObject).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertHeadJudgement(HitResult result)
|
||||
=> AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertTailJudgement(HitResult result)
|
||||
=> AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertNoteJudgement(HitResult result)
|
||||
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertComboAtJudgement(int judgementIndex, int combo)
|
||||
=> AddAssert($"combo at judgement {judgementIndex} is {combo}", () => judgementResults.ElementAt(judgementIndex).ComboAfterJudgement, () => Is.EqualTo(combo));
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject>? beatmap = null)
|
||||
{
|
||||
if (beatmap == null)
|
||||
{
|
||||
beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = time_tail - time_head,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||
}
|
||||
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
SelectedMods.Value = new List<Mod>
|
||||
{
|
||||
new ManiaModNoRelease()
|
||||
};
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
{
|
||||
p.ScoreProcessor.NewJudgement += result =>
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Argon;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public partial class TestSceneComboCounter : ManiaSkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("setup", () => SetContents(s =>
|
||||
{
|
||||
if (s is ArgonSkin)
|
||||
return new ArgonManiaComboCounter();
|
||||
|
||||
if (s is LegacySkin)
|
||||
return new LegacyManiaComboCounter();
|
||||
|
||||
return new LegacyManiaComboCounter();
|
||||
}));
|
||||
|
||||
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20);
|
||||
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||
}
|
||||
}
|
||||
}
|
@ -28,14 +28,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
AddStep("Show " + result.GetDescription(), () =>
|
||||
{
|
||||
SetContents(_ =>
|
||||
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||
{
|
||||
Type = result
|
||||
}, null)
|
||||
{
|
||||
var drawableManiaJudgement = new DrawableManiaJudgement
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
};
|
||||
|
||||
drawableManiaJudgement.Apply(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||
{
|
||||
Type = result
|
||||
}, null);
|
||||
|
||||
return drawableManiaJudgement;
|
||||
});
|
||||
|
||||
// for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
|
||||
// (see `LegacyManiaJudgementPiece.load()`).
|
||||
|
@ -3,15 +3,22 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public partial class TestScenePlayfield : ManiaSkinnableTestScene
|
||||
{
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||
|
||||
private List<StageDefinition> stageDefinitions = new List<StageDefinition>();
|
||||
|
||||
[Test]
|
||||
@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
Child = new ManiaPlayfield(stageDefinitions)
|
||||
});
|
||||
});
|
||||
|
||||
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||
}
|
||||
|
||||
[TestCase(2)]
|
||||
@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmapForSkinProvider()
|
||||
|
@ -14,12 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
SetContents(_ =>
|
||||
{
|
||||
ManiaAction normalAction = ManiaAction.Key1;
|
||||
ManiaAction specialAction = ManiaAction.Special1;
|
||||
ManiaAction action = ManiaAction.Key1;
|
||||
|
||||
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||
{
|
||||
Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction)
|
||||
Child = new Stage(0, new StageDefinition(4), ref action)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddStep("Hold key", () =>
|
||||
{
|
||||
clock.CurrentTime = 0;
|
||||
note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager().CurrentState, ManiaAction.Key1));
|
||||
note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager()!.CurrentState, ManiaAction.Key1));
|
||||
});
|
||||
AddStep("progress time", () => clock.CurrentTime = 500);
|
||||
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
||||
|
@ -474,8 +474,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => !j.Type.IsHit()));
|
||||
|
||||
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
AddAssert("second hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,36 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
// play with a converted beatmap to allow dual stages mod to work.
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo());
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[Test]
|
||||
public void TestSingleStage()
|
||||
{
|
||||
AddStep("Load single stage", LoadPlayer);
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDualStage()
|
||||
{
|
||||
AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() }));
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
}
|
||||
|
||||
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
|
||||
=> hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
|
||||
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor);
|
||||
|
||||
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
|
||||
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
|
||||
|
@ -131,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
||||
{
|
||||
var specialAction = ManiaAction.Special1;
|
||||
|
||||
var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction);
|
||||
var stage = new Stage(0, new StageDefinition(2), ref action);
|
||||
stages.Add(stage);
|
||||
|
||||
return new ScrollingTestContainer(direction)
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osuTK;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -79,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
else
|
||||
convertType |= PatternType.LowProbability;
|
||||
|
||||
if (!convertType.HasFlagFast(PatternType.KeepSingle))
|
||||
if (!convertType.HasFlag(PatternType.KeepSingle))
|
||||
{
|
||||
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
|
||||
convertType |= PatternType.Mirror;
|
||||
@ -102,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
||||
if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by copying the last hit objects in reverse-column order
|
||||
for (int i = RandomStart; i < TotalColumns; i++)
|
||||
@ -114,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||
// If we convert to 7K + 1, let's not overload the special key
|
||||
&& (TotalColumns != 8 || lastColumn != 0)
|
||||
// Make sure the last column was not the centre column
|
||||
@ -127,7 +126,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||
if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by placing on the already filled columns
|
||||
for (int i = RandomStart; i < TotalColumns; i++)
|
||||
@ -141,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (PreviousPattern.HitObjects.Count() == 1)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.Stair))
|
||||
if (convertType.HasFlag(PatternType.Stair))
|
||||
{
|
||||
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
|
||||
int targetColumn = lastColumn + 1;
|
||||
@ -152,7 +151,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.ReverseStair))
|
||||
if (convertType.HasFlag(PatternType.ReverseStair))
|
||||
{
|
||||
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
|
||||
int targetColumn = lastColumn - 1;
|
||||
@ -164,10 +163,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.KeepSingle))
|
||||
if (convertType.HasFlag(PatternType.KeepSingle))
|
||||
return generateRandomNotes(1);
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.Mirror))
|
||||
if (convertType.HasFlag(PatternType.Mirror))
|
||||
{
|
||||
if (ConversionDifficulty > 6.5)
|
||||
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||
@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.78, 0.42, 0, 0);
|
||||
|
||||
return generateRandomPattern(1, 0.62, 0, 0);
|
||||
@ -187,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.35, 0.08, 0, 0);
|
||||
|
||||
return generateRandomPattern(0.52, 0.15, 0, 0);
|
||||
@ -195,7 +194,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 2)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.18, 0, 0, 0);
|
||||
|
||||
return generateRandomPattern(0.45, 0, 0, 0);
|
||||
@ -208,9 +207,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
foreach (var obj in p.HitObjects)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||
StairType = PatternType.ReverseStair;
|
||||
if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||
StairType = PatternType.Stair;
|
||||
}
|
||||
|
||||
@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
|
||||
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
|
||||
|
||||
if (!allowStacking)
|
||||
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
|
||||
@ -250,7 +249,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int getNextColumn(int last)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.Gathered))
|
||||
if (convertType.HasFlag(PatternType.Gathered))
|
||||
{
|
||||
last++;
|
||||
if (last == TotalColumns)
|
||||
@ -297,7 +296,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack))
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack))
|
||||
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
@ -7,7 +7,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
|
||||
@ -147,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
|
||||
@ -155,13 +154,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 2.5)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.3, 0, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.17, 0, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.27, 0, 0);
|
||||
@ -219,7 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
int lastColumn = nextColumn;
|
||||
@ -371,7 +370,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
||||
|
||||
bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
|
||||
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
|
||||
|
||||
if (canGenerateTwoNotes)
|
||||
@ -404,7 +403,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
int endTime = startTime + SegmentDuration * SpanCount;
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
for (int i = 0; i < columnRepeat; i++)
|
||||
@ -433,7 +432,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
|
||||
|
||||
// Create the hold note
|
||||
|
@ -45,18 +45,15 @@ namespace osu.Game.Rulesets.Mania
|
||||
LeftKeys = stage1LeftKeys,
|
||||
RightKeys = stage1RightKeys,
|
||||
SpecialKey = InputKey.V,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1
|
||||
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
||||
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||
|
||||
var stage2Bindings = new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = stage2LeftKeys,
|
||||
RightKeys = stage2RightKeys,
|
||||
SpecialKey = InputKey.B,
|
||||
SpecialAction = ManiaAction.Special2,
|
||||
NormalActionStart = nextNormal
|
||||
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
||||
ActionStart = (ManiaAction)singleStageVariant,
|
||||
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||
|
||||
return stage1Bindings.Concat(stage2Bindings);
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
|
||||
{
|
||||
public partial class EditHoldNoteEndPiece : CompositeDrawable
|
||||
{
|
||||
public Action? DragStarted { get; init; }
|
||||
public Action<Vector2>? Dragging { get; init; }
|
||||
public Action? DragEnded { get; init; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = DefaultNotePiece.NOTE_HEIGHT;
|
||||
|
||||
CornerRadius = 5;
|
||||
Masking = true;
|
||||
|
||||
InternalChild = new DefaultNotePiece();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
DragStarted?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
base.OnDrag(e);
|
||||
Dragging?.Invoke(e.ScreenSpaceMousePosition);
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
base.OnDragEnd(e);
|
||||
DragEnded?.Invoke();
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
var colour = colours.Yellow;
|
||||
|
||||
if (IsHovered)
|
||||
colour = colour.Lighten(1);
|
||||
|
||||
Colour = colour;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
@ -18,10 +18,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private EditNotePiece head;
|
||||
private EditNotePiece tail;
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap? editorBeatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IPositionSnapProvider? positionSnapProvider { get; set; }
|
||||
|
||||
private EditHoldNoteEndPiece head = null!;
|
||||
private EditHoldNoteEndPiece tail = null!;
|
||||
|
||||
public HoldNoteSelectionBlueprint(HoldNote hold)
|
||||
: base(hold)
|
||||
@ -33,8 +42,43 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
head = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
||||
tail = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
||||
head = new EditHoldNoteEndPiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
DragStarted = () => changeHandler?.BeginChange(),
|
||||
Dragging = pos =>
|
||||
{
|
||||
double endTimeBeforeDrag = HitObject.EndTime;
|
||||
double proposedStartTime = positionSnapProvider?.FindSnappedPositionAndTime(pos).Time ?? HitObjectContainer.TimeAtScreenSpacePosition(pos);
|
||||
double proposedEndTime = endTimeBeforeDrag;
|
||||
|
||||
if (proposedStartTime >= proposedEndTime)
|
||||
return;
|
||||
|
||||
HitObject.StartTime = proposedStartTime;
|
||||
HitObject.EndTime = proposedEndTime;
|
||||
editorBeatmap?.Update(HitObject);
|
||||
},
|
||||
DragEnded = () => changeHandler?.EndChange(),
|
||||
},
|
||||
tail = new EditHoldNoteEndPiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
DragStarted = () => changeHandler?.BeginChange(),
|
||||
Dragging = pos =>
|
||||
{
|
||||
double proposedStartTime = HitObject.StartTime;
|
||||
double proposedEndTime = positionSnapProvider?.FindSnappedPositionAndTime(pos).Time ?? HitObjectContainer.TimeAtScreenSpacePosition(pos);
|
||||
|
||||
if (proposedStartTime >= proposedEndTime)
|
||||
return;
|
||||
|
||||
HitObject.StartTime = proposedStartTime;
|
||||
HitObject.EndTime = proposedEndTime;
|
||||
editorBeatmap?.Update(HitObject);
|
||||
},
|
||||
DragEnded = () => changeHandler?.EndChange(),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -18,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public BindableBool ShowSpeedChanges { get; } = new BindableBool();
|
||||
|
||||
public double? TimelineTimeRange { get; set; }
|
||||
|
||||
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
|
||||
|
||||
public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods)
|
||||
@ -38,5 +41,11 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
Origin = Anchor.Centre,
|
||||
Size = Vector2.One
|
||||
};
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get<int>(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value;
|
||||
base.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
@ -14,6 +13,7 @@ using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
@ -21,7 +21,10 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
||||
{
|
||||
private DrawableManiaEditorRuleset drawableRuleset;
|
||||
private DrawableManiaEditorRuleset drawableRuleset = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorScreenWithTimeline? screenWithTimeline { get; set; }
|
||||
|
||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
@ -72,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
||||
continue;
|
||||
|
||||
ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||
|
||||
if (current == null)
|
||||
continue;
|
||||
@ -83,5 +86,13 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (screenWithTimeline?.TimelineArea.Timeline != null)
|
||||
drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -16,6 +17,16 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
[Resolved]
|
||||
private HitObjectComposer composer { get; set; } = null!;
|
||||
|
||||
protected override void OnSelectionChanged()
|
||||
{
|
||||
base.OnSelectionChanged();
|
||||
|
||||
var selectedObjects = SelectedItems.OfType<ManiaHitObject>().ToArray();
|
||||
|
||||
SelectionBox.CanFlipX = canFlipX(selectedObjects);
|
||||
SelectionBox.CanFlipY = canFlipY(selectedObjects);
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
|
||||
@ -26,6 +37,58 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool HandleFlip(Direction direction, bool flipOverOrigin)
|
||||
{
|
||||
var selectedObjects = SelectedItems.OfType<ManiaHitObject>().ToArray();
|
||||
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||
|
||||
if (selectedObjects.Length == 0)
|
||||
return false;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case Direction.Horizontal:
|
||||
if (!canFlipX(selectedObjects))
|
||||
return false;
|
||||
|
||||
int firstColumn = flipOverOrigin ? 0 : selectedObjects.Min(ho => ho.Column);
|
||||
int lastColumn = flipOverOrigin ? (int)EditorBeatmap.BeatmapInfo.Difficulty.CircleSize - 1 : selectedObjects.Max(ho => ho.Column);
|
||||
|
||||
EditorBeatmap.PerformOnSelection(hitObject =>
|
||||
{
|
||||
var maniaObject = (ManiaHitObject)hitObject;
|
||||
maniaPlayfield.Remove(maniaObject);
|
||||
maniaObject.Column = firstColumn + (lastColumn - maniaObject.Column);
|
||||
maniaPlayfield.Add(maniaObject);
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
case Direction.Vertical:
|
||||
if (!canFlipY(selectedObjects))
|
||||
return false;
|
||||
|
||||
double selectionStartTime = selectedObjects.Min(ho => ho.StartTime);
|
||||
double selectionEndTime = selectedObjects.Max(ho => ho.GetEndTime());
|
||||
|
||||
EditorBeatmap.PerformOnSelection(hitObject =>
|
||||
{
|
||||
hitObject.StartTime = selectionStartTime + (selectionEndTime - hitObject.GetEndTime());
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(direction), direction, "Cannot flip over the supplied direction.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool canFlipX(ManiaHitObject[] selectedObjects)
|
||||
=> selectedObjects.Select(ho => ho.Column).Distinct().Count() > 1;
|
||||
|
||||
private static bool canFlipY(ManiaHitObject[] selectedObjects)
|
||||
=> selectedObjects.Length > 1 && selectedObjects.Min(ho => ho.StartTime) < selectedObjects.Max(ho => ho.GetEndTime());
|
||||
|
||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||
|
@ -3,20 +3,164 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
{
|
||||
public partial class ManiaDifficultySection : DifficultySection
|
||||
public partial class ManiaDifficultySection : SetupSection
|
||||
{
|
||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||
|
||||
private LabelledSliderBar<float> keyCountSlider { get; set; } = null!;
|
||||
private LabelledSwitchButton specialStyle { get; set; } = null!;
|
||||
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Editor? editor { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
CircleSizeSlider.Label = BeatmapsetsStrings.ShowStatsCsMania;
|
||||
CircleSizeSlider.Description = "The number of columns in the beatmap";
|
||||
if (CircleSizeSlider.Current is BindableNumber<float> circleSizeFloat)
|
||||
circleSizeFloat.Precision = 1;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
keyCountSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsCsMania,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "The number of columns in the beatmap",
|
||||
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 1,
|
||||
}
|
||||
},
|
||||
specialStyle = new LabelledSwitchButton
|
||||
{
|
||||
Label = "Use special (N+1) style",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||
},
|
||||
healthDrainSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.DrainRateDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
overallDifficultySlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.OverallDifficultyDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
baseVelocitySlider = new LabelledSliderBar<double>
|
||||
{
|
||||
Label = EditorSetupStrings.BaseVelocity,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
MinValue = 0.4,
|
||||
MaxValue = 3.6,
|
||||
Precision = 0.01f,
|
||||
}
|
||||
},
|
||||
tickRateSlider = new LabelledSliderBar<double>
|
||||
{
|
||||
Label = EditorSetupStrings.TickRate,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.TickRateDescription,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
MinValue = 1,
|
||||
MaxValue = 4,
|
||||
Precision = 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
keyCountSlider.Current.BindValueChanged(updateKeyCount);
|
||||
healthDrainSlider.Current.BindValueChanged(_ => updateValues());
|
||||
overallDifficultySlider.Current.BindValueChanged(_ => updateValues());
|
||||
baseVelocitySlider.Current.BindValueChanged(_ => updateValues());
|
||||
tickRateSlider.Current.BindValueChanged(_ => updateValues());
|
||||
}
|
||||
|
||||
private bool updatingKeyCount;
|
||||
|
||||
private void updateKeyCount(ValueChangedEvent<float> keyCount)
|
||||
{
|
||||
if (updatingKeyCount) return;
|
||||
|
||||
updateValues();
|
||||
|
||||
if (editor == null) return;
|
||||
|
||||
updatingKeyCount = true;
|
||||
|
||||
editor.Reload().ContinueWith(t =>
|
||||
{
|
||||
if (!t.GetResultSafely())
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
changeHandler!.RestoreState(-1);
|
||||
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value = keyCount.OldValue;
|
||||
updatingKeyCount = false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
updatingKeyCount = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateValues()
|
||||
{
|
||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||
|
||||
Beatmap.UpdateAllHitObjects();
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
{
|
||||
public partial class ManiaSetupSection : RulesetSetupSection
|
||||
{
|
||||
private LabelledSwitchButton specialStyle;
|
||||
|
||||
public ManiaSetupSection()
|
||||
: base(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
specialStyle = new LabelledSwitchButton
|
||||
{
|
||||
Label = "Use special (N+1) style",
|
||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
specialStyle.Current.BindValueChanged(_ => updateBeatmap());
|
||||
}
|
||||
|
||||
private void updateBeatmap()
|
||||
{
|
||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
@ -19,16 +19,8 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public enum ManiaAction
|
||||
{
|
||||
[Description("Special 1")]
|
||||
Special1 = 1,
|
||||
|
||||
[Description("Special 2")]
|
||||
Special2,
|
||||
|
||||
// This offsets the start value of normal keys in-case we add more special keys
|
||||
// above at a later time, without breaking replays/configs.
|
||||
[Description("Key 1")]
|
||||
Key1 = 10,
|
||||
Key1,
|
||||
|
||||
[Description("Key 2")]
|
||||
Key2,
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@ -89,79 +88,79 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
yield return new ManiaModNightcore();
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
yield return new ManiaModDoubleTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
yield return new ManiaModPerfect();
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new ManiaModSuddenDeath();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new ManiaModCinema();
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new ManiaModAutoplay();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
yield return new ManiaModEasy();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.FadeIn))
|
||||
if (mods.HasFlag(LegacyMods.FadeIn))
|
||||
yield return new ManiaModFadeIn();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
yield return new ManiaModFlashlight();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
yield return new ManiaModHalfTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
yield return new ManiaModHardRock();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
yield return new ManiaModHidden();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key1))
|
||||
if (mods.HasFlag(LegacyMods.Key1))
|
||||
yield return new ManiaModKey1();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key2))
|
||||
if (mods.HasFlag(LegacyMods.Key2))
|
||||
yield return new ManiaModKey2();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key3))
|
||||
if (mods.HasFlag(LegacyMods.Key3))
|
||||
yield return new ManiaModKey3();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key4))
|
||||
if (mods.HasFlag(LegacyMods.Key4))
|
||||
yield return new ManiaModKey4();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key5))
|
||||
if (mods.HasFlag(LegacyMods.Key5))
|
||||
yield return new ManiaModKey5();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key6))
|
||||
if (mods.HasFlag(LegacyMods.Key6))
|
||||
yield return new ManiaModKey6();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key7))
|
||||
if (mods.HasFlag(LegacyMods.Key7))
|
||||
yield return new ManiaModKey7();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key8))
|
||||
if (mods.HasFlag(LegacyMods.Key8))
|
||||
yield return new ManiaModKey8();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key9))
|
||||
if (mods.HasFlag(LegacyMods.Key9))
|
||||
yield return new ManiaModKey9();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.KeyCoop))
|
||||
if (mods.HasFlag(LegacyMods.KeyCoop))
|
||||
yield return new ManiaModDualStages();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new ManiaModNoFail();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Random))
|
||||
if (mods.HasFlag(LegacyMods.Random))
|
||||
yield return new ManiaModRandom();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Mirror))
|
||||
if (mods.HasFlag(LegacyMods.Mirror))
|
||||
yield return new ManiaModMirror();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModEasy(),
|
||||
new ManiaModNoFail(),
|
||||
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
|
||||
new ManiaModNoRelease(),
|
||||
};
|
||||
|
||||
case ModType.DifficultyIncrease:
|
||||
@ -419,9 +419,10 @@ namespace osu.Game.Rulesets.Mania
|
||||
return new ManiaFilterCriteria();
|
||||
}
|
||||
|
||||
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
||||
|
||||
public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
||||
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||
[
|
||||
new ManiaDifficultySection(),
|
||||
];
|
||||
|
||||
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
||||
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert), typeof(ManiaModNoRelease) };
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
|
110
osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs
Normal file
110
osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.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;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public partial class ManiaModNoRelease : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||
{
|
||||
public override string Name => "No Release";
|
||||
|
||||
public override string Acronym => "NR";
|
||||
|
||||
public override LocalisableString Description => "No more timing the end of hold notes.";
|
||||
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
var hitObjects = maniaBeatmap.HitObjects.Select(obj =>
|
||||
{
|
||||
if (obj is HoldNote hold)
|
||||
return new NoReleaseHoldNote(hold);
|
||||
|
||||
return obj;
|
||||
}).ToList();
|
||||
|
||||
maniaBeatmap.HitObjects = hitObjects;
|
||||
}
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||
{
|
||||
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
||||
|
||||
foreach (var stage in maniaRuleset.Playfield.Stages)
|
||||
{
|
||||
foreach (var column in stage.Columns)
|
||||
{
|
||||
column.RegisterPool<NoReleaseTailNote, NoReleaseDrawableHoldNoteTail>(10, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class NoReleaseDrawableHoldNoteTail : DrawableHoldNoteTail
|
||||
{
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
// apply perfect once the tail is reached
|
||||
if (HoldNote.HoldStartTime != null && timeOffset >= 0)
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
else
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private class NoReleaseTailNote : TailNote
|
||||
{
|
||||
}
|
||||
|
||||
private class NoReleaseHoldNote : HoldNote
|
||||
{
|
||||
public NoReleaseHoldNote(HoldNote hold)
|
||||
{
|
||||
StartTime = hold.StartTime;
|
||||
Duration = hold.Duration;
|
||||
Column = hold.Column;
|
||||
NodeSamples = hold.NodeSamples;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
AddNested(Head = new HeadNote
|
||||
{
|
||||
StartTime = StartTime,
|
||||
Column = Column,
|
||||
Samples = GetNodeSamples(0),
|
||||
});
|
||||
|
||||
AddNested(Tail = new NoReleaseTailNote
|
||||
{
|
||||
StartTime = EndTime,
|
||||
Column = Column,
|
||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||
});
|
||||
|
||||
AddNested(Body = new HoldNoteBody
|
||||
{
|
||||
StartTime = StartTime,
|
||||
Column = Column
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -268,11 +268,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
ApplyMaxResult();
|
||||
else
|
||||
MissForcefully();
|
||||
}
|
||||
|
||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||
if (Tail.AllJudged && !Body.AllJudged)
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||
if (!Body.AllJudged)
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
|
||||
// Important that this is always called when a result is applied.
|
||||
endHold();
|
||||
}
|
||||
}
|
||||
|
||||
public override void MissForcefully()
|
||||
|
@ -72,18 +72,18 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// <summary>
|
||||
/// The head note of the hold.
|
||||
/// </summary>
|
||||
public HeadNote Head { get; private set; }
|
||||
public HeadNote Head { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tail note of the hold.
|
||||
/// </summary>
|
||||
public TailNote Tail { get; private set; }
|
||||
public TailNote Tail { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body of the hold.
|
||||
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
|
||||
/// </summary>
|
||||
public HoldNoteBody Body { get; private set; }
|
||||
public HoldNoteBody Body { get; protected set; }
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
|
@ -17,28 +17,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
|
||||
|
||||
private readonly ManiaAction[] columnActions;
|
||||
|
||||
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
columnActions = new ManiaAction[Beatmap.TotalColumns];
|
||||
|
||||
var normalAction = ManiaAction.Key1;
|
||||
var specialAction = ManiaAction.Special1;
|
||||
int totalCounter = 0;
|
||||
|
||||
foreach (var stage in Beatmap.Stages)
|
||||
{
|
||||
for (int i = 0; i < stage.Columns; i++)
|
||||
{
|
||||
if (stage.IsSpecialColumn(i))
|
||||
columnActions[totalCounter] = specialAction++;
|
||||
else
|
||||
columnActions[totalCounter] = normalAction++;
|
||||
totalCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void GenerateFrames()
|
||||
@ -57,11 +38,11 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
switch (point)
|
||||
{
|
||||
case HitPoint:
|
||||
actions.Add(columnActions[point.Column]);
|
||||
actions.Add(ManiaAction.Key1 + point.Column);
|
||||
break;
|
||||
|
||||
case ReleasePoint:
|
||||
actions.Remove(columnActions[point.Column]);
|
||||
actions.Remove(ManiaAction.Key1 + point.Column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
// 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.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
|
||||
@ -27,118 +25,27 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
var normalAction = ManiaAction.Key1;
|
||||
var specialAction = ManiaAction.Special1;
|
||||
|
||||
var action = ManiaAction.Key1;
|
||||
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
|
||||
int counter = 0;
|
||||
|
||||
while (activeColumns > 0)
|
||||
{
|
||||
bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter);
|
||||
|
||||
if ((activeColumns & 1) > 0)
|
||||
Actions.Add(isSpecial ? specialAction : normalAction);
|
||||
Actions.Add(action);
|
||||
|
||||
if (isSpecial)
|
||||
specialAction++;
|
||||
else
|
||||
normalAction++;
|
||||
|
||||
counter++;
|
||||
action++;
|
||||
activeColumns >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
|
||||
int keys = 0;
|
||||
|
||||
foreach (var action in Actions)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ManiaAction.Special1:
|
||||
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0);
|
||||
break;
|
||||
|
||||
case ManiaAction.Special2:
|
||||
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
// the index in lazer, which doesn't include special keys.
|
||||
int nonSpecialKeyIndex = action - ManiaAction.Key1;
|
||||
|
||||
// the index inclusive of special keys.
|
||||
int overallIndex = 0;
|
||||
|
||||
// iterate to find the index including special keys.
|
||||
for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++)
|
||||
{
|
||||
// skip over special columns.
|
||||
if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex))
|
||||
continue;
|
||||
// found a non-special column to use.
|
||||
if (nonSpecialKeyIndex == 0)
|
||||
break;
|
||||
// found a non-special column but not ours.
|
||||
nonSpecialKeyIndex--;
|
||||
}
|
||||
|
||||
keys |= 1 << overallIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
keys |= 1 << (int)action;
|
||||
|
||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the overall index (across all stages) for a specified special key.
|
||||
/// </summary>
|
||||
/// <param name="maniaBeatmap">The beatmap.</param>
|
||||
/// <param name="specialOffset">The special key offset (0 is S1).</param>
|
||||
/// <returns>The overall index for the special column.</returns>
|
||||
private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset)
|
||||
{
|
||||
for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
|
||||
{
|
||||
if (isColumnAtIndexSpecial(maniaBeatmap, i))
|
||||
{
|
||||
if (specialOffset == 0)
|
||||
return i;
|
||||
|
||||
specialOffset--;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("Special key index is too high.", nameof(specialOffset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the column at an overall index (across all stages) is a special column.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap.</param>
|
||||
/// <param name="index">The overall index to check.</param>
|
||||
private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index)
|
||||
{
|
||||
foreach (var stage in beatmap.Stages)
|
||||
{
|
||||
if (index >= stage.Columns)
|
||||
{
|
||||
index -= stage.Columns;
|
||||
continue;
|
||||
}
|
||||
|
||||
return stage.IsSpecialColumn(index);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Column index is too high.", nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
LeftKeys = leftKeys,
|
||||
RightKeys = rightKeys,
|
||||
SpecialKey = InputKey.Space,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1,
|
||||
}.GenerateKeyBindingsFor(variant, out _);
|
||||
}.GenerateKeyBindingsFor(variant);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonManiaComboCounter : ArgonComboCounter
|
||||
{
|
||||
protected override bool DisplayXSymbol => false;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
private IBindable<ScrollingDirection> direction = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// the logic of flipping the position of the combo counter w.r.t. the direction does not work with "Closest" anchor,
|
||||
// because it always forces the anchor to be top or bottom based on scrolling direction.
|
||||
UsesFixedAnchor = true;
|
||||
|
||||
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||
direction.BindValueChanged(_ => updateAnchor());
|
||||
|
||||
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||
// which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer.
|
||||
Schedule(() => Schedule(updateAnchor));
|
||||
}
|
||||
|
||||
private void updateAnchor()
|
||||
{
|
||||
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||
if (!Anchor.HasFlag(Anchor.y1))
|
||||
{
|
||||
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||
}
|
||||
|
||||
// change the sign of the Y coordinate in line with the scrolling direction.
|
||||
// i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here.
|
||||
Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -26,6 +28,37 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle per ruleset defaults here.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
||||
|
||||
if (combo != null)
|
||||
{
|
||||
combo.ShowLabel.Value = false;
|
||||
combo.Anchor = Anchor.TopCentre;
|
||||
combo.Origin = Anchor.Centre;
|
||||
combo.Y = 200;
|
||||
}
|
||||
})
|
||||
{
|
||||
new ArgonManiaComboCounter(),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
|
||||
|
@ -65,11 +65,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||
|
||||
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d =>
|
||||
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength)?.With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
d.Origin = Anchor.Centre;
|
||||
d.Blending = BlendingParameters.Additive;
|
||||
d.Scale = new Vector2(lightScale);
|
||||
@ -91,11 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30).With(d =>
|
||||
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30)?.With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
if (d is TextureAnimation animation)
|
||||
animation.IsPlaying = false;
|
||||
|
||||
@ -245,7 +239,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
// i dunno this looks about right??
|
||||
// the guard against zero draw height is intended for zero-length hold notes. yes, such cases have been spotted in the wild.
|
||||
if (sprite.DrawHeight > 0)
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * MathF.Max(1, 32800 / sprite.DrawHeight));
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -43,11 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||
|
||||
explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d =>
|
||||
explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength)?.With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
d.Origin = Anchor.Centre;
|
||||
d.Blending = BlendingParameters.Additive;
|
||||
d.Scale = new Vector2(explosionScale);
|
||||
|
@ -0,0 +1,91 @@
|
||||
// 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.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyManiaComboCounter : LegacyComboCounter
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
DisplayedCountText.Anchor = Anchor.Centre;
|
||||
DisplayedCountText.Origin = Anchor.Centre;
|
||||
|
||||
PopOutCountText.Anchor = Anchor.Centre;
|
||||
PopOutCountText.Origin = Anchor.Centre;
|
||||
PopOutCountText.Colour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
private IBindable<ScrollingDirection> direction = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||
direction.BindValueChanged(_ => updateAnchor());
|
||||
|
||||
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||
// which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer.
|
||||
Schedule(() => Schedule(updateAnchor));
|
||||
}
|
||||
|
||||
private void updateAnchor()
|
||||
{
|
||||
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||
if (!Anchor.HasFlag(Anchor.y1))
|
||||
{
|
||||
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||
}
|
||||
|
||||
// since we flip the vertical anchor when changing scroll direction,
|
||||
// we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
|
||||
if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
|
||||
Y = -Y;
|
||||
}
|
||||
|
||||
protected override void OnCountIncrement()
|
||||
{
|
||||
base.OnCountIncrement();
|
||||
|
||||
PopOutCountText.Hide();
|
||||
DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f))
|
||||
.ScaleTo(new Vector2(1f), 300, Easing.Out)
|
||||
.FadeIn(120);
|
||||
}
|
||||
|
||||
protected override void OnCountChange()
|
||||
{
|
||||
base.OnCountChange();
|
||||
|
||||
PopOutCountText.Hide();
|
||||
DisplayedCountText.ScaleTo(1f);
|
||||
}
|
||||
|
||||
protected override void OnCountRolling()
|
||||
{
|
||||
if (DisplayedCount > 0)
|
||||
{
|
||||
PopOutCountText.Text = FormatCount(DisplayedCount);
|
||||
PopOutCountText.FadeTo(0.8f).FadeOut(200)
|
||||
.ScaleTo(1f).ScaleTo(4f, 200);
|
||||
|
||||
DisplayedCountText.FadeTo(0.5f, 300);
|
||||
}
|
||||
|
||||
base.OnCountRolling();
|
||||
}
|
||||
}
|
||||
}
|
@ -28,13 +28,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
string bottomImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
|
||||
?? "mania-stage-bottom";
|
||||
|
||||
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
d.Scale = new Vector2(1.6f);
|
||||
});
|
||||
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => d.Scale = new Vector2(1.6f));
|
||||
|
||||
if (sprite != null)
|
||||
InternalChild = sprite;
|
||||
|
@ -5,9 +5,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -78,6 +80,40 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Modifications for global components.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||
if (!IsProvidingLegacyResources)
|
||||
return null;
|
||||
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
||||
|
||||
if (combo != null)
|
||||
{
|
||||
combo.Anchor = Anchor.TopCentre;
|
||||
combo.Origin = Anchor.Centre;
|
||||
combo.Y = this.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
|
||||
}
|
||||
})
|
||||
{
|
||||
new LegacyManiaComboCounter(),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
return getResult(resultComponent.Component);
|
||||
|
||||
|
@ -5,22 +5,12 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public partial class DrawableManiaJudgement : DrawableJudgement
|
||||
{
|
||||
public DrawableManiaJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
||||
: base(result, judgedObject)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableManiaJudgement()
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||
|
||||
private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||
|
@ -8,9 +8,10 @@ using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Input.Handlers;
|
||||
@ -56,13 +57,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||
private readonly BindableInt configScrollSpeed = new BindableInt();
|
||||
private double smoothTimeRange;
|
||||
|
||||
private double currentTimeRange;
|
||||
protected double TargetTimeRange;
|
||||
|
||||
// Stores the current speed adjustment active in gameplay.
|
||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||
|
||||
private ISkinSource currentSkin = null!;
|
||||
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; } = null!;
|
||||
|
||||
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
@ -101,9 +107,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||
|
||||
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
|
||||
configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
|
||||
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
|
||||
|
||||
TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||
|
||||
KeyBindingInputManager.Add(new ManiaTouchInputArea());
|
||||
}
|
||||
@ -144,7 +150,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
// This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position.
|
||||
float scale = lengthToHitPosition / length_to_default_hit_position;
|
||||
|
||||
TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||
// we're intentionally using the game host's update clock here to decouple the time range tween from the gameplay clock (which can be arbitrarily paused, or even rewinding)
|
||||
currentTimeRange = Interpolation.DampContinuously(currentTimeRange, TargetTimeRange, 50, gameHost.UpdateThread.Clock.ElapsedFrameTime);
|
||||
TimeRange.Value = currentTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -66,13 +66,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
Content = new[] { new Drawable[stageDefinitions.Count] }
|
||||
});
|
||||
|
||||
var normalColumnAction = ManiaAction.Key1;
|
||||
var specialColumnAction = ManiaAction.Special1;
|
||||
var columnAction = ManiaAction.Key1;
|
||||
int firstColumnIndex = 0;
|
||||
|
||||
for (int i = 0; i < stageDefinitions.Count; i++)
|
||||
{
|
||||
var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
|
||||
var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref columnAction);
|
||||
|
||||
playfieldGrid.Content[0][i] = newStage;
|
||||
|
||||
|
@ -192,12 +192,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
if (press)
|
||||
{
|
||||
inputManager?.KeyBindingContainer?.TriggerPressed(Action.Value);
|
||||
inputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
|
||||
highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputManager?.KeyBindingContainer?.TriggerReleased(Action.Value);
|
||||
inputManager?.KeyBindingContainer.TriggerReleased(Action.Value);
|
||||
highlightOverlay.FadeTo(0, 400, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private ISkinSource currentSkin = null!;
|
||||
|
||||
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction columnStartAction)
|
||||
{
|
||||
this.firstColumnIndex = firstColumnIndex;
|
||||
Definition = definition;
|
||||
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 1,
|
||||
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
|
||||
Action = { Value = columnStartAction++ }
|
||||
};
|
||||
|
||||
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
||||
|
@ -26,37 +26,30 @@ namespace osu.Game.Rulesets.Mania
|
||||
public InputKey SpecialKey;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
||||
/// The <see cref="ManiaAction"/> at which the columns should begin.
|
||||
/// </summary>
|
||||
public ManiaAction NormalActionStart;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> for the special column.
|
||||
/// </summary>
|
||||
public ManiaAction SpecialAction;
|
||||
public ManiaAction ActionStart;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||
/// </summary>
|
||||
/// <param name="columns">The number of columns that need to be bound.</param>
|
||||
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
||||
/// <returns>The keybindings.</returns>
|
||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns)
|
||||
{
|
||||
ManiaAction currentNormalAction = NormalActionStart;
|
||||
ManiaAction currentAction = ActionStart;
|
||||
|
||||
var bindings = new List<KeyBinding>();
|
||||
|
||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
||||
bindings.Add(new KeyBinding(LeftKeys[i], currentAction++));
|
||||
|
||||
if (columns % 2 == 1)
|
||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||
bindings.Add(new KeyBinding(SpecialKey, currentAction++));
|
||||
|
||||
for (int i = 0; i < columns / 2; i++)
|
||||
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||
bindings.Add(new KeyBinding(RightKeys[i], currentAction++));
|
||||
|
||||
nextNormalAction = currentNormalAction;
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
|
||||
mergeSelection();
|
||||
|
||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems?.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
mergeSelection();
|
||||
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
||||
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
||||
@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
|
||||
mergeSelection();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
@ -8,7 +9,9 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -25,22 +28,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
|
||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(true);
|
||||
gridActive<RectangularPositionSnapGrid>(true);
|
||||
|
||||
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
rectangularGridActive(true);
|
||||
gridActive<RectangularPositionSnapGrid>(true);
|
||||
|
||||
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -117,33 +120,58 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[Test]
|
||||
public void TestGridSnapMomentaryToggle()
|
||||
{
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||
rectangularGridActive(true);
|
||||
gridActive<RectangularPositionSnapGrid>(true);
|
||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
}
|
||||
|
||||
private void rectangularGridActive(bool active)
|
||||
private void gridActive<T>(bool active) where T : PositionSnapGrid
|
||||
{
|
||||
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move cursor to (1, 1)", () =>
|
||||
AddStep("move cursor to spacing + (1, 1)", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single();
|
||||
InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1)));
|
||||
var composer = Editor.ChildrenOfType<T>().Single();
|
||||
InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1)));
|
||||
});
|
||||
|
||||
if (active)
|
||||
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
||||
{
|
||||
AddAssert("placement blueprint at spacing + (0, 0)", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<T>().Single();
|
||||
return Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position,
|
||||
uniqueSnappingPosition(composer));
|
||||
});
|
||||
}
|
||||
else
|
||||
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
||||
{
|
||||
AddAssert("placement blueprint at spacing + (1, 1)", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<T>().Single();
|
||||
return Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position,
|
||||
uniqueSnappingPosition(composer) + new Vector2(1, 1));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 uniqueSnappingPosition(PositionSnapGrid grid)
|
||||
{
|
||||
return grid switch
|
||||
{
|
||||
RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value),
|
||||
TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value),
|
||||
CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45),
|
||||
_ => Vector2.Zero
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGridSizeToggling()
|
||||
{
|
||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Any());
|
||||
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Any());
|
||||
gridSizeIs(4);
|
||||
|
||||
nextGridSizeIs(8);
|
||||
@ -159,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
}
|
||||
|
||||
private void gridSizeIs(int size)
|
||||
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single().Spacing == new Vector2(size)
|
||||
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Single().Spacing.Value == new Vector2(size)
|
||||
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
@ -177,6 +178,79 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addAssertPointPositionChanged(points, i);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangingControlPointTypeViaTab()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.LINEAR);
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200));
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
AddStep("select first control point", () => visualiser.Pieces[0].IsSelected.Value = true);
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
assertControlPointPathType(0, PathType.BEZIER);
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.LINEAR);
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.BSpline(4));
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointPathType(2, PathType.BSpline(4));
|
||||
|
||||
AddStep("select third last control point", () =>
|
||||
{
|
||||
visualiser.Pieces[0].IsSelected.Value = false;
|
||||
visualiser.Pieces[2].IsSelected.Value = true;
|
||||
});
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(2, PathType.PERFECT_CURVE);
|
||||
|
||||
AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
|
||||
assertControlPointPathType(0, PathType.BEZIER);
|
||||
assertControlPointPathType(2, null);
|
||||
|
||||
AddStep("select first and third control points", () =>
|
||||
{
|
||||
visualiser.Pieces[0].IsSelected.Value = true;
|
||||
visualiser.Pieces[2].IsSelected.Value = true;
|
||||
});
|
||||
AddStep("press alt-1", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.Key(Key.Number1);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.LINEAR);
|
||||
assertControlPointPathType(2, PathType.LINEAR);
|
||||
}
|
||||
|
||||
private void addAssertPointPositionChanged(Vector2[] points, int index)
|
||||
{
|
||||
AddAssert($"Point at {points.ElementAt(index)} changed",
|
||||
|
@ -2,13 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(200);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -71,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -89,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -111,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(4);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100, 100));
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -130,8 +133,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -149,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertLength(100);
|
||||
}
|
||||
|
||||
@ -171,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -195,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(4);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -215,8 +218,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -239,8 +242,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(4);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -268,8 +271,54 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointPosition(3, new Vector2(200, 100));
|
||||
assertControlPointPosition(4, new Vector2(200));
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(2, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualPathTypeControlViaKeyboard()
|
||||
{
|
||||
addMovementStep(new Vector2(200));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(300, 200));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(300));
|
||||
|
||||
assertControlPointTypeDuringPlacement(0, PathType.PERFECT_CURVE);
|
||||
|
||||
AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
|
||||
assertControlPointTypeDuringPlacement(0, PathType.LINEAR);
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
|
||||
|
||||
AddStep("press alt-2", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.Key(Key.Number2);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
assertControlPointTypeDuringPlacement(0, PathType.BEZIER);
|
||||
|
||||
AddStep("start new segment via S", () => InputManager.Key(Key.S));
|
||||
assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
|
||||
|
||||
addMovementStep(new Vector2(400, 300));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(400));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -293,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addClickStep(MouseButton.Right);
|
||||
assertPlaced(true);
|
||||
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -312,11 +361,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(808, tolerance: 10);
|
||||
assertControlPointCount(5);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, null);
|
||||
assertControlPointType(2, null);
|
||||
assertControlPointType(3, null);
|
||||
assertControlPointType(4, null);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(1, null);
|
||||
assertFinalControlPointType(2, null);
|
||||
assertFinalControlPointType(3, null);
|
||||
assertFinalControlPointType(4, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -337,10 +386,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(600, tolerance: 10);
|
||||
assertControlPointCount(4);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, PathType.BSpline(4));
|
||||
assertControlPointType(2, PathType.BSpline(4));
|
||||
assertControlPointType(3, null);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(1, PathType.BSpline(4));
|
||||
assertFinalControlPointType(2, PathType.BSpline(4));
|
||||
assertFinalControlPointType(3, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -359,7 +408,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -379,7 +428,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -400,7 +449,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -421,7 +470,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -438,7 +487,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||
@ -454,7 +503,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected));
|
||||
|
||||
private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
||||
private void assertControlPointTypeDuringPlacement(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}",
|
||||
() => this.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(index).ControlPoint.Type, () => Is.EqualTo(type));
|
||||
|
||||
private void assertFinalControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
||||
|
||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1));
|
||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
|
@ -161,9 +161,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
pressed = value;
|
||||
if (value)
|
||||
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
||||
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton));
|
||||
else
|
||||
OnReleased(new KeyBindingReleaseEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
||||
OnReleased(new KeyBindingReleaseEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private void scheduleHit() => AddStep("schedule action", () =>
|
||||
{
|
||||
double delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
|
||||
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay);
|
||||
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton)), delay);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||
Add(slider);
|
||||
slider.HitObject.NodeSamples.Clear();
|
||||
});
|
||||
|
||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -41,22 +42,27 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
base.PostProcess();
|
||||
|
||||
var osuBeatmap = (Beatmap<OsuHitObject>)Beatmap;
|
||||
ApplyStacking(Beatmap);
|
||||
}
|
||||
|
||||
if (osuBeatmap.HitObjects.Count > 0)
|
||||
internal static void ApplyStacking(IBeatmap beatmap)
|
||||
{
|
||||
var hitObjects = beatmap.HitObjects as List<OsuHitObject> ?? beatmap.HitObjects.OfType<OsuHitObject>().ToList();
|
||||
|
||||
if (hitObjects.Count > 0)
|
||||
{
|
||||
// Reset stacking
|
||||
foreach (var h in osuBeatmap.HitObjects)
|
||||
foreach (var h in hitObjects)
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
else
|
||||
applyStackingOld(osuBeatmap);
|
||||
applyStackingOld(beatmap.BeatmapInfo, hitObjects);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)
|
||||
private static void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
||||
@ -64,24 +70,24 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
|
||||
int extendedEndIndex = endIndex;
|
||||
|
||||
if (endIndex < beatmap.HitObjects.Count - 1)
|
||||
if (endIndex < hitObjects.Count - 1)
|
||||
{
|
||||
// Extend the end index to include objects they are stacked on
|
||||
for (int i = endIndex; i >= startIndex; i--)
|
||||
{
|
||||
int stackBaseIndex = i;
|
||||
|
||||
for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
|
||||
for (int n = stackBaseIndex + 1; n < hitObjects.Count; n++)
|
||||
{
|
||||
OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex];
|
||||
OsuHitObject stackBaseObject = hitObjects[stackBaseIndex];
|
||||
if (stackBaseObject is Spinner) break;
|
||||
|
||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner)
|
||||
continue;
|
||||
|
||||
double endTime = stackBaseObject.GetEndTime();
|
||||
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
||||
double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency;
|
||||
|
||||
if (objectN.StartTime - endTime > stackThreshold)
|
||||
// We are no longer within stacking range of the next object.
|
||||
@ -100,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
if (stackBaseIndex > extendedEndIndex)
|
||||
{
|
||||
extendedEndIndex = stackBaseIndex;
|
||||
if (extendedEndIndex == beatmap.HitObjects.Count - 1)
|
||||
if (extendedEndIndex == hitObjects.Count - 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -123,10 +129,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
* 2 and 1 will be ignored in the i loop because they already have a stack value.
|
||||
*/
|
||||
|
||||
OsuHitObject objectI = beatmap.HitObjects[i];
|
||||
OsuHitObject objectI = hitObjects[i];
|
||||
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
|
||||
|
||||
double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
||||
double stackThreshold = objectI.TimePreempt * beatmapInfo.StackLeniency;
|
||||
|
||||
/* If this object is a hitcircle, then we enter this "special" case.
|
||||
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
|
||||
@ -136,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
while (--n >= 0)
|
||||
{
|
||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
double endTime = objectN.GetEndTime();
|
||||
@ -164,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
for (int j = n + 1; j <= i; j++)
|
||||
{
|
||||
// For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
|
||||
OsuHitObject objectJ = beatmap.HitObjects[j];
|
||||
OsuHitObject objectJ = hitObjects[j];
|
||||
if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
|
||||
objectJ.StackHeight -= offset;
|
||||
}
|
||||
@ -191,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
*/
|
||||
while (--n >= startIndex)
|
||||
{
|
||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
if (objectI.StartTime - objectN.StartTime > stackThreshold)
|
||||
@ -208,11 +214,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private void applyStackingOld(Beatmap<OsuHitObject> beatmap)
|
||||
private static void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
||||
{
|
||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||
for (int i = 0; i < hitObjects.Count; i++)
|
||||
{
|
||||
OsuHitObject currHitObject = beatmap.HitObjects[i];
|
||||
OsuHitObject currHitObject = hitObjects[i];
|
||||
|
||||
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
|
||||
continue;
|
||||
@ -220,11 +226,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
double startTime = currHitObject.GetEndTime();
|
||||
int sliderStack = 0;
|
||||
|
||||
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
|
||||
for (int j = i + 1; j < hitObjects.Count; j++)
|
||||
{
|
||||
double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
||||
double stackThreshold = hitObjects[i].TimePreempt * beatmapInfo.StackLeniency;
|
||||
|
||||
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
|
||||
if (hitObjects[j].StartTime - stackThreshold > startTime)
|
||||
break;
|
||||
|
||||
// The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
|
||||
@ -239,17 +245,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
// Effects of this can be seen on https://osu.ppy.sh/beatmapsets/243#osu/1146 at sliders around 86647 ms, where
|
||||
// if we use `EndTime` here it would result in unexpected stacking.
|
||||
|
||||
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||
if (Vector2Extensions.Distance(hitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||
{
|
||||
currHitObject.StackHeight++;
|
||||
startTime = beatmap.HitObjects[j].StartTime;
|
||||
startTime = hitObjects[j].StartTime;
|
||||
}
|
||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
|
||||
else if (Vector2Extensions.Distance(hitObjects[j].Position, position2) < stack_distance)
|
||||
{
|
||||
// Case for sliders - bump notes down and right, rather than up and left.
|
||||
sliderStack++;
|
||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = beatmap.HitObjects[j].StartTime;
|
||||
hitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = hitObjects[j].StartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
flashlightRating *= 0.7;
|
||||
}
|
||||
|
||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
|
||||
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
|
||||
double baseFlashlightPerformance = 0.0;
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0;
|
||||
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||
|
||||
double basePerformance =
|
||||
Math.Pow(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user