mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 19:44:22 +08:00
Compare commits
1017 Commits
@@ -13,6 +13,24 @@
|
||||
"commands": [
|
||||
"dotnet-format"
|
||||
]
|
||||
},
|
||||
"jetbrains.resharper.globaltools": {
|
||||
"version": "2020.2.4",
|
||||
"commands": [
|
||||
"jb"
|
||||
]
|
||||
},
|
||||
"nvika": {
|
||||
"version": "2.0.0",
|
||||
"commands": [
|
||||
"nvika"
|
||||
]
|
||||
},
|
||||
"codefilesanity": {
|
||||
"version": "15.0.0",
|
||||
"commands": [
|
||||
"CodeFileSanity"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,3 +334,5 @@ inspectcode
|
||||
|
||||
# BenchmarkDotNet
|
||||
/BenchmarkDotNet.Artifacts
|
||||
|
||||
*.GeneratedMSBuildEditorConfig.editorconfig
|
||||
|
||||
Vendored
+25
-42
@@ -9,11 +9,10 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Desktop",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -24,12 +23,11 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Desktop",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -40,11 +38,10 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Tests",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -55,12 +52,11 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Tests",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -71,11 +67,10 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Tournament.Tests",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -86,12 +81,11 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Tournament.Tests",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -102,25 +96,14 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Benchmarks",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Restore (netcoreapp3.1)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"restore",
|
||||
"build/Desktop.proj"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,5 +4,6 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(
|
||||
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
||||
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
||||
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:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
|
||||
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||
|
||||
@@ -75,7 +75,6 @@ git pull
|
||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
|
||||
|
||||
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
|
||||
- Visual Studio Code users must run the `Restore` task before any build attempt.
|
||||
|
||||
You can also build and run *osu!* from the command-line with a single command:
|
||||
|
||||
|
||||
+6
-17
@@ -1,7 +1,4 @@
|
||||
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
|
||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3"
|
||||
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ARGUMENTS
|
||||
@@ -18,23 +15,15 @@ var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
|
||||
// TASKS
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// windows only because both inspectcode and nvika depend on net45
|
||||
Task("InspectCode")
|
||||
.WithCriteria(IsRunningOnWindows())
|
||||
.Does(() => {
|
||||
InspectCode(desktopSlnf, new InspectCodeSettings {
|
||||
CachesHome = "inspectcode",
|
||||
OutputFile = "inspectcodereport.xml",
|
||||
ArgumentCustomization = arg => {
|
||||
if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output
|
||||
arg.Append("--verbosity:WARN");
|
||||
return arg;
|
||||
},
|
||||
});
|
||||
var inspectcodereport = "inspectcodereport.xml";
|
||||
var cacheDir = "inspectcode";
|
||||
var verbosity = AppVeyor.IsRunningOnAppVeyor ? "WARN" : "INFO"; // Don't flood CI output
|
||||
|
||||
int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
|
||||
if (returnCode != 0)
|
||||
throw new Exception($"inspectcode failed with return code {returnCode}");
|
||||
DotNetCoreTool(rootDirectory.FullPath,
|
||||
"jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity={verbosity}");
|
||||
DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
|
||||
});
|
||||
|
||||
Task("CodeFileSanity")
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
"version": "3.1.100"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "2.1.1"
|
||||
"Microsoft.Build.Traversal": "2.2.3"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1016.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1029.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1127.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -135,6 +135,9 @@ namespace osu.Desktop
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
return edit.Beatmap.ToString();
|
||||
|
||||
case UserActivity.InLobby lobby:
|
||||
return lobby.Room.Name.Value;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace osu.Desktop
|
||||
switch (host.Window)
|
||||
{
|
||||
// Legacy osuTK DesktopGameWindow
|
||||
case DesktopGameWindow desktopGameWindow:
|
||||
case OsuTKDesktopWindow desktopGameWindow:
|
||||
desktopGameWindow.CursorState |= CursorState.Hidden;
|
||||
desktopGameWindow.SetIconFromStream(iconStream);
|
||||
desktopGameWindow.Title = Name;
|
||||
@@ -139,7 +139,7 @@ namespace osu.Desktop
|
||||
|
||||
// SDL2 DesktopWindow
|
||||
case DesktopWindow desktopWindow:
|
||||
desktopWindow.CursorState.Value |= CursorState.Hidden;
|
||||
desktopWindow.CursorState |= CursorState.Hidden;
|
||||
desktopWindow.SetIconFromStream(iconStream);
|
||||
desktopWindow.Title = Name;
|
||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||
|
||||
+7
-18
@@ -9,11 +9,10 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Catch.Tests.csproj",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -24,24 +23,14 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Catch.Tests.csproj",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Restore",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"restore"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,17 +12,32 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestFixture]
|
||||
public class CatchLegacyModConversionTest : LegacyModConversionTest
|
||||
{
|
||||
[TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })]
|
||||
[TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })]
|
||||
[TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })]
|
||||
[TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })]
|
||||
private static readonly object[][] catch_mod_mapping =
|
||||
{
|
||||
new object[] { LegacyMods.NoFail, new[] { typeof(CatchModNoFail) } },
|
||||
new object[] { LegacyMods.Easy, new[] { typeof(CatchModEasy) } },
|
||||
new object[] { LegacyMods.Hidden, new[] { typeof(CatchModHidden) } },
|
||||
new object[] { LegacyMods.HardRock, new[] { typeof(CatchModHardRock) } },
|
||||
new object[] { LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) } },
|
||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } },
|
||||
new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } },
|
||||
new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } },
|
||||
new object[] { LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) } },
|
||||
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
|
||||
new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
|
||||
new object[] { LegacyMods.Perfect, new[] { typeof(CatchModPerfect) } },
|
||||
new object[] { LegacyMods.Cinema, new[] { typeof(CatchModCinema) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(catch_mod_mapping))]
|
||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })]
|
||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
|
||||
[TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })]
|
||||
[TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })]
|
||||
[TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })]
|
||||
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
|
||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||
|
||||
[TestCaseSource(nameof(catch_mod_mapping))]
|
||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
}
|
||||
|
||||
@@ -38,17 +38,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
new Fruit
|
||||
{
|
||||
X = 0,
|
||||
StartTime = 250
|
||||
StartTime = 1000
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.WIDTH,
|
||||
StartTime = 500
|
||||
StartTime = 2000
|
||||
},
|
||||
new JuiceStream
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 750,
|
||||
StartTime = 3000,
|
||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,71 +18,42 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
base.LoadComplete();
|
||||
|
||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||
AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
|
||||
AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep)));
|
||||
|
||||
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||
|
||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
|
||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true)));
|
||||
|
||||
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||
}
|
||||
|
||||
private Drawable createDrawableTinyDroplet()
|
||||
private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) =>
|
||||
setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash);
|
||||
|
||||
private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash);
|
||||
|
||||
private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet()));
|
||||
|
||||
private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false)
|
||||
{
|
||||
var droplet = new TestCatchTinyDroplet
|
||||
{
|
||||
Scale = 1.5f,
|
||||
};
|
||||
var hitObject = d.HitObject;
|
||||
hitObject.StartTime = 1000000000000;
|
||||
hitObject.Scale = 1.5f;
|
||||
|
||||
return new DrawableTinyDroplet(droplet)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
RelativePositionAxes = Axes.None,
|
||||
Position = Vector2.Zero,
|
||||
Alpha = 1,
|
||||
LifetimeStart = double.NegativeInfinity,
|
||||
LifetimeEnd = double.PositiveInfinity,
|
||||
};
|
||||
}
|
||||
if (hyperdash)
|
||||
((PalpableCatchHitObject)hitObject).HyperDashTarget = new Banana();
|
||||
|
||||
private Drawable createDrawableDroplet(bool hyperdash = false)
|
||||
{
|
||||
var droplet = new TestCatchDroplet
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.RelativePositionAxes = Axes.None;
|
||||
d.Position = Vector2.Zero;
|
||||
d.HitObjectApplied += _ =>
|
||||
{
|
||||
Scale = 1.5f,
|
||||
HyperDashTarget = hyperdash ? new Banana() : null
|
||||
};
|
||||
|
||||
return new DrawableDroplet(droplet)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
RelativePositionAxes = Axes.None,
|
||||
Position = Vector2.Zero,
|
||||
Alpha = 1,
|
||||
LifetimeStart = double.NegativeInfinity,
|
||||
LifetimeEnd = double.PositiveInfinity,
|
||||
};
|
||||
}
|
||||
|
||||
private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false)
|
||||
{
|
||||
Fruit fruit = new TestCatchFruit(rep)
|
||||
{
|
||||
Scale = 1.5f,
|
||||
HyperDashTarget = hyperdash ? new Banana() : null
|
||||
};
|
||||
|
||||
return new DrawableFruit(fruit)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
RelativePositionAxes = Axes.None,
|
||||
Position = Vector2.Zero,
|
||||
Alpha = 1,
|
||||
LifetimeStart = double.NegativeInfinity,
|
||||
LifetimeEnd = double.PositiveInfinity,
|
||||
d.LifetimeStart = double.NegativeInfinity;
|
||||
d.LifetimeEnd = double.PositiveInfinity;
|
||||
};
|
||||
return d;
|
||||
}
|
||||
|
||||
public class TestCatchFruit : Fruit
|
||||
@@ -90,26 +61,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public TestCatchFruit(FruitVisualRepresentation rep)
|
||||
{
|
||||
VisualRepresentation = rep;
|
||||
StartTime = 1000000000000;
|
||||
}
|
||||
|
||||
public override FruitVisualRepresentation VisualRepresentation { get; }
|
||||
}
|
||||
|
||||
public class TestCatchDroplet : Droplet
|
||||
{
|
||||
public TestCatchDroplet()
|
||||
{
|
||||
StartTime = 1000000000000;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestCatchTinyDroplet : TinyDroplet
|
||||
{
|
||||
public TestCatchTinyDroplet()
|
||||
{
|
||||
StartTime = 1000000000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||
{
|
||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
||||
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||
|
||||
foreach (var currentObject in beatmap.HitObjects)
|
||||
{
|
||||
if (currentObject is Fruit fruitObject)
|
||||
objectWithDroplets.Add(fruitObject);
|
||||
palpableObjects.Add(fruitObject);
|
||||
|
||||
if (currentObject is JuiceStream)
|
||||
{
|
||||
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
|
||||
foreach (var juice in currentObject.NestedHitObjects)
|
||||
{
|
||||
if (!(currentJuiceElement is TinyDroplet))
|
||||
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
|
||||
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||
palpableObjects.Add(palpableObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
|
||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||
|
||||
@@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int lastDirection = 0;
|
||||
double lastExcess = halfCatcherWidth;
|
||||
|
||||
for (int i = 0; i < objectWithDroplets.Count - 1; i++)
|
||||
for (int i = 0; i < palpableObjects.Count - 1; i++)
|
||||
{
|
||||
CatchHitObject currentObject = objectWithDroplets[i];
|
||||
CatchHitObject nextObject = objectWithDroplets[i + 1];
|
||||
var currentObject = palpableObjects[i];
|
||||
var nextObject = palpableObjects[i + 1];
|
||||
|
||||
// Reset variables in-case values have changed (e.g. after applying HR)
|
||||
currentObject.HyperDashTarget = null;
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
{
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
|
||||
public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
|
||||
public new PalpableCatchHitObject BaseObject => (PalpableCatchHitObject)base.BaseObject;
|
||||
|
||||
public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
|
||||
public new PalpableCatchHitObject LastObject => (PalpableCatchHitObject)base.LastObject;
|
||||
|
||||
public readonly float NormalizedPosition;
|
||||
public readonly float LastNormalizedPosition;
|
||||
|
||||
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModEasy : ModEasy
|
||||
public class CatchModEasy : ModEasyWithExtraLives
|
||||
{
|
||||
public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
private const double fade_out_offset_multiplier = 0.6;
|
||||
private const double fade_out_duration_multiplier = 0.44;
|
||||
|
||||
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawable is DrawableCatchHitObject catchDrawable))
|
||||
base.ApplyNormalVisibilityState(hitObject, state);
|
||||
|
||||
if (!(hitObject is DrawableCatchHitObject catchDrawable))
|
||||
return;
|
||||
|
||||
if (catchDrawable.NestedHitObjects.Any())
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class Banana : Fruit
|
||||
public class Banana : Fruit, IHasComboInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of banana in current shower.
|
||||
@@ -26,6 +29,29 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
Samples = samples;
|
||||
}
|
||||
|
||||
private Color4? colour;
|
||||
|
||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
// override any external colour changes with banananana
|
||||
return colour ??= getBananaColour();
|
||||
}
|
||||
|
||||
private Color4 getBananaColour()
|
||||
{
|
||||
switch (RNG.Next(0, 3))
|
||||
{
|
||||
default:
|
||||
return new Color4(255, 240, 0, 255);
|
||||
|
||||
case 1:
|
||||
return new Color4(255, 192, 0, 255);
|
||||
|
||||
case 2:
|
||||
return new Color4(214, 221, 28, 255);
|
||||
}
|
||||
}
|
||||
|
||||
private class BananaHitSampleInfo : HitSampleInfo
|
||||
{
|
||||
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
||||
|
||||
@@ -27,11 +27,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
set => x = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this object can be placed on the catcher's plate.
|
||||
/// </summary>
|
||||
public virtual bool CanBePlated => false;
|
||||
|
||||
/// <summary>
|
||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||
/// </summary>
|
||||
@@ -63,13 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
set => ComboIndexBindable.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Difference between the distance to the next object
|
||||
/// and the distance that would have triggered a hyper dash.
|
||||
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
|
||||
/// </summary>
|
||||
public float DistanceToHyperDash { get; set; }
|
||||
|
||||
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||
|
||||
/// <summary>
|
||||
@@ -83,16 +71,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public float Scale { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this fruit can initiate a hyperdash.
|
||||
/// </summary>
|
||||
public bool HyperDash => HyperDashTarget != null;
|
||||
|
||||
/// <summary>
|
||||
/// The target fruit if we are to initiate a hyperdash.
|
||||
/// </summary>
|
||||
public CatchHitObject HyperDashTarget;
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
@@ -105,14 +83,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single object that can be caught by the catcher.
|
||||
/// </summary>
|
||||
public abstract class PalpableCatchHitObject : CatchHitObject
|
||||
{
|
||||
public override bool CanBePlated => true;
|
||||
}
|
||||
|
||||
public enum FruitVisualRepresentation
|
||||
{
|
||||
Pear,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
@@ -15,14 +13,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
private Color4? colour;
|
||||
|
||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
// override any external colour changes with banananana
|
||||
return colour ??= getBananaColour();
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
base.UpdateInitialTransforms();
|
||||
@@ -46,20 +36,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
if (Samples != null)
|
||||
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
|
||||
}
|
||||
|
||||
private Color4 getBananaColour()
|
||||
{
|
||||
switch (RNG.Next(0, 3))
|
||||
{
|
||||
default:
|
||||
return new Color4(255, 240, 0, 255);
|
||||
|
||||
case 1:
|
||||
return new Color4(255, 192, 0, 255);
|
||||
|
||||
case 2:
|
||||
return new Color4(214, 221, 28, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
|
||||
public class DrawableBananaShower : DrawableCatchHitObject
|
||||
{
|
||||
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||
private readonly Container bananaContainer;
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
switch (hitObject)
|
||||
{
|
||||
case Banana banana:
|
||||
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
||||
return createDrawableRepresentation?.Invoke(banana);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
|
||||
@@ -2,67 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public abstract class PalpableDrawableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
||||
where TObject : PalpableCatchHitObject
|
||||
{
|
||||
protected Container ScaleContainer { get; private set; }
|
||||
|
||||
protected PalpableDrawableCatchHitObject(TObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
Masking = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
ScaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
|
||||
ScaleContainer.Scale = new Vector2(HitObject.Scale);
|
||||
}
|
||||
|
||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours) =>
|
||||
comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count];
|
||||
}
|
||||
|
||||
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
|
||||
where TObject : CatchHitObject
|
||||
{
|
||||
public new TObject HitObject;
|
||||
|
||||
protected DrawableCatchHitObject(TObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
Anchor = Anchor.BottomLeft;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||
{
|
||||
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||
|
||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||
|
||||
@@ -72,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
: base(hitObject)
|
||||
{
|
||||
X = hitObject.X;
|
||||
Anchor = Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
public Func<CatchHitObject, bool> CheckPosition;
|
||||
@@ -88,22 +37,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
{
|
||||
var endTime = HitObject.GetEndTime();
|
||||
|
||||
using (BeginAbsoluteSequence(endTime, true))
|
||||
switch (state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Miss:
|
||||
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
|
||||
break;
|
||||
case ArmedState.Miss:
|
||||
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
|
||||
break;
|
||||
|
||||
case ArmedState.Hit:
|
||||
this.FadeOut();
|
||||
break;
|
||||
}
|
||||
case ArmedState.Hit:
|
||||
this.FadeOut();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableDroplet : PalpableDrawableCatchHitObject<Droplet>
|
||||
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||
{
|
||||
public override bool StaysOnPlate => false;
|
||||
|
||||
public DrawableDroplet(Droplet h)
|
||||
public DrawableDroplet(CatchHitObject h)
|
||||
: base(h)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableFruit : PalpableDrawableCatchHitObject<Fruit>
|
||||
public class DrawableFruit : DrawablePalpableCatchHitObject
|
||||
{
|
||||
public DrawableFruit(Fruit h)
|
||||
public DrawableFruit(CatchHitObject h)
|
||||
: base(h)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
|
||||
public class DrawableJuiceStream : DrawableCatchHitObject
|
||||
{
|
||||
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||
private readonly Container dropletContainer;
|
||||
@@ -47,8 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
switch (hitObject)
|
||||
{
|
||||
case CatchHitObject catchObject:
|
||||
return createDrawableRepresentation?.Invoke(catchObject)?.With(o =>
|
||||
((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
||||
return createDrawableRepresentation?.Invoke(catchObject);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
|
||||
{
|
||||
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
||||
/// </summary>
|
||||
public virtual bool StaysOnPlate => true;
|
||||
|
||||
protected readonly Container ScaleContainer;
|
||||
|
||||
protected DrawablePalpableCatchHitObject(CatchHitObject h)
|
||||
: base(h)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
|
||||
AddInternal(ScaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ScaleContainer.Scale = new Vector2(HitObject.Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +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.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DropletPiece : CompositeDrawable
|
||||
{
|
||||
public DropletPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var hitObject = drawableCatchObject.HitObject;
|
||||
|
||||
InternalChild = new Pulp
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||
};
|
||||
|
||||
if (hitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(2f),
|
||||
Depth = 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
BorderThickness = 6,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0.3f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class BananaPiece : PulpFormation
|
||||
{
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class BorderPiece : Circle
|
||||
{
|
||||
public BorderPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
BorderColour = Color4.White;
|
||||
BorderThickness = 6f * FruitPiece.RADIUS_ADJUST;
|
||||
|
||||
// Border is drawn only when there is a child drawable.
|
||||
Child = new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class DropletPiece : CompositeDrawable
|
||||
{
|
||||
public DropletPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
|
||||
InternalChild = new Pulp
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||
};
|
||||
|
||||
if (drawableCatchObject.HitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new HyperDropletBorderPiece());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-44
@@ -5,12 +5,9 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
internal class FruitPiece : CompositeDrawable
|
||||
{
|
||||
@@ -19,8 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
/// </summary>
|
||||
public const float RADIUS_ADJUST = 1.1f;
|
||||
|
||||
private Circle border;
|
||||
private CatchHitObject hitObject;
|
||||
private BorderPiece border;
|
||||
private PalpableCatchHitObject hitObject;
|
||||
|
||||
public FruitPiece()
|
||||
{
|
||||
@@ -30,52 +27,18 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
hitObject = drawableCatchObject.HitObject;
|
||||
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
||||
border = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = 6f * RADIUS_ADJUST,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
},
|
||||
getFruitFor(hitObject.VisualRepresentation),
|
||||
border = new BorderPiece(),
|
||||
});
|
||||
|
||||
if (hitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
BorderThickness = 12f * RADIUS_ADJUST,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0.3f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
}
|
||||
}
|
||||
});
|
||||
AddInternal(new HyperBorderPiece());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class GrapePiece : PulpFormation
|
||||
{
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.Graphics;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class HyperBorderPiece : BorderPiece
|
||||
{
|
||||
public HyperBorderPiece()
|
||||
{
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||
BorderThickness = 12f * FruitPiece.RADIUS_ADJUST;
|
||||
|
||||
Child.Alpha = 0.3f;
|
||||
Child.Blending = BlendingParameters.Additive;
|
||||
Child.Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class HyperDropletBorderPiece : HyperBorderPiece
|
||||
{
|
||||
public HyperDropletBorderPiece()
|
||||
{
|
||||
Size /= 2;
|
||||
BorderThickness = 6f;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class PearPiece : PulpFormation
|
||||
{
|
||||
+1
-2
@@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class PineapplePiece : PulpFormation
|
||||
{
|
||||
+1
-1
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class PulpFormation : CompositeDrawable
|
||||
{
|
||||
+1
-2
@@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class RaspberryPiece : PulpFormation
|
||||
{
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single object that can be caught by the catcher.
|
||||
/// This includes normal fruits, droplets, and bananas but excludes objects that act only as a container of nested hit objects.
|
||||
/// </summary>
|
||||
public abstract class PalpableCatchHitObject : CatchHitObject, IHasComboInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Difference between the distance to the next object
|
||||
/// and the distance that would have triggered a hyper dash.
|
||||
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
|
||||
/// </summary>
|
||||
public float DistanceToHyperDash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this fruit can initiate a hyperdash.
|
||||
/// </summary>
|
||||
public bool HyperDash => HyperDashTarget != null;
|
||||
|
||||
/// <summary>
|
||||
/// The target fruit if we are to initiate a hyperdash.
|
||||
/// </summary>
|
||||
public CatchHitObject HyperDashTarget;
|
||||
|
||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count];
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
float lastPosition = CatchPlayfield.CENTER_X;
|
||||
double lastTime = 0;
|
||||
|
||||
void moveToNext(CatchHitObject h)
|
||||
void moveToNext(PalpableCatchHitObject h)
|
||||
{
|
||||
float positionChange = Math.Abs(lastPosition - h.X);
|
||||
double timeAvailable = h.StartTime - lastTime;
|
||||
@@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects)
|
||||
{
|
||||
switch (obj)
|
||||
if (obj is PalpableCatchHitObject palpableObject)
|
||||
{
|
||||
case Fruit _:
|
||||
moveToNext(obj);
|
||||
break;
|
||||
moveToNext(palpableObject);
|
||||
}
|
||||
|
||||
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
||||
{
|
||||
switch (nestedObj)
|
||||
if (nestedObj is PalpableCatchHitObject palpableNestedObject)
|
||||
{
|
||||
case Banana _:
|
||||
case TinyDroplet _:
|
||||
case Droplet _:
|
||||
case Fruit _:
|
||||
moveToNext(nestedObj);
|
||||
break;
|
||||
moveToNext(palpableNestedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
if (Position > lastCatchFrame.Position)
|
||||
lastCatchFrame.Actions.Add(CatchAction.MoveRight);
|
||||
else if (Position < lastCatchFrame.Position)
|
||||
Actions.Add(CatchAction.MoveLeft);
|
||||
lastCatchFrame.Actions.Add(CatchAction.MoveLeft);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
|
||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
||||
|
||||
|
||||
@@ -55,21 +55,18 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
HitObjectContainer,
|
||||
CatcherArea,
|
||||
};
|
||||
|
||||
NewResult += onNewResult;
|
||||
RevertResult += onRevertResult;
|
||||
}
|
||||
|
||||
public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
h.OnNewResult += onNewResult;
|
||||
h.OnRevertResult += onRevertResult;
|
||||
|
||||
base.Add(h);
|
||||
|
||||
var fruit = (DrawableCatchHitObject)h;
|
||||
fruit.CheckPosition = CheckIfWeCanCatch;
|
||||
((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
|
||||
}
|
||||
|
||||
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||
|
||||
|
||||
@@ -220,11 +220,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <summary>
|
||||
/// Let the catcher attempt to catch a fruit.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit to catch.</param>
|
||||
/// <param name="hitObject">The fruit to catch.</param>
|
||||
/// <returns>Whether the catch is possible.</returns>
|
||||
public bool AttemptCatch(CatchHitObject fruit)
|
||||
public bool AttemptCatch(CatchHitObject hitObject)
|
||||
{
|
||||
if (!fruit.CanBePlated)
|
||||
if (!(hitObject is PalpableCatchHitObject fruit))
|
||||
return false;
|
||||
|
||||
var halfCatchWidth = catchWidth * 0.5f;
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
};
|
||||
}
|
||||
|
||||
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
||||
{
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
||||
}
|
||||
|
||||
if (result.IsHit && fruit.HitObject.CanBePlated)
|
||||
if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit)
|
||||
{
|
||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
if (caughtFruit == null) return;
|
||||
|
||||
caughtFruit.RelativePositionAxes = Axes.None;
|
||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||
caughtFruit.IsOnPlate = true;
|
||||
|
||||
caughtFruit.Anchor = Anchor.TopCentre;
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
||||
}
|
||||
|
||||
if (fruit.HitObject.LastInCombo)
|
||||
if (hitObject.HitObject.LastInCombo)
|
||||
{
|
||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||
runAfterLoaded(() => MovableCatcher.Explode());
|
||||
@@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
MovableCatcher.Drop();
|
||||
}
|
||||
|
||||
comboDisplay.OnNewResult(fruit, result);
|
||||
comboDisplay.OnNewResult(hitObject, result);
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||
|
||||
+7
-18
@@ -9,11 +9,10 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Mania.Tests.csproj",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -24,24 +23,14 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Mania.Tests.csproj",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Restore",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"restore"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -96,6 +96,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
|
||||
@@ -12,19 +12,44 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestFixture]
|
||||
public class ManiaLegacyModConversionTest : LegacyModConversionTest
|
||||
{
|
||||
[TestCase(LegacyMods.Easy, new[] { typeof(ManiaModEasy) })]
|
||||
[TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) })]
|
||||
[TestCase(LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) })]
|
||||
[TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })]
|
||||
private static readonly object[][] mania_mod_mapping =
|
||||
{
|
||||
new object[] { LegacyMods.NoFail, new[] { typeof(ManiaModNoFail) } },
|
||||
new object[] { LegacyMods.Easy, new[] { typeof(ManiaModEasy) } },
|
||||
new object[] { LegacyMods.Hidden, new[] { typeof(ManiaModHidden) } },
|
||||
new object[] { LegacyMods.HardRock, new[] { typeof(ManiaModHardRock) } },
|
||||
new object[] { LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) } },
|
||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) } },
|
||||
new object[] { LegacyMods.HalfTime, new[] { typeof(ManiaModHalfTime) } },
|
||||
new object[] { LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) } },
|
||||
new object[] { LegacyMods.Flashlight, new[] { typeof(ManiaModFlashlight) } },
|
||||
new object[] { LegacyMods.Autoplay, new[] { typeof(ManiaModAutoplay) } },
|
||||
new object[] { LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) } },
|
||||
new object[] { LegacyMods.Key4, new[] { typeof(ManiaModKey4) } },
|
||||
new object[] { LegacyMods.Key5, new[] { typeof(ManiaModKey5) } },
|
||||
new object[] { LegacyMods.Key6, new[] { typeof(ManiaModKey6) } },
|
||||
new object[] { LegacyMods.Key7, new[] { typeof(ManiaModKey7) } },
|
||||
new object[] { LegacyMods.Key8, new[] { typeof(ManiaModKey8) } },
|
||||
new object[] { LegacyMods.FadeIn, new[] { typeof(ManiaModFadeIn) } },
|
||||
new object[] { LegacyMods.Random, new[] { typeof(ManiaModRandom) } },
|
||||
new object[] { LegacyMods.Cinema, new[] { typeof(ManiaModCinema) } },
|
||||
new object[] { LegacyMods.Key9, new[] { typeof(ManiaModKey9) } },
|
||||
new object[] { LegacyMods.KeyCoop, new[] { typeof(ManiaModDualStages) } },
|
||||
new object[] { LegacyMods.Key1, new[] { typeof(ManiaModKey1) } },
|
||||
new object[] { LegacyMods.Key3, new[] { typeof(ManiaModKey3) } },
|
||||
new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } },
|
||||
new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } }
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(mania_mod_mapping))]
|
||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })]
|
||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })]
|
||||
[TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModFlashlight), typeof(ManiaModNightcore) })]
|
||||
[TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })]
|
||||
[TestCase(LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
|
||||
[TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
|
||||
[TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })]
|
||||
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
|
||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||
|
||||
[TestCaseSource(nameof(mania_mod_mapping))]
|
||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
@@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
/// </summary>
|
||||
public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
SetContents(() => new FillFlowContainer
|
||||
{
|
||||
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
protected abstract DrawableManiaHitObject CreateHitObject();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
if (hitWindows.IsHitResultAllowed(result))
|
||||
{
|
||||
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
|
||||
new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
|
||||
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||
{
|
||||
Type = result
|
||||
}, null)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
||||
@@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -26,6 +27,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFadeOnMiss()
|
||||
{
|
||||
AddStep("miss tick", () =>
|
||||
{
|
||||
foreach (var holdNote in holdNotes)
|
||||
holdNote.ChildrenOfType<DrawableHoldNoteHead>().First().MissForcefully();
|
||||
});
|
||||
}
|
||||
|
||||
private IEnumerable<DrawableHoldNote> holdNotes => CreatedDrawables.SelectMany(d => d.ChildrenOfType<DrawableHoldNote>());
|
||||
|
||||
protected override DrawableManiaHitObject CreateHitObject()
|
||||
{
|
||||
var note = new HoldNote { Duration = 1000 };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@@ -54,12 +55,77 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNoteMissAfterNextObjectStartTime()
|
||||
{
|
||||
var objects = new List<ManiaHitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 1010,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1020,
|
||||
EndTime = 1030
|
||||
}
|
||||
};
|
||||
|
||||
performTest(objects, new List<ReplayFrame>());
|
||||
|
||||
addJudgementAssert(objects[0], HitResult.IgnoreHit);
|
||||
addJudgementAssert(objects[1], HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNoteReleasedHitAfterNextObjectStartTime()
|
||||
{
|
||||
var objects = new List<ManiaHitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 1010,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1020,
|
||||
EndTime = 1030
|
||||
}
|
||||
};
|
||||
|
||||
var frames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(1030),
|
||||
new ManiaReplayFrame(1040, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(1050)
|
||||
};
|
||||
|
||||
performTest(objects, frames);
|
||||
|
||||
addJudgementAssert(objects[0], HitResult.IgnoreHit);
|
||||
addJudgementAssert("first head", () => ((HoldNote)objects[0]).Head, HitResult.Perfect);
|
||||
addJudgementAssert("first tail", () => ((HoldNote)objects[0]).Tail, HitResult.Perfect);
|
||||
|
||||
addJudgementAssert(objects[1], HitResult.IgnoreHit);
|
||||
addJudgementAssert("second head", () => ((HoldNote)objects[1]).Head, HitResult.Great);
|
||||
addJudgementAssert("second tail", () => ((HoldNote)objects[1]).Tail, HitResult.Perfect);
|
||||
}
|
||||
|
||||
private void addJudgementAssert(ManiaHitObject hitObject, HitResult result)
|
||||
{
|
||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
|
||||
}
|
||||
|
||||
private void addJudgementAssert(string name, Func<ManiaHitObject> hitObject, HitResult result)
|
||||
{
|
||||
AddAssert($"{name} judgement is {result}",
|
||||
() => judgementResults.Single(r => r.HitObject == hitObject()).Type == result);
|
||||
}
|
||||
|
||||
private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset)
|
||||
{
|
||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
||||
|
||||
@@ -204,10 +204,6 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
// don't perform any fading – we are handling that ourselves.
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
LifetimeEnd = HitObject.StartTime + visible_range;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
@@ -12,8 +11,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public class ManiaBlueprintContainer : ComposeBlueprintContainer
|
||||
{
|
||||
public ManiaBlueprintContainer(IEnumerable<DrawableHitObject> drawableHitObjects)
|
||||
: base(drawableHitObjects)
|
||||
public ManiaBlueprintContainer(HitObjectComposer composer)
|
||||
: base(composer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@@ -89,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
return drawableRuleset;
|
||||
}
|
||||
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||
=> new ManiaBlueprintContainer(hitObjects);
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
||||
=> new ManiaBlueprintContainer(this);
|
||||
|
||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||
{
|
||||
|
||||
@@ -119,6 +119,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
if (mods.HasFlag(LegacyMods.Key9))
|
||||
yield return new ManiaModKey9();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.KeyCoop))
|
||||
yield return new ManiaModDualStages();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new ManiaModNoFail();
|
||||
|
||||
@@ -173,13 +176,22 @@ namespace osu.Game.Rulesets.Mania
|
||||
value |= LegacyMods.Key9;
|
||||
break;
|
||||
|
||||
case ManiaModDualStages _:
|
||||
value |= LegacyMods.KeyCoop;
|
||||
break;
|
||||
|
||||
case ManiaModFadeIn _:
|
||||
value |= LegacyMods.FadeIn;
|
||||
value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that.
|
||||
break;
|
||||
|
||||
case ManiaModMirror _:
|
||||
value |= LegacyMods.Mirror;
|
||||
break;
|
||||
|
||||
case ManiaModRandom _:
|
||||
value |= LegacyMods.Random;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModEasy : ModEasy
|
||||
public class ManiaModEasy : ModEasyWithExtraLives
|
||||
{
|
||||
public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
|
||||
}
|
||||
|
||||
@@ -1,10 +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 osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
@@ -71,6 +70,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state) => this.FadeOut(150);
|
||||
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
public double? HoldStartTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the hold note has been released too early and shouldn't give full score for the release.
|
||||
/// Time at which the hold note has been broken, i.e. released too early, resulting in a reduced score.
|
||||
/// </summary>
|
||||
public bool HasBroken { get; private set; }
|
||||
public double? HoldBrokenTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the hold note has been released potentially without having caused a break.
|
||||
@@ -229,12 +229,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
using (BeginDelayedSequence(HitObject.Duration, true))
|
||||
base.UpdateStateTransforms(state);
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (Tail.AllJudged)
|
||||
@@ -244,7 +238,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
}
|
||||
|
||||
if (Tail.Judged && !Tail.IsHit)
|
||||
HasBroken = true;
|
||||
HoldBrokenTime = Time.Current;
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
@@ -255,6 +249,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return false;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
if (Time.Elapsed < 0)
|
||||
return false;
|
||||
|
||||
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
return false;
|
||||
|
||||
@@ -287,6 +285,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
if (Time.Elapsed < 0)
|
||||
return;
|
||||
|
||||
// Make sure a hold was started
|
||||
if (HoldStartTime == null)
|
||||
return;
|
||||
@@ -296,7 +298,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
// If the key has been released too early, the user should not receive full score for the release
|
||||
if (!Tail.IsHit)
|
||||
HasBroken = true;
|
||||
HoldBrokenTime = Time.Current;
|
||||
|
||||
releaseTime = Time.Current;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
/// <summary>
|
||||
@@ -19,8 +17,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
public void UpdateResult() => base.UpdateResult(true);
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
base.UpdateInitialTransforms();
|
||||
|
||||
// This hitobject should never expire, so this is just a safe maximum.
|
||||
LifetimeEnd = LifetimeStart + 30000;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
ApplyResult(r =>
|
||||
{
|
||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken))
|
||||
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null))
|
||||
result = HitResult.Meh;
|
||||
|
||||
r.Type = result;
|
||||
|
||||
@@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
/// <summary>
|
||||
/// References the time at which the user started holding the hold note.
|
||||
/// </summary>
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
|
||||
+8
-8
@@ -6,20 +6,20 @@
|
||||
"EndTime": 2750.0,
|
||||
"Column": 1,
|
||||
"NodeSamples": [
|
||||
["normal-hitnormal"],
|
||||
["soft-hitnormal"],
|
||||
["drum-hitnormal"]
|
||||
["Gameplay/normal-hitnormal"],
|
||||
["Gameplay/soft-hitnormal"],
|
||||
["Gameplay/drum-hitnormal"]
|
||||
],
|
||||
"Samples": ["-hitnormal"]
|
||||
"Samples": ["Gameplay/-hitnormal"]
|
||||
}, {
|
||||
"StartTime": 1875.0,
|
||||
"EndTime": 2750.0,
|
||||
"Column": 0,
|
||||
"NodeSamples": [
|
||||
["soft-hitnormal"],
|
||||
["drum-hitnormal"]
|
||||
["Gameplay/soft-hitnormal"],
|
||||
["Gameplay/drum-hitnormal"]
|
||||
],
|
||||
"Samples": ["-hitnormal"]
|
||||
"Samples": ["Gameplay/-hitnormal"]
|
||||
}]
|
||||
}, {
|
||||
"StartTime": 3750.0,
|
||||
@@ -27,7 +27,7 @@
|
||||
"StartTime": 3750.0,
|
||||
"EndTime": 3750.0,
|
||||
"Column": 3,
|
||||
"Samples": ["normal-hitnormal"]
|
||||
"Samples": ["Gameplay/normal-hitnormal"]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
+4
-4
@@ -6,10 +6,10 @@
|
||||
"EndTime": 1500.0,
|
||||
"Column": 0,
|
||||
"NodeSamples": [
|
||||
["normal-hitnormal"],
|
||||
["Gameplay/normal-hitnormal"],
|
||||
[]
|
||||
],
|
||||
"Samples": ["normal-hitnormal"]
|
||||
"Samples": ["Gameplay/normal-hitnormal"]
|
||||
}]
|
||||
}, {
|
||||
"StartTime": 2000.0,
|
||||
@@ -18,10 +18,10 @@
|
||||
"EndTime": 3000.0,
|
||||
"Column": 2,
|
||||
"NodeSamples": [
|
||||
["drum-hitnormal"],
|
||||
["Gameplay/drum-hitnormal"],
|
||||
[]
|
||||
],
|
||||
"Samples": ["drum-hitnormal"]
|
||||
"Samples": ["Gameplay/drum-hitnormal"]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
+3
-3
@@ -5,17 +5,17 @@
|
||||
"StartTime": 8470.0,
|
||||
"EndTime": 8470.0,
|
||||
"Column": 0,
|
||||
"Samples": ["normal-hitnormal", "normal-hitclap"]
|
||||
"Samples": ["Gameplay/normal-hitnormal", "Gameplay/normal-hitclap"]
|
||||
}, {
|
||||
"StartTime": 8626.470587768974,
|
||||
"EndTime": 8626.470587768974,
|
||||
"Column": 1,
|
||||
"Samples": ["normal-hitnormal"]
|
||||
"Samples": ["Gameplay/normal-hitnormal"]
|
||||
}, {
|
||||
"StartTime": 8782.941175537948,
|
||||
"EndTime": 8782.941175537948,
|
||||
"Column": 2,
|
||||
"Samples": ["normal-hitnormal", "normal-hitclap"]
|
||||
"Samples": ["Gameplay/normal-hitnormal", "Gameplay/normal-hitclap"]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -18,9 +18,17 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
public class LegacyBodyPiece : LegacyManiaColumnElement
|
||||
{
|
||||
private DrawableHoldNote holdNote;
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores the start time of the fade animation that plays when any of the nested
|
||||
/// hitobjects of the hold note are missed.
|
||||
/// </summary>
|
||||
private readonly Bindable<double?> missFadeTime = new Bindable<double?>();
|
||||
|
||||
[CanBeNull]
|
||||
private Drawable bodySprite;
|
||||
|
||||
@@ -38,6 +46,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject)
|
||||
{
|
||||
holdNote = (DrawableHoldNote)drawableObject;
|
||||
|
||||
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
||||
?? $"mania-note{FallbackColumnIndex}L";
|
||||
|
||||
@@ -92,11 +102,26 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
InternalChild = bodySprite;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
|
||||
var holdNote = (DrawableHoldNote)drawableObject;
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
isHitting.BindValueChanged(onIsHittingChanged, true);
|
||||
missFadeTime.BindValueChanged(onMissFadeTimeChanged, true);
|
||||
|
||||
holdNote.ApplyCustomUpdateState += applyCustomUpdateState;
|
||||
applyCustomUpdateState(holdNote, holdNote.State.Value);
|
||||
}
|
||||
|
||||
private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
// ensure that the hold note is also faded out when the head/tail/any tick is missed.
|
||||
if (state == ArmedState.Miss)
|
||||
missFadeTime.Value ??= hitObject.HitStateUpdateTime;
|
||||
}
|
||||
|
||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||
@@ -158,10 +183,38 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
private void onMissFadeTimeChanged(ValueChangedEvent<double?> missFadeTimeChange)
|
||||
{
|
||||
if (missFadeTimeChange.NewValue == null)
|
||||
return;
|
||||
|
||||
// this update could come from any nested object of the hold note (or even from an input).
|
||||
// make sure the transforms are consistent across all affected parts.
|
||||
using (BeginAbsoluteSequence(missFadeTimeChange.NewValue.Value))
|
||||
{
|
||||
// colour and duration matches stable
|
||||
// transforms not applied to entire hold note in order to not affect hit lighting
|
||||
const double fade_duration = 60;
|
||||
|
||||
holdNote.Head.FadeColour(Colour4.DarkGray, fade_duration);
|
||||
holdNote.Tail.FadeColour(Colour4.DarkGray, fade_duration);
|
||||
bodySprite?.FadeColour(Colour4.DarkGray, fade_duration);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (holdNote != null)
|
||||
holdNote.ApplyCustomUpdateState -= applyCustomUpdateState;
|
||||
|
||||
lightContainer?.Expire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
if (result.IsHit)
|
||||
hitPolicy.HandleHit(judgedObject);
|
||||
|
||||
if (!result.IsHit || !DisplayJudgements.Value)
|
||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -19,22 +20,52 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
protected override void ApplyMissAnimations()
|
||||
{
|
||||
if (JudgementText != null)
|
||||
JudgementText.Font = JudgementText.Font.With(size: 25);
|
||||
}
|
||||
if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
|
||||
{
|
||||
// this is temporary logic until mania's skin transformer returns IAnimatableJudgements
|
||||
JudgementBody.ScaleTo(1.6f);
|
||||
JudgementBody.ScaleTo(1, 100, Easing.In);
|
||||
|
||||
protected override double FadeInDuration => 50;
|
||||
JudgementBody.MoveTo(Vector2.Zero);
|
||||
JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
|
||||
JudgementBody.RotateTo(0);
|
||||
JudgementBody.RotateTo(40, 800, Easing.InQuint);
|
||||
JudgementBody.FadeOutFromOne(800);
|
||||
|
||||
LifetimeEnd = JudgementBody.LatestTransformEndTime;
|
||||
}
|
||||
|
||||
base.ApplyMissAnimations();
|
||||
}
|
||||
|
||||
protected override void ApplyHitAnimations()
|
||||
{
|
||||
JudgementBody.ScaleTo(0.8f);
|
||||
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
|
||||
|
||||
JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250);
|
||||
this.Delay(FadeInDuration).FadeOut(200);
|
||||
JudgementBody.Delay(50)
|
||||
.ScaleTo(0.75f, 250)
|
||||
.FadeOut(200);
|
||||
}
|
||||
|
||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||
|
||||
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||
{
|
||||
public DefaultManiaJudgementPiece(HitResult result)
|
||||
: base(result)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
JudgementText.Font = JudgementText.Font.With(size: 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
@@ -44,9 +43,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
||||
public void HandleHit(DrawableHitObject hitObject)
|
||||
{
|
||||
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||
|
||||
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||
{
|
||||
if (obj.Judged)
|
||||
|
||||
@@ -167,6 +167,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
// Tick judgements should not display text.
|
||||
if (judgedObject is DrawableHoldNoteTick)
|
||||
return;
|
||||
|
||||
judgements.Clear(false);
|
||||
judgements.Add(judgementPool.Get(j =>
|
||||
{
|
||||
|
||||
+7
-18
@@ -9,11 +9,10 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Osu.Tests.csproj",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -24,24 +23,14 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Osu.Tests.csproj",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Restore",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"restore"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
var first = (OsuHitObject)objects.First();
|
||||
var second = (OsuHitObject)objects.Last();
|
||||
|
||||
return first.Position == second.Position;
|
||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,5 +86,64 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSecondCircleInSelectionAlsoSnaps()
|
||||
{
|
||||
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
|
||||
|
||||
AddStep("disable distance snap", () => InputManager.Key(Key.Q));
|
||||
|
||||
AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
|
||||
|
||||
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("increment time", () => EditorClock.SeekForward(true));
|
||||
|
||||
AddStep("move mouse right", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.2f, 0)));
|
||||
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("increment time", () => EditorClock.SeekForward(true));
|
||||
|
||||
AddStep("move mouse down", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Width * 0.2f)));
|
||||
AddStep("place third object", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("enter selection mode", () => InputManager.Key(Key.Number1));
|
||||
|
||||
AddStep("select objects 2 and 3", () =>
|
||||
{
|
||||
// add selection backwards to test non-sequential time ordering
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[2]);
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]);
|
||||
});
|
||||
|
||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
|
||||
|
||||
AddAssert("object 3 snapped to 1", () =>
|
||||
{
|
||||
var objects = EditorBeatmap.HitObjects;
|
||||
|
||||
var first = (OsuHitObject)objects.First();
|
||||
var third = (OsuHitObject)objects.Last();
|
||||
|
||||
return Precision.AlmostEquals(first.EndPosition, third.Position);
|
||||
});
|
||||
|
||||
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f)));
|
||||
|
||||
AddAssert("object 2 snapped to 1", () =>
|
||||
{
|
||||
var objects = EditorBeatmap.HitObjects;
|
||||
|
||||
var first = (OsuHitObject)objects.First();
|
||||
var second = (OsuHitObject)objects.ElementAt(1);
|
||||
|
||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||
});
|
||||
|
||||
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +174,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
private class SnapProvider : IPositionSnapProvider
|
||||
{
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||
|
||||
@@ -280,8 +280,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
private void addClickStep(MouseButton button)
|
||||
{
|
||||
AddStep($"press {button}", () => InputManager.PressButton(button));
|
||||
AddStep($"release {button}", () => InputManager.ReleaseButton(button));
|
||||
AddStep($"click {button}", () => InputManager.Click(button));
|
||||
}
|
||||
|
||||
private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected);
|
||||
@@ -295,7 +294,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position.Value, 1));
|
||||
|
||||
private Slider getSlider() => HitObjectContainer.Count > 0 ? (Slider)((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
||||
private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
||||
|
||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
|
||||
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Mod = new OsuModSpunOut(),
|
||||
Autoplay = false,
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () => Player.ChildrenOfType<DrawableSpinner>().Single().Progress >= 1
|
||||
PassCondition = () => Player.ChildrenOfType<DrawableSpinner>().SingleOrDefault()?.Progress >= 1
|
||||
});
|
||||
|
||||
[TestCase(null)]
|
||||
@@ -45,7 +45,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Mods = mods,
|
||||
Autoplay = false,
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () => Precision.AlmostEquals(Player.ChildrenOfType<SpinnerSpmCounter>().Single().SpinsPerMinute, 286, 1)
|
||||
PassCondition = () =>
|
||||
{
|
||||
var counter = Player.ChildrenOfType<SpinnerSpmCounter>().SingleOrDefault();
|
||||
return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,18 +12,36 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class OsuLegacyModConversionTest : LegacyModConversionTest
|
||||
{
|
||||
[TestCase(LegacyMods.Easy, new[] { typeof(OsuModEasy) })]
|
||||
[TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) })]
|
||||
[TestCase(LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) })]
|
||||
[TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })]
|
||||
private static readonly object[][] osu_mod_mapping =
|
||||
{
|
||||
new object[] { LegacyMods.NoFail, new[] { typeof(OsuModNoFail) } },
|
||||
new object[] { LegacyMods.Easy, new[] { typeof(OsuModEasy) } },
|
||||
new object[] { LegacyMods.TouchDevice, new[] { typeof(OsuModTouchDevice) } },
|
||||
new object[] { LegacyMods.Hidden, new[] { typeof(OsuModHidden) } },
|
||||
new object[] { LegacyMods.HardRock, new[] { typeof(OsuModHardRock) } },
|
||||
new object[] { LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) } },
|
||||
new object[] { LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) } },
|
||||
new object[] { LegacyMods.Relax, new[] { typeof(OsuModRelax) } },
|
||||
new object[] { LegacyMods.HalfTime, new[] { typeof(OsuModHalfTime) } },
|
||||
new object[] { LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) } },
|
||||
new object[] { LegacyMods.Flashlight, new[] { typeof(OsuModFlashlight) } },
|
||||
new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } },
|
||||
new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } },
|
||||
new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } },
|
||||
new object[] { LegacyMods.Perfect, new[] { typeof(OsuModPerfect) } },
|
||||
new object[] { LegacyMods.Cinema, new[] { typeof(OsuModCinema) } },
|
||||
new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } }
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(osu_mod_mapping))]
|
||||
[TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })]
|
||||
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })]
|
||||
[TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModFlashlight), typeof(OsuModFlashlight) })]
|
||||
[TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })]
|
||||
[TestCase(LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })]
|
||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime), typeof(OsuModPerfect) })]
|
||||
[TestCase(LegacyMods.SpunOut | LegacyMods.Easy, new[] { typeof(OsuModSpunOut), typeof(OsuModEasy) })]
|
||||
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
|
||||
public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
|
||||
|
||||
[TestCaseSource(nameof(osu_mod_mapping))]
|
||||
public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
}
|
||||
|
||||
@@ -43,10 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
showResult(HitResult.Great);
|
||||
|
||||
AddUntilStep("judgements shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any());
|
||||
AddAssert("judgement body immediately visible",
|
||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.JudgementBody.Alpha == 1));
|
||||
AddAssert("hit lighting hidden",
|
||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.Lighting.Alpha == 0));
|
||||
AddAssert("hit lighting has no transforms", () => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => !judgement.Lighting.Transforms.Any()));
|
||||
AddAssert("hit lighting hidden", () => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.Lighting.Alpha == 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -57,10 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
showResult(HitResult.Great);
|
||||
|
||||
AddUntilStep("judgements shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any());
|
||||
AddAssert("judgement body not immediately visible",
|
||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1));
|
||||
AddAssert("hit lighting shown",
|
||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.Lighting.Alpha > 0));
|
||||
AddUntilStep("hit lighting shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any(judgement => judgement.Lighting.Alpha > 0));
|
||||
}
|
||||
|
||||
private void showResult(HitResult result)
|
||||
@@ -89,7 +84,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Children = new Drawable[]
|
||||
{
|
||||
pool,
|
||||
pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j =>
|
||||
pool.Get(j => j.Apply(new JudgementResult(new HitObject
|
||||
{
|
||||
StartTime = Time.Current
|
||||
}, new Judgement())
|
||||
{
|
||||
Type = result,
|
||||
}, null)).With(j =>
|
||||
{
|
||||
j.Anchor = Anchor.Centre;
|
||||
j.Origin = Anchor.Centre;
|
||||
@@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private class TestDrawableOsuJudgement : DrawableOsuJudgement
|
||||
{
|
||||
public new SkinnableSprite Lighting => base.Lighting;
|
||||
public new Container JudgementBody => base.JudgementBody;
|
||||
public new SkinnableDrawable JudgementBody => base.JudgementBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@@ -94,9 +95,19 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
addMultipleObjectsStep();
|
||||
|
||||
AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100));
|
||||
AddStep("move hitobject", () =>
|
||||
{
|
||||
var manualClock = new ManualClock();
|
||||
followPointRenderer.Clock = new FramedClock(manualClock);
|
||||
|
||||
manualClock.CurrentTime = getObject(1).HitObject.StartTime;
|
||||
followPointRenderer.UpdateSubTree();
|
||||
|
||||
getObject(2).HitObject.Position = new Vector2(300, 100);
|
||||
});
|
||||
|
||||
assertGroups();
|
||||
assertDirections();
|
||||
}
|
||||
|
||||
[TestCase(0, 0)] // Start -> Start
|
||||
@@ -171,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
|
||||
hitObjectContainer.Add(drawableObject);
|
||||
followPointRenderer.AddFollowPoints(drawableObject);
|
||||
followPointRenderer.AddFollowPoints(objects[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,10 +191,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
AddStep("remove hitobject", () =>
|
||||
{
|
||||
var drawableObject = getFunc?.Invoke();
|
||||
var drawableObject = getFunc.Invoke();
|
||||
|
||||
hitObjectContainer.Remove(drawableObject);
|
||||
followPointRenderer.RemoveFollowPoints(drawableObject);
|
||||
followPointRenderer.RemoveFollowPoints(drawableObject.HitObject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,7 +218,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void assertGroups()
|
||||
{
|
||||
AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count);
|
||||
AddAssert("has correct group count", () => followPointRenderer.Entries.Count == hitObjectContainer.Count);
|
||||
AddAssert("group endpoints are correct", () =>
|
||||
{
|
||||
for (int i = 0; i < hitObjectContainer.Count; i++)
|
||||
@@ -215,10 +226,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
DrawableOsuHitObject expectedStart = getObject(i);
|
||||
DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
|
||||
|
||||
if (getGroup(i).Start != expectedStart)
|
||||
if (getEntry(i).Start != expectedStart.HitObject)
|
||||
throw new AssertionException($"Object {i} expected to be the start of group {i}.");
|
||||
|
||||
if (getGroup(i).End != expectedEnd)
|
||||
if (getEntry(i).End != expectedEnd?.HitObject)
|
||||
throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
|
||||
}
|
||||
|
||||
@@ -238,6 +249,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
if (expectedEnd == null)
|
||||
continue;
|
||||
|
||||
var manualClock = new ManualClock();
|
||||
followPointRenderer.Clock = new FramedClock(manualClock);
|
||||
|
||||
manualClock.CurrentTime = expectedStart.HitObject.StartTime;
|
||||
followPointRenderer.UpdateSubTree();
|
||||
|
||||
var points = getGroup(i).ChildrenOfType<FollowPoint>().ToArray();
|
||||
if (points.Length == 0)
|
||||
continue;
|
||||
@@ -255,7 +272,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
|
||||
|
||||
private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
|
||||
private FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index];
|
||||
|
||||
private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType<FollowPointConnection>().Single(c => c.Entry == getEntry(index));
|
||||
|
||||
private class TestHitObjectContainer : Container<DrawableOsuHitObject>
|
||||
{
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@@ -38,13 +38,37 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
|
||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||
{
|
||||
var drawable = createSingle(circleSize, auto, timeOffset, positionOffset);
|
||||
|
||||
var playfield = new TestOsuPlayfield();
|
||||
playfield.Add(drawable);
|
||||
return playfield;
|
||||
}
|
||||
|
||||
private Drawable testStream(float circleSize, bool auto = false)
|
||||
{
|
||||
var playfield = new TestOsuPlayfield();
|
||||
|
||||
Vector2 pos = new Vector2(-250, 0);
|
||||
|
||||
for (int i = 0; i <= 1000; i += 100)
|
||||
{
|
||||
playfield.Add(createSingle(circleSize, auto, i, pos));
|
||||
pos.X += 50;
|
||||
}
|
||||
|
||||
return playfield;
|
||||
}
|
||||
|
||||
private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset)
|
||||
{
|
||||
positionOffset ??= Vector2.Zero;
|
||||
|
||||
var circle = new HitCircle
|
||||
{
|
||||
StartTime = Time.Current + 1000 + timeOffset,
|
||||
Position = positionOffset.Value,
|
||||
Position = OsuPlayfield.BASE_SIZE / 4 + positionOffset.Value,
|
||||
};
|
||||
|
||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
||||
@@ -53,31 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
||||
|
||||
return drawable;
|
||||
}
|
||||
|
||||
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Depth = depthIndex++
|
||||
};
|
||||
|
||||
private Drawable testStream(float circleSize, bool auto = false)
|
||||
{
|
||||
var container = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
Vector2 pos = new Vector2(-250, 0);
|
||||
|
||||
for (int i = 0; i <= 1000; i += 100)
|
||||
{
|
||||
container.Add(testSingle(circleSize, auto, i, pos));
|
||||
pos.X += 50;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
protected class TestDrawableHitCircle : DrawableHitCircle
|
||||
{
|
||||
private readonly bool auto;
|
||||
@@ -101,5 +108,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
protected class TestOsuPlayfield : OsuPlayfield
|
||||
{
|
||||
public TestOsuPlayfield()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneHitCircleApplication : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestApplyNewCircle()
|
||||
{
|
||||
DrawableHitCircle dho = null;
|
||||
|
||||
AddStep("create circle", () => Child = dho = new DrawableHitCircle(prepareObject(new HitCircle
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
IndexInCurrentCombo = 0
|
||||
}))
|
||||
{
|
||||
Clock = new FramedClock(new StopwatchClock())
|
||||
});
|
||||
|
||||
AddStep("apply new circle", () => dho.Apply(prepareObject(new HitCircle
|
||||
{
|
||||
Position = new Vector2(128, 128),
|
||||
ComboIndex = 1,
|
||||
}), null));
|
||||
}
|
||||
|
||||
private HitCircle prepareObject(HitCircle circle)
|
||||
{
|
||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
@@ -10,6 +13,19 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneShaking : TestSceneHitCircle
|
||||
{
|
||||
private readonly List<ScheduledDelegate> scheduledTasks = new List<ScheduledDelegate>();
|
||||
|
||||
protected override IBeatmap CreateBeatmapForSkinProvider()
|
||||
{
|
||||
// best way to run cleanup before a new step is run
|
||||
foreach (var task in scheduledTasks)
|
||||
task.Cancel();
|
||||
|
||||
scheduledTasks.Clear();
|
||||
|
||||
return base.CreateBeatmapForSkinProvider();
|
||||
}
|
||||
|
||||
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
||||
{
|
||||
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
|
||||
@@ -17,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
||||
|
||||
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
|
||||
Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
|
||||
scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay));
|
||||
|
||||
return drawableHitObject;
|
||||
}
|
||||
|
||||
@@ -125,6 +125,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
if (!enabled) return null;
|
||||
|
||||
if (component is OsuSkinComponent osuComponent && osuComponent.Component == OsuSkinComponents.SliderBody)
|
||||
return null;
|
||||
|
||||
return new OsuSpriteText
|
||||
{
|
||||
Text = identifier,
|
||||
|
||||
@@ -112,10 +112,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
||||
});
|
||||
|
||||
AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
|
||||
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
|
||||
AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderRepeat>().All(assertSamples));
|
||||
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
||||
AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle));
|
||||
AddAssert("tick samples not updated", () => slider.HitObject.NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
|
||||
AddAssert("repeat samples updated", () => slider.HitObject.NestedHitObjects.OfType<SliderRepeat>().All(assertSamples));
|
||||
AddAssert("tail has no samples", () => slider.HitObject.TailCircle.Samples.Count == 0);
|
||||
|
||||
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
((Slider)slider.HitObject).NodeSamples.Add(new List<HitSampleInfo> { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
|
||||
slider.HitObject.NodeSamples.Add(new List<HitSampleInfo> { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
|
||||
|
||||
Add(slider);
|
||||
});
|
||||
@@ -147,10 +147,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
||||
});
|
||||
|
||||
AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
|
||||
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
|
||||
AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType<SliderRepeat>().All(assertSamples));
|
||||
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
|
||||
AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle));
|
||||
AddAssert("tick samples not updated", () => slider.HitObject.NestedHitObjects.OfType<SliderTick>().All(assertTickSamples));
|
||||
AddAssert("repeat samples not updated", () => slider.HitObject.NestedHitObjects.OfType<SliderRepeat>().All(assertSamples));
|
||||
AddAssert("tail has no samples", () => slider.HitObject.TailCircle.Samples.Count == 0);
|
||||
|
||||
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneSliderApplication : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private SkinManager skinManager { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestApplyNewSlider()
|
||||
{
|
||||
DrawableSlider dho = null;
|
||||
|
||||
AddStep("create slider", () => Child = dho = new DrawableSlider(prepareObject(new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
IndexInCurrentCombo = 0,
|
||||
StartTime = Time.Current,
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(150, 100),
|
||||
new Vector2(300, 0),
|
||||
})
|
||||
})));
|
||||
|
||||
AddWaitStep("wait for progression", 1);
|
||||
|
||||
AddStep("apply new slider", () => dho.Apply(prepareObject(new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
ComboIndex = 1,
|
||||
StartTime = dho.HitObject.StartTime,
|
||||
Path = new SliderPath(PathType.Bezier, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(150, 100),
|
||||
new Vector2(300, 0),
|
||||
}),
|
||||
RepeatCount = 1
|
||||
}), null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBallTintChangedOnAccentChange()
|
||||
{
|
||||
DrawableSlider dho = null;
|
||||
|
||||
AddStep("create slider", () =>
|
||||
{
|
||||
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
|
||||
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
||||
|
||||
Child = new SkinProvidingContainer(tintingSkin)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = dho = new DrawableSlider(prepareObject(new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
IndexInCurrentCombo = 0,
|
||||
StartTime = Time.Current,
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(150, 100),
|
||||
new Vector2(300, 0),
|
||||
})
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
||||
AddAssert("ball is white", () => dho.ChildrenOfType<SliderBall>().Single().AccentColour == Color4.White);
|
||||
|
||||
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
||||
AddAssert("ball is red", () => dho.ChildrenOfType<SliderBall>().Single().AccentColour == Color4.Red);
|
||||
}
|
||||
|
||||
private Slider prepareObject(Slider slider)
|
||||
{
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return slider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
|
||||
}
|
||||
|
||||
private DrawableSlider slider;
|
||||
private DrawableSlider drawableSlider;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
@@ -68,7 +68,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
addSeekStep(startTime);
|
||||
retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
|
||||
setSnaking(true);
|
||||
|
||||
ensureSnakingIn(startTime + fade_in_modifier);
|
||||
@@ -93,7 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
|
||||
double startTime = hitObjects[sliderIndex].StartTime;
|
||||
retrieveDrawableSlider(sliderIndex);
|
||||
addSeekStep(startTime);
|
||||
retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
|
||||
setSnaking(false);
|
||||
|
||||
ensureNoSnakingIn(startTime + fade_in_modifier);
|
||||
@@ -127,9 +129,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
checkPositionChange(16600, sliderRepeat, positionDecreased);
|
||||
}
|
||||
|
||||
private void retrieveDrawableSlider(int index) =>
|
||||
AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
|
||||
slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index));
|
||||
private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () =>
|
||||
(drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
|
||||
|
||||
private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
|
||||
private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
|
||||
@@ -150,13 +151,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
|
||||
private Func<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func<Vector2>)sliderStart : sliderEnd;
|
||||
|
||||
private List<Vector2> sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve;
|
||||
private List<Vector2> sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
||||
private Vector2 sliderStart() => sliderCurve.First();
|
||||
private Vector2 sliderEnd() => sliderCurve.Last();
|
||||
|
||||
private Vector2 sliderRepeat()
|
||||
{
|
||||
var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1);
|
||||
var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]);
|
||||
var repeat = drawable.ChildrenOfType<Container<DrawableSliderRepeat>>().First().Children.First();
|
||||
return repeat.Position;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private TestDrawableSpinner drawableSpinner;
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestVariousSpinners(bool autoplay)
|
||||
{
|
||||
string term = autoplay ? "Hit" : "Miss";
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneSpinnerApplication : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestApplyNewSpinner()
|
||||
{
|
||||
DrawableSpinner dho = null;
|
||||
|
||||
AddStep("create spinner", () => Child = dho = new DrawableSpinner(prepareObject(new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
IndexInCurrentCombo = 0,
|
||||
Duration = 500,
|
||||
}))
|
||||
{
|
||||
Clock = new FramedClock(new StopwatchClock())
|
||||
});
|
||||
|
||||
AddStep("rotate some", () => dho.RotationTracker.AddRotation(180));
|
||||
AddAssert("rotation is set", () => dho.Result.RateAdjustedRotation == 180);
|
||||
|
||||
AddStep("apply new spinner", () => dho.Apply(prepareObject(new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
ComboIndex = 1,
|
||||
Duration = 1000,
|
||||
}), null));
|
||||
|
||||
AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0);
|
||||
}
|
||||
|
||||
private Spinner prepareObject(Spinner circle)
|
||||
{
|
||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,11 +62,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||
});
|
||||
AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
|
||||
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100));
|
||||
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
||||
|
||||
addSeekStep(0);
|
||||
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
|
||||
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, 0, 100));
|
||||
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
||||
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
||||
});
|
||||
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.RotationTracker.RateAdjustedRotation);
|
||||
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
||||
|
||||
addSeekStep(2500);
|
||||
AddAssert("disc rotation rewound",
|
||||
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
|
||||
AddAssert("is cumulative rotation rewound",
|
||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
||||
|
||||
addSeekStep(5000);
|
||||
AddAssert("is disc rotation almost same",
|
||||
@@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddAssert("is symbol rotation almost same",
|
||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
|
||||
AddAssert("is cumulative rotation almost same",
|
||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
|
||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||
var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
|
||||
return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
|
||||
});
|
||||
|
||||
addSeekStep(0);
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
private readonly Path path;
|
||||
private readonly Slider slider;
|
||||
private readonly int controlPointIndex;
|
||||
public int ControlPointIndex { get; set; }
|
||||
|
||||
private IBindable<Vector2> sliderPosition;
|
||||
private IBindable<int> pathVersion;
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
|
||||
{
|
||||
this.slider = slider;
|
||||
this.controlPointIndex = controlPointIndex;
|
||||
ControlPointIndex = controlPointIndex;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
path.ClearVertices();
|
||||
|
||||
int nextIndex = controlPointIndex + 1;
|
||||
int nextIndex = ControlPointIndex + 1;
|
||||
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
|
||||
return;
|
||||
|
||||
|
||||
+35
-3
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -18,12 +19,16 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
||||
|
||||
internal readonly Container<PathControlPointPiece> Pieces;
|
||||
internal readonly Container<PathControlPointConnectionPiece> Connections;
|
||||
|
||||
@@ -64,6 +69,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
// If inserting in the path (not appending),
|
||||
// update indices of existing connections after insert location
|
||||
if (e.NewStartingIndex < Pieces.Count)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.ControlPointIndex >= e.NewStartingIndex)
|
||||
connection.ControlPointIndex += e.NewItems.Count;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
{
|
||||
var point = (PathControlPoint)e.NewItems[i];
|
||||
@@ -86,6 +102,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
Connections.RemoveAll(c => c.ControlPoint == point);
|
||||
}
|
||||
|
||||
// If removing before the end of the path,
|
||||
// update indices of connections after remove location
|
||||
if (e.OldStartingIndex < Pieces.Count)
|
||||
{
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.ControlPointIndex >= e.OldStartingIndex)
|
||||
connection.ControlPointIndex -= e.OldItems.Count;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -105,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
switch (action.ActionMethod)
|
||||
{
|
||||
case PlatformActionMethod.Delete:
|
||||
return deleteSelected();
|
||||
return DeleteSelected();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -126,7 +153,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool deleteSelected()
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
public bool DeleteSelected()
|
||||
{
|
||||
List<PathControlPoint> toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
|
||||
|
||||
@@ -134,7 +164,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (toRemove.Count == 0)
|
||||
return false;
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
RemoveControlPointsRequested?.Invoke(toRemove);
|
||||
changeHandler?.EndChange();
|
||||
|
||||
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||
foreach (var piece in Pieces)
|
||||
@@ -169,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
return new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()),
|
||||
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => DeleteSelected()),
|
||||
new OsuMenuItem("Curve type")
|
||||
{
|
||||
Items = items
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -43,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||
: base(slider)
|
||||
{
|
||||
@@ -60,18 +64,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
};
|
||||
}
|
||||
|
||||
private IBindable<int> pathVersion;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
||||
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
||||
|
||||
pathVersion.BindTo(HitObject.Path.Version);
|
||||
pathVersion.BindValueChanged(_ => updatePath());
|
||||
|
||||
BodyPiece.UpdateFrom(HitObject);
|
||||
}
|
||||
|
||||
public override bool HandleQuickDeletion()
|
||||
{
|
||||
var hoveredControlPoint = ControlPointVisualiser?.Pieces.FirstOrDefault(p => p.IsHovered);
|
||||
|
||||
if (hoveredControlPoint == null)
|
||||
return false;
|
||||
|
||||
hoveredControlPoint.IsSelected.Value = true;
|
||||
ControlPointVisualiser.DeleteSelected();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@@ -82,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
protected override void OnSelected()
|
||||
{
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser((Slider)slider.HitObject, true)
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true)
|
||||
{
|
||||
RemoveControlPointsRequested = removeControlPoints
|
||||
});
|
||||
@@ -109,9 +125,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
rightClickPosition = e.MouseDownPosition;
|
||||
return false; // Allow right click to be handled by context menu
|
||||
|
||||
case MouseButton.Left when e.ControlPressed && IsSelected:
|
||||
placementControlPointIndex = addControlPoint(e.MousePosition);
|
||||
return true; // Stop input from being handled and modifying the selection
|
||||
case MouseButton.Left:
|
||||
if (e.ControlPressed && IsSelected)
|
||||
{
|
||||
placementControlPointIndex = addControlPoint(e.MousePosition);
|
||||
return true; // Stop input from being handled and modifying the selection
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -146,8 +167,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
}
|
||||
|
||||
private BindableList<PathControlPoint> controlPoints => HitObject.Path.ControlPoints;
|
||||
|
||||
private int addControlPoint(Vector2 position)
|
||||
{
|
||||
position -= HitObject.Position;
|
||||
@@ -216,7 +235,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||
|
||||
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -17,62 +16,64 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public class DrawableOsuEditRuleset : DrawableOsuRuleset
|
||||
{
|
||||
/// <summary>
|
||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||
/// </summary>
|
||||
private const double editor_hit_object_fade_out_extension = 700;
|
||||
|
||||
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
}
|
||||
|
||||
public override DrawableHitObject<OsuHitObject> CreateDrawableRepresentation(OsuHitObject h)
|
||||
=> base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState);
|
||||
|
||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
if (state == ArmedState.Idle)
|
||||
return;
|
||||
|
||||
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||
switch (hitObject)
|
||||
{
|
||||
default:
|
||||
// there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
|
||||
return;
|
||||
|
||||
case DrawableSlider _:
|
||||
// no specifics to sliders but let them fade slower below.
|
||||
break;
|
||||
|
||||
case DrawableHitCircle circle: // also handles slider heads
|
||||
circle.ApproachCircle
|
||||
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||
.Expire();
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the existing fade out transform
|
||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||
|
||||
if (existing == null)
|
||||
return;
|
||||
|
||||
hitObject.RemoveTransform(existing);
|
||||
|
||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||
}
|
||||
|
||||
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();
|
||||
protected override Playfield CreatePlayfield() => new OsuEditPlayfield();
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One };
|
||||
|
||||
private class OsuPlayfieldNoCursor : OsuPlayfield
|
||||
private class OsuEditPlayfield : OsuPlayfield
|
||||
{
|
||||
protected override GameplayCursorContainer CreateCursor() => null;
|
||||
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
d.ApplyCustomUpdateState += updateState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||
/// </summary>
|
||||
private const double editor_hit_object_fade_out_extension = 700;
|
||||
|
||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
if (state == ArmedState.Idle)
|
||||
return;
|
||||
|
||||
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||
switch (hitObject)
|
||||
{
|
||||
default:
|
||||
// there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
|
||||
return;
|
||||
|
||||
case DrawableSlider _:
|
||||
// no specifics to sliders but let them fade slower below.
|
||||
break;
|
||||
|
||||
case DrawableHitCircle circle: // also handles slider heads
|
||||
circle.ApproachCircle
|
||||
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||
.Expire();
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the existing fade out transform
|
||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||
|
||||
if (existing == null)
|
||||
return;
|
||||
|
||||
hitObject.RemoveTransform(existing);
|
||||
|
||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
@@ -14,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public class OsuBlueprintContainer : ComposeBlueprintContainer
|
||||
{
|
||||
public OsuBlueprintContainer(IEnumerable<DrawableHitObject> drawableHitObjects)
|
||||
: base(drawableHitObjects)
|
||||
public OsuBlueprintContainer(HitObjectComposer composer)
|
||||
: base(composer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
@@ -80,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
updateDistanceSnapGrid();
|
||||
}
|
||||
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||
=> new OsuBlueprintContainer(hitObjects);
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
||||
=> new OsuBlueprintContainer(this);
|
||||
|
||||
private DistanceSnapGrid distanceSnapGrid;
|
||||
private Container distanceSnapGridContainer;
|
||||
@@ -106,11 +105,20 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
return snapResult;
|
||||
|
||||
return new SnapResult(screenSpacePosition, null);
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
var positionSnap = SnapScreenSpacePositionToValidPosition(screenSpacePosition);
|
||||
if (positionSnap.ScreenSpacePosition != screenSpacePosition)
|
||||
return positionSnap;
|
||||
|
||||
// will be null if distance snap is disabled or not feasible for the current time value.
|
||||
if (distanceSnapGrid == null)
|
||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||
|
||||
@@ -21,12 +21,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
base.OnSelectionChanged();
|
||||
|
||||
bool canOperate = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
||||
Quad quad = selectedMovableObjects.Length > 0 ? getSurroundingQuad(selectedMovableObjects) : new Quad();
|
||||
|
||||
SelectionBox.CanRotate = canOperate;
|
||||
SelectionBox.CanScaleX = canOperate;
|
||||
SelectionBox.CanScaleY = canOperate;
|
||||
SelectionBox.CanReverse = canOperate;
|
||||
SelectionBox.CanRotate = quad.Width > 0 || quad.Height > 0;
|
||||
SelectionBox.CanScaleX = quad.Width > 0;
|
||||
SelectionBox.CanScaleY = quad.Height > 0;
|
||||
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
||||
}
|
||||
|
||||
protected override void OnOperationEnded()
|
||||
@@ -45,12 +45,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
public override bool HandleReverse()
|
||||
{
|
||||
var hitObjects = selectedMovableObjects;
|
||||
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
||||
|
||||
double endTime = hitObjects.Max(h => h.GetEndTime());
|
||||
double startTime = hitObjects.Min(h => h.StartTime);
|
||||
|
||||
bool moreThanOneObject = hitObjects.Length > 1;
|
||||
bool moreThanOneObject = hitObjects.Count > 1;
|
||||
|
||||
foreach (var h in hitObjects)
|
||||
{
|
||||
@@ -207,11 +207,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
Quad quad = getSurroundingQuad(hitObjects);
|
||||
|
||||
if (quad.TopLeft.X + delta.X < 0 ||
|
||||
quad.TopLeft.Y + delta.Y < 0 ||
|
||||
quad.BottomRight.X + delta.X > DrawWidth ||
|
||||
quad.BottomRight.Y + delta.Y > DrawHeight)
|
||||
return false;
|
||||
Vector2 newTopLeft = quad.TopLeft + delta;
|
||||
if (newTopLeft.X < 0)
|
||||
delta.X -= newTopLeft.X;
|
||||
if (newTopLeft.Y < 0)
|
||||
delta.Y -= newTopLeft.Y;
|
||||
|
||||
Vector2 newBottomRight = quad.BottomRight + delta;
|
||||
if (newBottomRight.X > DrawWidth)
|
||||
delta.X -= newBottomRight.X - DrawWidth;
|
||||
if (newBottomRight.Y > DrawHeight)
|
||||
delta.Y -= newBottomRight.Y - DrawHeight;
|
||||
|
||||
foreach (var h in hitObjects)
|
||||
h.Position += delta;
|
||||
|
||||
@@ -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 osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Judgements
|
||||
{
|
||||
public class OsuSpinnerJudgementResult : OsuJudgementResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Spinner"/>.
|
||||
/// </summary>
|
||||
public Spinner Spinner => (Spinner)HitObject;
|
||||
|
||||
/// <summary>
|
||||
/// The total rotation performed on the spinner disc, disregarding the spin direction,
|
||||
/// adjusted for the track's playback rate.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This value is always non-negative and is monotonically increasing with time
|
||||
/// (i.e. will only increase if time is passing forward, but can decrease during rewind).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The rotation from each frame is multiplied by the clock's current playback rate.
|
||||
/// The reason this is done is to ensure that spinners give the same score and require the same number of spins
|
||||
/// regardless of whether speed-modifying mods are applied.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Assuming no speed-modifying mods are active,
|
||||
/// if the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise,
|
||||
/// this property will return the value of 720 (as opposed to 0).
|
||||
/// If Double Time is active instead (with a speed multiplier of 1.5x),
|
||||
/// in the same scenario the property will return 720 * 1.5 = 1080.
|
||||
/// </example>
|
||||
public float RateAdjustedRotation;
|
||||
|
||||
/// <summary>
|
||||
/// Time instant at which the spinner has been completed (the user has executed all required spins).
|
||||
/// Will be null if all required spins haven't been completed.
|
||||
/// </summary>
|
||||
public double? TimeCompleted;
|
||||
|
||||
public OsuSpinnerJudgementResult(HitObject hitObject, Judgement judgement)
|
||||
: base(hitObject, judgement)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
/// </summary>
|
||||
private const float target_clamp = 1;
|
||||
|
||||
private readonly float targetBreakMultiplier = 0;
|
||||
private readonly float easing = 1;
|
||||
private readonly float targetBreakMultiplier;
|
||||
private readonly float easing;
|
||||
|
||||
private readonly CompositeDrawable restrictTo;
|
||||
|
||||
@@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
this.restrictTo = restrictTo;
|
||||
this.beatmap = beatmap;
|
||||
|
||||
targetBreakMultiplier = 0;
|
||||
easing = 1;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user