1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 17:27:24 +08:00

Merge branch 'master' into diffcalc/fix/clockrate-adjusted-decay

This commit is contained in:
smoogipoo 2021-03-26 11:47:38 +09:00
commit 8438fce764
369 changed files with 4523 additions and 1782 deletions

View File

@ -195,3 +195,6 @@ dotnet_diagnostic.IDE0069.severity = none
#Disable operator overloads requiring alternate named methods #Disable operator overloads requiring alternate named methods
dotnet_diagnostic.CA2225.severity = none dotnet_diagnostic.CA2225.severity = none
# Banned APIs
dotnet_diagnostic.RS0030.severity = error

View File

@ -1,7 +1,18 @@
--- ---
name: Bug Report name: Bug Report
about: Issues regarding encountered bugs. about: Report a bug or crash to desktop
--- ---
<!--
IMPORTANT: Your issue may already be reported.
Please check:
- Pinned issues, at the top of https://github.com/ppy/osu/issues
- Current priority 0 issues at https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0
- Search for your issue. If you find that it already exists, please respond with a reaction or add any further information that may be helpful.
-->
**Describe the bug:** **Describe the bug:**
**Screenshots or videos showing encountered issue:** **Screenshots or videos showing encountered issue:**
@ -9,8 +20,11 @@ about: Issues regarding encountered bugs.
**osu!lazer version:** **osu!lazer version:**
**Logs:** **Logs:**
<!-- <!--
*please attach logs here, which are located at:* *please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),* - `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).* - `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
--> -->

View File

@ -1,18 +0,0 @@
---
name: Crash Report
about: Issues regarding crashes or permanent freezes.
---
**Describe the crash:**
**Screenshots or videos showing encountered issue:**
**osu!lazer version:**
**Logs:**
<!--
*please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).*
-->
**Computer Specifications:**

View File

@ -1,6 +1,6 @@
--- ---
name: Feature Request name: Feature Request
about: Features you would like to see in the game! about: Propose a feature you would like to see in the game!
--- ---
**Describe the new feature:** **Describe the new feature:**

View File

@ -1,20 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (legacy osuTK)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--tk" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net5.0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>

View File

@ -7,3 +7,4 @@ 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) 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.EnumerableExtensions;Don't use internal extension methods.
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.

View File

@ -30,7 +30,7 @@
<Rule Id="CA1819" Action="None" /> <Rule Id="CA1819" Action="None" />
<Rule Id="CA1822" Action="None" /> <Rule Id="CA1822" Action="None" />
<Rule Id="CA1823" Action="None" /> <Rule Id="CA1823" Action="None" />
<Rule Id="CA2007" Action="None" /> <Rule Id="CA2007" Action="Warning" />
<Rule Id="CA2214" Action="None" /> <Rule Id="CA2214" Action="None" />
<Rule Id="CA2227" Action="None" /> <Rule Id="CA2227" Action="None" />
</Rules> </Rules>

View File

@ -18,7 +18,7 @@
<ItemGroup Label="Code Analysis"> <ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" /> <AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Code Analysis"> <PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.219.1" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.323.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -17,7 +17,7 @@ using osu.Game.Database;
namespace osu.Android namespace osu.Android
{ {
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })] [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]
@ -100,15 +100,15 @@ namespace osu.Android
// copy to an arbitrary-access memory stream to be able to proceed with the import. // copy to an arbitrary-access memory stream to be able to proceed with the import.
var copy = new MemoryStream(); var copy = new MemoryStream();
using (var stream = ContentResolver.OpenInputStream(uri)) using (var stream = ContentResolver.OpenInputStream(uri))
await stream.CopyToAsync(copy); await stream.CopyToAsync(copy).ConfigureAwait(false);
lock (tasks) lock (tasks)
{ {
tasks.Add(new ImportTask(copy, filename)); tasks.Add(new ImportTask(copy, filename));
} }
})); })).ConfigureAwait(false);
await game.Import(tasks.ToArray()); await game.Import(tasks.ToArray()).ConfigureAwait(false);
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
} }
} }

View File

@ -136,24 +136,12 @@ namespace osu.Desktop
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
switch (host.Window) var desktopWindow = (SDL2DesktopWindow)host.Window;
{
// Legacy osuTK DesktopGameWindow
case OsuTKDesktopWindow desktopGameWindow:
desktopGameWindow.CursorState |= CursorState.Hidden;
desktopGameWindow.SetIconFromStream(iconStream);
desktopGameWindow.Title = Name;
desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
break;
// SDL2 DesktopWindow
case SDL2DesktopWindow desktopWindow:
desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(iconStream); desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name; desktopWindow.Title = Name;
desktopWindow.DragDrop += f => fileDrop(new[] { f }); desktopWindow.DragDrop += f => fileDrop(new[] { f });
break;
}
} }
private void fileDrop(string[] filePaths) private void fileDrop(string[] filePaths)

View File

@ -22,9 +22,8 @@ namespace osu.Desktop
{ {
// Back up the cwd before DesktopGameHost changes it // Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory; var cwd = Environment.CurrentDirectory;
bool useOsuTK = args.Contains("--tk");
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK)) using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
{ {
host.ExceptionThrown += handleException; host.ExceptionThrown += handleException;

View File

@ -42,7 +42,7 @@ namespace osu.Desktop.Updater
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
} }
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync(); protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{ {
@ -51,9 +51,9 @@ namespace osu.Desktop.Updater
try try
{ {
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false);
var info = await updateManager.CheckForUpdate(!useDeltaPatching); var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
if (info.ReleasesToApply.Count == 0) if (info.ReleasesToApply.Count == 0)
{ {
@ -79,12 +79,12 @@ namespace osu.Desktop.Updater
try try
{ {
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f); await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.Progress = 0; notification.Progress = 0;
notification.Text = @"Installing update..."; notification.Text = @"Installing update...";
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
updatePending = true; updatePending = true;
@ -97,7 +97,7 @@ namespace osu.Desktop.Updater
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
// try again without deltas. // try again without deltas.
await checkForUpdateAsync(false, notification); await checkForUpdateAsync(false, notification).ConfigureAwait(false);
scheduleRecheck = false; scheduleRecheck = false;
} }
else else
@ -116,7 +116,7 @@ namespace osu.Desktop.Updater
if (scheduleRecheck) if (scheduleRecheck)
{ {
// check again in 30 minutes. // check again in 30 minutes.
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30); Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
} }
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
LocalConfig.Set(OsuSetting.IncreaseFirstObjectVisibility, false); LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
} }
[Test] [Test]

View File

@ -202,7 +202,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public void TestHitLightingColour() public void TestHitLightingColour()
{ {
var fruitColour = SkinConfiguration.DefaultComboColours[1]; var fruitColour = SkinConfiguration.DefaultComboColours[1];
AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true)); AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit())); AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("correct hit lighting colour", () => AddAssert("correct hit lighting colour", () =>
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour); catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test] [Test]
public void TestHitLightingDisabled() public void TestHitLightingDisabled()
{ {
AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false)); AddStep("disable hit lighting", () => config.SetValue(OsuSetting.HitLighting, false));
AddStep("catch fruit", () => attemptCatch(new Fruit())); AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any()); AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
} }

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -21,6 +21,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Catch.Skinning.Legacy; using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -50,40 +51,40 @@ namespace osu.Game.Rulesets.Catch
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new CatchModNightcore(); yield return new CatchModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime(); yield return new CatchModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new CatchModPerfect(); yield return new CatchModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath(); yield return new CatchModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new CatchModCinema(); yield return new CatchModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new CatchModAutoplay(); yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new CatchModEasy(); yield return new CatchModEasy();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new CatchModFlashlight(); yield return new CatchModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new CatchModHalfTime(); yield return new CatchModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new CatchModHardRock(); yield return new CatchModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new CatchModHidden(); yield return new CatchModHidden();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new CatchModNoFail(); yield return new CatchModNoFail();
if (mods.HasFlag(LegacyMods.Relax)) if (mods.HasFlagFast(LegacyMods.Relax))
yield return new CatchModRelax(); yield return new CatchModRelax();
} }

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{ {
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new Skill[] return new Skill[]
{ {
new Movement(halfCatcherWidth), new Movement(mods, halfCatcherWidth),
}; };
} }

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{ {
@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
private float lastDistanceMoved; private float lastDistanceMoved;
private double lastStrainTime; private double lastStrainTime;
public Movement(float halfCatcherWidth) public Movement(Mod[] mods, float halfCatcherWidth)
: base(mods)
{ {
HalfCatcherWidth = halfCatcherWidth; HalfCatcherWidth = halfCatcherWidth;
} }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
} }
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); Assert.AreEqual(4000, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | | | // | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();

View File

@ -5,11 +5,13 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -345,6 +347,14 @@ namespace osu.Game.Rulesets.Mania.Tests
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head);
AddAssert("head is visible",
() => currentPlayer.ChildrenOfType<DrawableHoldNote>()
.Single(note => note.HitObject == beatmap.HitObjects[0])
.Head
.Alpha == 1);
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
} }
@ -352,6 +362,8 @@ namespace osu.Game.Rulesets.Mania.Tests
{ {
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
protected override bool PauseOnFocusLost => false; protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score) public ScoreAccessibleReplayPlayer(Score score)

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor) private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor); => hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor) private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor)); => verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.78, 0.3, 0); return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03); return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.43, 0.08, 0); return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(StartTime, 0.56, 0.18, 0); return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
@ -157,13 +158,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 2.5) if (ConversionDifficulty > 2.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.3, 0, 0); return generateNRandomNotes(StartTime, 0.3, 0, 0);
return generateNRandomNotes(StartTime, 0.37, 0.08, 0); return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
} }
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.17, 0, 0); return generateNRandomNotes(StartTime, 0.17, 0, 0);
return generateNRandomNotes(StartTime, 0.27, 0, 0); return generateNRandomNotes(StartTime, 0.27, 0, 0);
@ -221,7 +222,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
int lastColumn = nextColumn; int lastColumn = nextColumn;
@ -373,7 +374,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes) if (canGenerateTwoNotes)
@ -406,7 +407,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int endTime = startTime + SegmentDuration * SpanCount; int endTime = startTime + SegmentDuration * SpanCount;
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
for (int i = 0; i < columnRepeat; i++) for (int i = 0; i < columnRepeat; i++)
@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern); holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
// Create the hold note // Create the hold note

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osuTK; using osuTK;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
else else
convertType |= PatternType.LowProbability; convertType |= PatternType.LowProbability;
if (!convertType.HasFlag(PatternType.KeepSingle)) if (!convertType.HasFlagFast(PatternType.KeepSingle))
{ {
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8) if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror; convertType |= PatternType.Mirror;
@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any()) if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by copying the last hit objects in reverse-column order // Generate a new pattern by copying the last hit objects in reverse-column order
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key // If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0) && (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column // Make sure the last column was not the centre column
@ -126,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any()) if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by placing on the already filled columns // Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (PreviousPattern.HitObjects.Count() == 1) if (PreviousPattern.HitObjects.Count() == 1)
{ {
if (convertType.HasFlag(PatternType.Stair)) if (convertType.HasFlagFast(PatternType.Stair))
{ {
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
int targetColumn = lastColumn + 1; int targetColumn = lastColumn + 1;
@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if (convertType.HasFlag(PatternType.ReverseStair)) if (convertType.HasFlagFast(PatternType.ReverseStair))
{ {
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
int targetColumn = lastColumn - 1; int targetColumn = lastColumn - 1;
@ -163,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
} }
if (convertType.HasFlag(PatternType.KeepSingle)) if (convertType.HasFlagFast(PatternType.KeepSingle))
return generateRandomNotes(1); return generateRandomNotes(1);
if (convertType.HasFlag(PatternType.Mirror)) if (convertType.HasFlagFast(PatternType.Mirror))
{ {
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.78, 0.42, 0, 0); return generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0); return generateRandomPattern(1, 0.62, 0, 0);
@ -186,7 +187,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.35, 0.08, 0, 0); return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0); return generateRandomPattern(0.52, 0.15, 0, 0);
@ -194,7 +195,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 2) if (ConversionDifficulty > 2)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.18, 0, 0, 0); return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0); return generateRandomPattern(0.45, 0, 0, 0);
@ -207,9 +208,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
foreach (var obj in p.HitObjects) foreach (var obj in p.HitObjects)
{ {
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair; StairType = PatternType.ReverseStair;
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
StairType = PatternType.Stair; StairType = PatternType.Stair;
} }
@ -229,7 +230,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack); bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
if (!allowStacking) if (!allowStacking)
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int getNextColumn(int last) int getNextColumn(int last)
{ {
if (convertType.HasFlag(PatternType.Gathered)) if (convertType.HasFlagFast(PatternType.Gathered))
{ {
last++; last++;
if (last == TotalColumns) if (last == TotalColumns)
@ -296,7 +297,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
{ {
if (convertType.HasFlag(PatternType.ForceNotStack)) if (convertType.HasFlagFast(PatternType.ForceNotStack))
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
var pattern = new Pattern(); var pattern = new Pattern();

View File

@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Configuration
{ {
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
} }
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings public override TrackedSettings CreateTrackedSettings() => new TrackedSettings

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input; protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Strain(((ManiaBeatmap)beatmap).TotalColumns) new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
}; };
protected override Mod[] DifficultyAdjustmentMods protected override Mod[] DifficultyAdjustmentMods

View File

@ -6,6 +6,7 @@ using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills namespace osu.Game.Rulesets.Mania.Difficulty.Skills
@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
private double individualStrain; private double individualStrain;
private double overallStrain; private double overallStrain;
public Strain(int totalColumns) public Strain(Mod[] mods, int totalColumns)
: base(mods)
{ {
holdEndTimes = new double[totalColumns]; holdEndTimes = new double[totalColumns];
individualStrains = new double[totalColumns]; individualStrains = new double[totalColumns];

View File

@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit
int minColumn = int.MaxValue; int minColumn = int.MaxValue;
int maxColumn = int.MinValue; int maxColumn = int.MinValue;
// find min/max in an initial pass before actually performing the movement.
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>()) foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
{ {
if (obj.Column < minColumn) if (obj.Column < minColumn)
@ -55,8 +56,11 @@ namespace osu.Game.Rulesets.Mania.Edit
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>()) EditorBeatmap.PerformOnSelection(h =>
obj.Column += columnDelta; {
if (h is ManiaHitObject maniaObj)
maniaObj.Column += columnDelta;
});
} }
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
@ -59,76 +60,76 @@ namespace osu.Game.Rulesets.Mania
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new ManiaModNightcore(); yield return new ManiaModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new ManiaModDoubleTime(); yield return new ManiaModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new ManiaModPerfect(); yield return new ManiaModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath(); yield return new ManiaModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new ManiaModCinema(); yield return new ManiaModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new ManiaModAutoplay(); yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new ManiaModEasy(); yield return new ManiaModEasy();
if (mods.HasFlag(LegacyMods.FadeIn)) if (mods.HasFlagFast(LegacyMods.FadeIn))
yield return new ManiaModFadeIn(); yield return new ManiaModFadeIn();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new ManiaModFlashlight(); yield return new ManiaModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new ManiaModHalfTime(); yield return new ManiaModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new ManiaModHardRock(); yield return new ManiaModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new ManiaModHidden(); yield return new ManiaModHidden();
if (mods.HasFlag(LegacyMods.Key1)) if (mods.HasFlagFast(LegacyMods.Key1))
yield return new ManiaModKey1(); yield return new ManiaModKey1();
if (mods.HasFlag(LegacyMods.Key2)) if (mods.HasFlagFast(LegacyMods.Key2))
yield return new ManiaModKey2(); yield return new ManiaModKey2();
if (mods.HasFlag(LegacyMods.Key3)) if (mods.HasFlagFast(LegacyMods.Key3))
yield return new ManiaModKey3(); yield return new ManiaModKey3();
if (mods.HasFlag(LegacyMods.Key4)) if (mods.HasFlagFast(LegacyMods.Key4))
yield return new ManiaModKey4(); yield return new ManiaModKey4();
if (mods.HasFlag(LegacyMods.Key5)) if (mods.HasFlagFast(LegacyMods.Key5))
yield return new ManiaModKey5(); yield return new ManiaModKey5();
if (mods.HasFlag(LegacyMods.Key6)) if (mods.HasFlagFast(LegacyMods.Key6))
yield return new ManiaModKey6(); yield return new ManiaModKey6();
if (mods.HasFlag(LegacyMods.Key7)) if (mods.HasFlagFast(LegacyMods.Key7))
yield return new ManiaModKey7(); yield return new ManiaModKey7();
if (mods.HasFlag(LegacyMods.Key8)) if (mods.HasFlagFast(LegacyMods.Key8))
yield return new ManiaModKey8(); yield return new ManiaModKey8();
if (mods.HasFlag(LegacyMods.Key9)) if (mods.HasFlagFast(LegacyMods.Key9))
yield return new ManiaModKey9(); yield return new ManiaModKey9();
if (mods.HasFlag(LegacyMods.KeyCoop)) if (mods.HasFlagFast(LegacyMods.KeyCoop))
yield return new ManiaModDualStages(); yield return new ManiaModDualStages();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new ManiaModNoFail(); yield return new ManiaModNoFail();
if (mods.HasFlag(LegacyMods.Random)) if (mods.HasFlagFast(LegacyMods.Random))
yield return new ManiaModRandom(); yield return new ManiaModRandom();
if (mods.HasFlag(LegacyMods.Mirror)) if (mods.HasFlagFast(LegacyMods.Mirror))
yield return new ManiaModMirror(); yield return new ManiaModMirror();
} }

View File

@ -1,18 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites; using System;
using osu.Game.Graphics; using System.Linq;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModFadeIn : ManiaModHidden public class ManiaModFadeIn : ManiaModPlayfieldCover
{ {
public override string Name => "Fade In"; public override string Name => "Fade In";
public override string Acronym => "FI"; public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModHidden;
public override string Description => @"Keys appear out of nowhere!"; public override string Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
} }

View File

@ -3,43 +3,17 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject> public class ManiaModHidden : ManiaModPlayfieldCover
{ {
public override string Description => @"Keys fade out before you hit them!"; public override string Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
/// <summary> public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
/// The direction in which the cover should expand.
/// </summary>
protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset) protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
{
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
{
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
Container hocParent = (Container)hoc.Parent;
hocParent.Remove(hoc);
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
{
c.RelativeSizeAxes = Axes.Both;
c.Direction = ExpandDirection;
c.Coverage = 0.5f;
}));
}
}
} }
} }

View File

@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
{
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
/// <summary>
/// The direction in which the cover should expand.
/// </summary>
protected abstract CoverExpandDirection ExpandDirection { get; }
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
{
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
{
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
Container hocParent = (Container)hoc.Parent;
hocParent.Remove(hoc);
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
{
c.RelativeSizeAxes = Axes.Both;
c.Direction = ExpandDirection;
c.Coverage = 0.5f;
}));
}
}
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // 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 namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
/// <summary> /// <summary>
@ -25,6 +27,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
LifetimeEnd = LifetimeStart + 30000; LifetimeEnd = LifetimeStart + 30000;
} }
protected override void UpdateHitStateTransforms(ArmedState state)
{
// suppress the base call explicitly.
// the hold note head should never change its visual state on its own due to the "freezing" mechanic
// (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line).
// it will be hidden along with its parenting hold note when required.
}
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
public override void OnReleased(ManiaAction action) public override void OnReleased(ManiaAction action)

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
@ -85,20 +86,28 @@ namespace osu.Game.Rulesets.Mania.Replays
{ {
var currentObject = Beatmap.HitObjects[i]; var currentObject = Beatmap.HitObjects[i];
var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
var releaseTime = calculateReleaseTime(currentObject, nextObjectInColumn);
double endTime = currentObject.GetEndTime();
bool canDelayKeyUp = nextObjectInColumn == null ||
nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9;
yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column };
yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column }; yield return new ReleasePoint { Time = releaseTime, Column = currentObject.Column };
} }
} }
private double calculateReleaseTime(HitObject currentObject, HitObject nextObject)
{
double endTime = currentObject.GetEndTime();
if (currentObject is HoldNote)
// hold note releases must be timed exactly.
return endTime;
bool canDelayKeyUpFully = nextObject == null ||
nextObject.StartTime > endTime + RELEASE_DELAY;
return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9);
}
protected override HitObject GetNextObject(int currentIndex) protected override HitObject GetNextObject(int currentIndex)
{ {
int desiredColumn = Beatmap.HitObjects[currentIndex].Column; int desiredColumn = Beatmap.HitObjects[currentIndex].Column;

View File

@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{ {
public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion
{ {
public const double FADE_IN_DURATION = 80;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Drawable explosion; private Drawable explosion;
@ -72,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
(explosion as IFramedAnimation)?.GotoFrame(0); (explosion as IFramedAnimation)?.GotoFrame(0);
explosion?.FadeInFromZero(80) explosion?.FadeInFromZero(FADE_IN_DURATION)
.Then().FadeOut(120); .Then().FadeOut(120);
} }
} }

View File

@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{ {
if (action == column.Action.Value) if (action == column.Action.Value)
{ {
upSprite.FadeTo(1); upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1);
downSprite.FadeTo(0); downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0);
} }
} }
} }

View File

@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
} }
public override Sample GetSample(ISampleInfo sampleInfo) public override ISample GetSample(ISampleInfo sampleInfo)
{ {
// layered hit sounds never play in mania // layered hit sounds never play in mania
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)

View File

@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.9311451172608853d, "diffcalc-test")] [TestCase(6.9311451172574934d, "diffcalc-test")]
[TestCase(1.0736587013228804d, "zero-length-sliders")] [TestCase(1.0736586907780401d, "zero-length-sliders")]
public void Test(double expected, string name) public void Test(double expected, string name)
=> base.Test(expected, name); => base.Test(expected, name);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,2 +1,6 @@
[General] [General]
Version: 1.0 Version: 1.0
[Fonts]
HitCircleOverlap: 3
ScoreOverlap: 3

View File

@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests
return null; return null;
} }
public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException(); public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestHitLightingDisabled() public void TestHitLightingDisabled()
{ {
AddStep("hit lighting disabled", () => config.Set(OsuSetting.HitLighting, false)); AddStep("hit lighting disabled", () => config.SetValue(OsuSetting.HitLighting, false));
showResult(HitResult.Great); showResult(HitResult.Great);
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestHitLightingEnabled() public void TestHitLightingEnabled()
{ {
AddStep("hit lighting enabled", () => config.Set(OsuSetting.HitLighting, true)); AddStep("hit lighting enabled", () => config.SetValue(OsuSetting.HitLighting, true));
showResult(HitResult.Great); showResult(HitResult.Great);

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddSliderStep("circle size", 0f, 10f, 0f, val => AddSliderStep("circle size", 0f, 10f, 0f, val =>
{ {
config.Set(OsuSetting.AutoCursorSize, true); config.SetValue(OsuSetting.AutoCursorSize, true);
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
Scheduler.AddOnce(recreate); Scheduler.AddOnce(recreate);
}); });
@ -64,21 +64,21 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase(10, 1.5f)] [TestCase(10, 1.5f)]
public void TestSizing(int circleSize, float userScale) public void TestSizing(int circleSize, float userScale)
{ {
AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale)); AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
AddStep("turn on autosizing", () => config.Set(OsuSetting.AutoCursorSize, true)); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
AddStep("load content", loadContent); AddStep("load content", loadContent);
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
AddStep("set user scale to 1", () => config.Set(OsuSetting.GameplayCursorSize, 1f)); AddStep("set user scale to 1", () => config.SetValue(OsuSetting.GameplayCursorSize, 1f));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize)); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize));
AddStep("turn off autosizing", () => config.Set(OsuSetting.AutoCursorSize, false)); AddStep("turn off autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, false));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1);
AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale)); AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
} }

View File

@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
AddStep("enable user provider", () => testUserSkin.Enabled = true); AddStep("enable user provider", () => testUserSkin.Enabled = true);
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true)); AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
checkNextHitObject("beatmap"); checkNextHitObject("beatmap");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false)); AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
checkNextHitObject("user"); checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false); AddStep("disable user provider", () => testUserSkin.Enabled = false);
@ -57,20 +57,20 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
AddStep("enable user provider", () => testUserSkin.Enabled = true); AddStep("enable user provider", () => testUserSkin.Enabled = true);
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true)); AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true)); AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true));
checkNextHitObject("beatmap"); checkNextHitObject("beatmap");
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true)); AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false)); AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false));
checkNextHitObject("beatmap"); checkNextHitObject("beatmap");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false)); AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true)); AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true));
checkNextHitObject("user"); checkNextHitObject("user");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false)); AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false)); AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false));
checkNextHitObject("user"); checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false); AddStep("disable user provider", () => testUserSkin.Enabled = false);
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public Sample GetSample(ISampleInfo sampleInfo) => null; public ISample GetSample(ISampleInfo sampleInfo) => null;
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => default; public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => default;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null; public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -17,10 +17,10 @@ namespace osu.Game.Rulesets.Osu.Configuration
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
{ {
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(OsuRulesetSetting.SnakingInSliders, true); SetDefault(OsuRulesetSetting.SnakingInSliders, true);
Set(OsuRulesetSetting.SnakingOutSliders, true); SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
Set(OsuRulesetSetting.ShowCursorTrail, true); SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
} }
} }

View File

@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Aim(), new Aim(mods),
new Speed() new Speed(mods)
}; };
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -17,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double angle_bonus_begin = Math.PI / 3; private const double angle_bonus_begin = Math.PI / 3;
private const double timing_threshold = 107; private const double timing_threshold = 107;
public Aim(Mod[] mods)
: base(mods)
{
}
protected override double SkillMultiplier => 26.25; protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15; protected override double StrainDecayBase => 0.15;

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -27,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double max_speed_bonus = 45; // ~330BPM private const double max_speed_bonus = 45; // ~330BPM
private const double speed_balancing_factor = 40; private const double speed_balancing_factor = 40;
public Speed(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
if (current.BaseObject is Spinner) if (current.BaseObject is Spinner)

View File

@ -7,11 +7,13 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osuTK; using osuTK;
@ -23,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// <summary> /// <summary>
/// A visualisation of a single <see cref="PathControlPoint"/> in a <see cref="Slider"/>. /// A visualisation of a single <see cref="PathControlPoint"/> in a <see cref="Slider"/>.
/// </summary> /// </summary>
public class PathControlPointPiece : BlueprintPiece<Slider> public class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
{ {
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection; public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
@ -195,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
markerRing.Alpha = IsSelected.Value ? 1 : 0; markerRing.Alpha = IsSelected.Value ? 1 : 0;
Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow; Color4 colour = getColourFromNodeType();
if (IsHovered || IsSelected.Value) if (IsHovered || IsSelected.Value)
colour = colour.Lighten(1); colour = colour.Lighten(1);
@ -203,5 +205,28 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
marker.Colour = colour; marker.Colour = colour;
marker.Scale = new Vector2(slider.Scale); marker.Scale = new Vector2(slider.Scale);
} }
private Color4 getColourFromNodeType()
{
if (!(ControlPoint.Type.Value is PathType pathType))
return colours.Yellow;
switch (pathType)
{
case PathType.Catmull:
return colours.Seafoam;
case PathType.Bezier:
return colours.Pink;
case PathType.PerfectCurve:
return colours.PurpleDark;
default:
return colours.Red;
}
}
public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty;
} }
} }

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderBodyPiece BodyPiece { get; private set; } protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; }
protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; }
[CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
private readonly DrawableSlider slider; private readonly DrawableSlider slider;
@ -114,6 +117,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// throw away frame buffers on deselection. // throw away frame buffers on deselection.
ControlPointVisualiser?.Expire(); ControlPointVisualiser?.Expire();
ControlPointVisualiser = null;
BodyPiece.RecyclePath(); BodyPiece.RecyclePath();
} }

View File

@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.Judgements
/// </example> /// </example>
public float RateAdjustedRotation; public float RateAdjustedRotation;
/// <summary>
/// Time instant at which the spin was started (the first user input which caused an increase in spin).
/// </summary>
public double? TimeStarted;
/// <summary> /// <summary>
/// Time instant at which the spinner has been completed (the user has executed all required spins). /// 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. /// Will be null if all required spins haven't been completed.

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation; public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
public bool PerformFail() => false; public bool PerformFail() => false;

View File

@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
internal class OsuModTraceable : ModWithVisibilityAdjustment public class OsuModTraceable : ModWithVisibilityAdjustment
{ {
public override string Name => "Traceable"; public override string Name => "Traceable";
public override string Acronym => "TC"; public override string Acronym => "TC";

View File

@ -164,28 +164,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApproachCircle.Expire(true); ApproachCircle.Expire(true);
} }
protected override void UpdateStartTimeStateTransforms()
{
base.UpdateStartTimeStateTransforms();
ApproachCircle.FadeOut(50);
}
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
{ {
Debug.Assert(HitObject.HitWindows != null); Debug.Assert(HitObject.HitWindows != null);
// todo: temporary / arbitrary, used for lifetime optimisation.
this.Delay(800).FadeOut();
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle:
this.Delay(HitObject.TimePreempt).FadeOut(500);
HitArea.HitAction = null; HitArea.HitAction = null;
break; break;
case ArmedState.Miss: case ArmedState.Miss:
ApproachCircle.FadeOut(50);
this.FadeOut(100); this.FadeOut(100);
break; break;
case ArmedState.Hit:
ApproachCircle.FadeOut(50);
// todo: temporary / arbitrary
this.Delay(800).FadeOut();
break;
} }
Expire(); Expire();

View File

@ -33,12 +33,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public SpinnerSpmCounter SpmCounter { get; private set; } public SpinnerSpmCounter SpmCounter { get; private set; }
private Container<DrawableSpinnerTick> ticks; private Container<DrawableSpinnerTick> ticks;
private SpinnerBonusDisplay bonusDisplay;
private PausableSkinnableSound spinningSample; private PausableSkinnableSound spinningSample;
private Bindable<bool> isSpinning; private Bindable<bool> isSpinning;
private bool spinnerFrequencyModulate; private bool spinnerFrequencyModulate;
/// <summary>
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
/// </summary>
public IBindable<double> GainedBonus => gainedBonus;
private readonly Bindable<double> gainedBonus = new Bindable<double>();
private const double fade_out_duration = 160;
public DrawableSpinner() public DrawableSpinner()
: this(null) : this(null)
{ {
@ -65,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()), new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()),
RotationTracker = new SpinnerRotationTracker(this) RotationTracker = new SpinnerRotationTracker(this)
} }
}, },
@ -76,12 +84,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Y = 120, Y = 120,
Alpha = 0 Alpha = 0
}, },
bonusDisplay = new SpinnerBonusDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = -120,
},
spinningSample = new PausableSkinnableSound spinningSample = new PausableSkinnableSound
{ {
Volume = { Value = 0 }, Volume = { Value = 0 },
@ -131,12 +133,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (tracking.NewValue) if (tracking.NewValue)
{ {
if (!spinningSample.IsPlaying) if (!spinningSample.IsPlaying)
spinningSample?.Play(); spinningSample.Play();
spinningSample?.VolumeTo(1, 300);
spinningSample.VolumeTo(1, 300);
} }
else else
{ {
spinningSample?.VolumeTo(0, 300).OnComplete(_ => spinningSample.Stop()); spinningSample.VolumeTo(0, fade_out_duration);
} }
} }
@ -158,11 +161,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected override void UpdateStartTimeStateTransforms()
{
base.UpdateStartTimeStateTransforms();
if (Result?.TimeStarted is double startTime)
{
using (BeginAbsoluteSequence(startTime))
fadeInCounter();
}
}
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
{ {
base.UpdateHitStateTransforms(state); base.UpdateHitStateTransforms(state);
this.FadeOut(160).Expire(); this.FadeOut(fade_out_duration).OnComplete(_ =>
{
// looping sample should be stopped here as it is safer than running in the OnComplete
// of the volume transition above.
spinningSample.Stop();
});
Expire();
// skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback. // skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback.
isSpinning?.TriggerChange(); isSpinning?.TriggerChange();
@ -262,13 +283,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.UpdateAfterChildren(); base.UpdateAfterChildren();
if (!SpmCounter.IsPresent && RotationTracker.Tracking) if (!SpmCounter.IsPresent && RotationTracker.Tracking)
SpmCounter.FadeIn(HitObject.TimeFadeIn); {
Result.TimeStarted ??= Time.Current;
fadeInCounter();
}
// don't update after end time to avoid the rate display dropping during fade out.
// this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period.
if (Time.Current <= HitObject.EndTime)
SpmCounter.SetRotation(Result.RateAdjustedRotation); SpmCounter.SetRotation(Result.RateAdjustedRotation);
updateBonusScore(); updateBonusScore();
} }
private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn);
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
private int wholeSpins; private int wholeSpins;
private void updateBonusScore() private void updateBonusScore()
@ -293,8 +324,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (tick != null) if (tick != null)
{ {
tick.TriggerResult(true); tick.TriggerResult(true);
if (tick is DrawableSpinnerBonusTick) if (tick is DrawableSpinnerBonusTick)
bonusDisplay.SetBonusCount(spins - HitObject.SpinsRequired); gainedBonus.Value = score_per_tick * (spins - HitObject.SpinsRequired);
} }
wholeSpins++; wholeSpins++;

View File

@ -29,6 +29,7 @@ using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.Statistics;
@ -58,52 +59,52 @@ namespace osu.Game.Rulesets.Osu
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new OsuModNightcore(); yield return new OsuModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new OsuModDoubleTime(); yield return new OsuModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new OsuModPerfect(); yield return new OsuModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new OsuModSuddenDeath(); yield return new OsuModSuddenDeath();
if (mods.HasFlag(LegacyMods.Autopilot)) if (mods.HasFlagFast(LegacyMods.Autopilot))
yield return new OsuModAutopilot(); yield return new OsuModAutopilot();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new OsuModCinema(); yield return new OsuModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new OsuModAutoplay(); yield return new OsuModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new OsuModEasy(); yield return new OsuModEasy();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new OsuModFlashlight(); yield return new OsuModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new OsuModHalfTime(); yield return new OsuModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new OsuModHardRock(); yield return new OsuModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new OsuModHidden(); yield return new OsuModHidden();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new OsuModNoFail(); yield return new OsuModNoFail();
if (mods.HasFlag(LegacyMods.Relax)) if (mods.HasFlagFast(LegacyMods.Relax))
yield return new OsuModRelax(); yield return new OsuModRelax();
if (mods.HasFlag(LegacyMods.SpunOut)) if (mods.HasFlagFast(LegacyMods.SpunOut))
yield return new OsuModSpunOut(); yield return new OsuModSpunOut();
if (mods.HasFlag(LegacyMods.Target)) if (mods.HasFlagFast(LegacyMods.Target))
yield return new OsuModTarget(); yield return new OsuModTarget();
if (mods.HasFlag(LegacyMods.TouchDevice)) if (mods.HasFlagFast(LegacyMods.TouchDevice))
yield return new OsuModTouchDevice(); yield return new OsuModTouchDevice();
} }

View File

@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Osu
SliderFollowCircle, SliderFollowCircle,
SliderBall, SliderBall,
SliderBody, SliderBody,
SpinnerBody SpinnerBody,
} }
} }

View File

@ -1,5 +1,18 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 114993,
"Objects": [{
"StartTime": 114993,
"EndTime": 114993,
"X": 493,
"Y": 92
}, {
"StartTime": 115290,
"EndTime": 115290,
"X": 451.659241,
"Y": 267.188
}]
}, {
"StartTime": 118858.0, "StartTime": 118858.0,
"Objects": [{ "Objects": [{
"StartTime": 118858.0, "StartTime": 118858.0,

View File

@ -9,7 +9,9 @@ SliderMultiplier:1.87
SliderTickRate:1 SliderTickRate:1
[TimingPoints] [TimingPoints]
49051,230.769230769231,4,2,1,15,1,0 114000,346.820809248555,4,2,1,71,1,0
118000,230.769230769231,4,2,1,15,1,0
[HitObjects] [HitObjects]
493,92,114993,2,0,P|472:181|442:308,1,180,12|0,0:0|0:0,0:0:0:0:
219,215,118858,2,0,P|224:170|244:-10,1,187,8|2,0:0|0:0,0:0:0:0: 219,215,118858,2,0,P|224:170|244:-10,1,187,8|2,0:0|0:0,0:0:0:0:

View File

@ -0,0 +1,68 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
public class DefaultSpinner : CompositeDrawable
{
private DrawableSpinner drawableSpinner;
private OsuSpriteText bonusCounter;
public DefaultSpinner()
{
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject)
{
drawableSpinner = (DrawableSpinner)drawableHitObject;
AddRangeInternal(new Drawable[]
{
new DefaultSpinnerDisc
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
bonusCounter = new OsuSpriteText
{
Alpha = 0,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Numeric.With(size: 24),
Y = -120,
}
});
}
private IBindable<double> gainedBonus;
protected override void LoadComplete()
{
base.LoadComplete();
gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy();
gainedBonus.BindValueChanged(bonus =>
{
bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo);
bonusCounter.FadeOutFromOne(1500);
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
});
}
}
}

View File

@ -40,14 +40,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public DefaultSpinnerDisc() public DefaultSpinnerDisc()
{ {
RelativeSizeAxes = Axes.Both;
// we are slightly bigger than our parent, to clip the top and bottom of the circle // we are slightly bigger than our parent, to clip the top and bottom of the circle
// this should probably be revisited when scaled spinners are a thing. // this should probably be revisited when scaled spinners are a thing.
Scale = new Vector2(initial_scale); Scale = new Vector2(initial_scale);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -74,10 +74,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
private void updateState(DrawableHitObject drawableObject, ArmedState state) private void updateState(DrawableHitObject drawableObject, ArmedState state)
{ {
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true)) using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
{
glow.FadeOut(400); glow.FadeOut(400);
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
{
switch (state) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public string Text public string Text
{ {
get => number.Text; get => number.Text.ToString();
set => number.Text = value; set => number.Text = value;
} }

View File

@ -1,47 +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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
/// <summary>
/// Shows incremental bonus score achieved for a spinner.
/// </summary>
public class SpinnerBonusDisplay : CompositeDrawable
{
private static readonly int score_per_tick = new SpinnerBonusTick().CreateJudgement().MaxNumericResult;
private readonly OsuSpriteText bonusCounter;
public SpinnerBonusDisplay()
{
AutoSizeAxes = Axes.Both;
InternalChild = bonusCounter = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Numeric.With(size: 24),
Alpha = 0,
};
}
private int displayedCount;
public void SetBonusCount(int count)
{
if (displayedCount == count)
return;
displayedCount = count;
bonusCounter.Text = $"{score_per_tick * count}";
bonusCounter.FadeOutFromOne(1500);
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
}
}
}

View File

@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
AddInternal(scaleContainer = new Container AddInternal(scaleContainer = new Container
{ {
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Y = SPINNER_Y_CENTRE,
Children = new Drawable[] Children = new Drawable[]
{ {
glow = new Sprite glow = new Sprite

View File

@ -33,30 +33,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
AddInternal(new Container AddRangeInternal(new Drawable[]
{
// the old-style spinner relied heavily on absolute screen-space coordinate values.
// wrap everything in a container simulating absolute coords to preserve alignment
// as there are skins that depend on it.
Width = 640,
Height = 480,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{ {
new Sprite new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-background"), Texture = source.GetTexture("spinner-background"),
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE),
Y = SPINNER_Y_CENTRE,
}, },
disc = new Sprite disc = new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-circle"), Texture = source.GetTexture("spinner-circle"),
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE),
Y = SPINNER_Y_CENTRE,
}, },
metre = new Container metre = new Container
{ {
@ -64,8 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// this anchor makes no sense, but that's what stable uses. // this anchor makes no sense, but that's what stable uses.
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
// adjustment for stable (metre has additional offset) Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET },
Margin = new MarginPadding { Top = 20 },
Masking = true, Masking = true,
Child = metreSprite = new Sprite Child = metreSprite = new Sprite
{ {
@ -75,7 +67,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE)
} }
} }
}
}); });
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Globalization;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -16,6 +17,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
public abstract class LegacySpinner : CompositeDrawable public abstract class LegacySpinner : CompositeDrawable
{ {
/// <remarks>
/// All constants are in osu!stable's gamefield space, which is shifted 16px downwards.
/// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space.
/// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable)
/// </remarks>
protected const float SPINNER_TOP_OFFSET = 45f - 16f;
protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f;
protected const float SPRITE_SCALE = 0.625f; protected const float SPRITE_SCALE = 0.625f;
protected DrawableSpinner DrawableSpinner { get; private set; } protected DrawableSpinner DrawableSpinner { get; private set; }
@ -23,36 +33,64 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private Sprite spin; private Sprite spin;
private Sprite clear; private Sprite clear;
private LegacySpriteText bonusCounter;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject, ISkinSource source) private void load(DrawableHitObject drawableHitObject, ISkinSource source)
{ {
RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre;
Origin = Anchor.Centre;
// osu!stable positions spinner components in window-space (as opposed to gamefield-space). This is a 640x480 area taking up the entire screen.
// In lazer, the gamefield-space positional transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to make this area take up the entire window space.
Size = new Vector2(640, 480);
Position = new Vector2(0, -8f);
DrawableSpinner = (DrawableSpinner)drawableHitObject; DrawableSpinner = (DrawableSpinner)drawableHitObject;
AddRangeInternal(new[] Container overlayContainer;
AddInternal(overlayContainer = new Container
{
Depth = float.MinValue,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{ {
spin = new Sprite spin = new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Depth = float.MinValue,
Texture = source.GetTexture("spinner-spin"), Texture = source.GetTexture("spinner-spin"),
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Y = 120 - 45 // offset temporarily to avoid overlapping default spin counter Y = SPINNER_TOP_OFFSET + 335,
}, },
clear = new Sprite clear = new Sprite
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MinValue,
Alpha = 0, Alpha = 0,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-clear"), Texture = source.GetTexture("spinner-clear"),
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Y = -60 Y = SPINNER_TOP_OFFSET + 115,
}, },
});
} }
});
bonusCounter = (source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as LegacySpriteText)?.With(c =>
{
c.Alpha = 0f;
c.Anchor = Anchor.TopCentre;
c.Origin = Anchor.Centre;
c.Font = c.Font.With(fixedWidth: false);
c.Scale = new Vector2(SPRITE_SCALE);
c.Y = SPINNER_TOP_OFFSET + 299;
});
if (bonusCounter != null)
overlayContainer.Add(bonusCounter);
}
private IBindable<double> gainedBonus;
private readonly Bindable<bool> completed = new Bindable<bool>(); private readonly Bindable<bool> completed = new Bindable<bool>();
@ -60,6 +98,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
base.LoadComplete(); base.LoadComplete();
if (bonusCounter != null)
{
gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy();
gainedBonus.BindValueChanged(bonus =>
{
bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo);
bonusCounter.FadeOutFromOne(800, Easing.Out);
bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out);
});
}
completed.BindValueChanged(onCompletedChanged, true); completed.BindValueChanged(onCompletedChanged, true);
DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms; DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms;

View File

@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
@ -13,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture] [TestFixture]
public class TestSceneHitExplosion : TaikoSkinnableTestScene public class TestSceneHitExplosion : TaikoSkinnableTestScene
{ {
protected override double TimePerAction => 100;
[Test] [Test]
public void TestNormalHit() public void TestNormalHit()
{ {
@ -21,11 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss))));
} }
[Test] [TestCase(HitResult.Great)]
public void TestStrongHit([Values(false, true)] bool hitBoth) [TestCase(HitResult.Ok)]
public void TestStrongHit(HitResult type)
{ {
AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); AddStep("create hit", () => SetContents(() => getContentFor(createStrongHit(type))));
AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth)))); AddStep("visualise second hit",
() => this.ChildrenOfType<HitExplosion>()
.ForEach(e => e.VisualiseSecondHit(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()))));
} }
private Drawable getContentFor(DrawableTestHit hit) private Drawable getContentFor(DrawableTestHit hit)
@ -38,17 +47,17 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
// the hit needs to be added to hierarchy in order for nested objects to be created correctly. // the hit needs to be added to hierarchy in order for nested objects to be created correctly.
// setting zero alpha is supposed to prevent the test from looking broken. // setting zero alpha is supposed to prevent the test from looking broken.
hit.With(h => h.Alpha = 0), hit.With(h => h.Alpha = 0),
new HitExplosion(hit, hit.Type) new HitExplosion(hit.Type)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
} }.With(explosion => explosion.Apply(hit))
} }
}; };
} }
private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth); private DrawableTestHit createStrongHit(HitResult type) => new DrawableTestStrongHit(Time.Current, type);
} }
} }

View File

@ -11,7 +11,6 @@ using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -108,12 +107,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
{ {
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
Hit hit = new Hit(); Hit hit = new Hit { StartTime = DrawableRuleset.Playfield.Time.Current };
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h); DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult });
} }
private void addStrongHitJudgement(bool kiai) private void addStrongHitJudgement(bool kiai)
@ -122,6 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit hit = new Hit Hit hit = new Hit
{ {
StartTime = DrawableRuleset.Playfield.Time.Current,
IsStrong = true, IsStrong = true,
Samples = createSamples(strong: true) Samples = createSamples(strong: true)
}; };
@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
DrawableRuleset.Playfield.Add(h); DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great });
} }
private void addMissJudgement() private void addMissJudgement()

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// </summary> /// </summary>
private int currentMonoLength; private int currentMonoLength;
public Colour(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
// changing from/to a drum roll or a swell does not constitute a colour change. // changing from/to a drum roll or a swell does not constitute a colour change.

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -47,6 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// </summary> /// </summary>
private int notesSinceRhythmChange; private int notesSinceRhythmChange;
public Rhythm(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
// drum rolls and swells are exempt. // drum rolls and swells are exempt.

View File

@ -5,6 +5,7 @@ using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -48,8 +49,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Creates a <see cref="Stamina"/> skill. /// Creates a <see cref="Stamina"/> skill.
/// </summary> /// </summary>
/// <param name="mods">Mods for use in skill calculations.</param>
/// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param> /// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param>
public Stamina(bool rightHand) public Stamina(Mod[] mods, bool rightHand)
: base(mods)
{ {
hand = rightHand ? 1 : 0; hand = rightHand ? 1 : 0;
} }

View File

@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{ {
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Colour(), new Colour(mods),
new Rhythm(), new Rhythm(mods),
new Stamina(true), new Stamina(mods, true),
new Stamina(false), new Stamina(mods, false),
}; };
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -52,32 +52,24 @@ namespace osu.Game.Rulesets.Taiko.Edit
public void SetStrongState(bool state) public void SetStrongState(bool state)
{ {
var hits = EditorBeatmap.SelectedHitObjects.OfType<Hit>(); EditorBeatmap.PerformOnSelection(h =>
EditorBeatmap.BeginChange();
foreach (var h in hits)
{ {
if (h.IsStrong != state) if (!(h is Hit taikoHit)) return;
{
h.IsStrong = state;
EditorBeatmap.Update(h);
}
}
EditorBeatmap.EndChange(); if (taikoHit.IsStrong != state)
{
taikoHit.IsStrong = state;
EditorBeatmap.Update(taikoHit);
}
});
} }
public void SetRimState(bool state) public void SetRimState(bool state)
{ {
var hits = EditorBeatmap.SelectedHitObjects.OfType<Hit>(); EditorBeatmap.PerformOnSelection(h =>
{
EditorBeatmap.BeginChange(); if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
});
foreach (var h in hits)
h.Type = state ? HitType.Rim : HitType.Centre;
EditorBeatmap.EndChange();
} }
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection) protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)

View File

@ -1,22 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI;
namespace osu.Game.Rulesets.Taiko.Skinning.Legacy namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{ {
public class LegacyHitExplosion : CompositeDrawable public class LegacyHitExplosion : CompositeDrawable, IAnimatableHitExplosion
{ {
private readonly Drawable sprite; private readonly Drawable sprite;
private readonly Drawable strongSprite;
private DrawableStrongNestedHit nestedStrongHit; [CanBeNull]
private bool switchedToStrongSprite; private readonly Drawable strongSprite;
/// <summary> /// <summary>
/// Creates a new legacy hit explosion. /// Creates a new legacy hit explosion.
@ -27,14 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
/// </remarks> /// </remarks>
/// <param name="sprite">The normal legacy explosion sprite.</param> /// <param name="sprite">The normal legacy explosion sprite.</param>
/// <param name="strongSprite">The strong legacy explosion sprite.</param> /// <param name="strongSprite">The strong legacy explosion sprite.</param>
public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null) public LegacyHitExplosion(Drawable sprite, [CanBeNull] Drawable strongSprite = null)
{ {
this.sprite = sprite; this.sprite = sprite;
this.strongSprite = strongSprite; this.strongSprite = strongSprite;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(DrawableHitObject judgedObject) private void load()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -56,45 +56,30 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
s.Origin = Anchor.Centre; s.Origin = Anchor.Centre;
})); }));
} }
if (judgedObject is DrawableHit hit)
nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit;
} }
protected override void LoadComplete() public void Animate(DrawableHitObject drawableHitObject)
{ {
base.LoadComplete();
const double animation_time = 120; const double animation_time = 120;
(sprite as IFramedAnimation)?.GotoFrame(0);
(strongSprite as IFramedAnimation)?.GotoFrame(0);
this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5); this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5);
this.ScaleTo(0.6f) this.ScaleTo(0.6f)
.Then().ScaleTo(1.1f, animation_time * 0.8) .Then().ScaleTo(1.1f, animation_time * 0.8)
.Then().ScaleTo(0.9f, animation_time * 0.4) .Then().ScaleTo(0.9f, animation_time * 0.4)
.Then().ScaleTo(1f, animation_time * 0.2); .Then().ScaleTo(1f, animation_time * 0.2);
Expire(true);
} }
protected override void Update() public void AnimateSecondHit()
{ {
base.Update(); if (strongSprite == null)
return;
if (shouldSwitchToStrongSprite() && !switchedToStrongSprite)
{
sprite.FadeOut(50, Easing.OutQuint); sprite.FadeOut(50, Easing.OutQuint);
strongSprite.FadeIn(50, Easing.OutQuint); strongSprite.FadeIn(50, Easing.OutQuint);
switchedToStrongSprite = true;
}
}
private bool shouldSwitchToStrongSprite()
{
if (nestedStrongHit == null || strongSprite == null)
return false;
return nestedStrongHit.IsHit;
} }
} }
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{ {
// if a taiko skin is providing explosion sprites, hide the judgements completely // if a taiko skin is providing explosion sprites, hide the judgements completely
if (hasExplosion.Value) if (hasExplosion.Value)
return Drawable.Empty(); return Drawable.Empty().With(d => d.Expire());
} }
if (!(component is TaikoSkinComponent taikoComponent)) if (!(component is TaikoSkinComponent taikoComponent))
@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
// suppress the default kiai explosion if the skin brings its own sprites. // suppress the default kiai explosion if the skin brings its own sprites.
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
if (hasExplosion.Value) if (hasExplosion.Value)
return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); return Drawable.Empty().With(d => d.Expire());
return null; return null;
@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}");
} }
public override Sample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); public override ISample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Source.GetConfig<TLookup, TValue>(lookup); public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Source.GetConfig<TLookup, TValue>(lookup);

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -57,43 +58,43 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new TaikoModNightcore(); yield return new TaikoModNightcore();
else if (mods.HasFlag(LegacyMods.DoubleTime)) else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new TaikoModDoubleTime(); yield return new TaikoModDoubleTime();
if (mods.HasFlag(LegacyMods.Perfect)) if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new TaikoModPerfect(); yield return new TaikoModPerfect();
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new TaikoModSuddenDeath(); yield return new TaikoModSuddenDeath();
if (mods.HasFlag(LegacyMods.Cinema)) if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new TaikoModCinema(); yield return new TaikoModCinema();
else if (mods.HasFlag(LegacyMods.Autoplay)) else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new TaikoModAutoplay(); yield return new TaikoModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlagFast(LegacyMods.Easy))
yield return new TaikoModEasy(); yield return new TaikoModEasy();
if (mods.HasFlag(LegacyMods.Flashlight)) if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new TaikoModFlashlight(); yield return new TaikoModFlashlight();
if (mods.HasFlag(LegacyMods.HalfTime)) if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new TaikoModHalfTime(); yield return new TaikoModHalfTime();
if (mods.HasFlag(LegacyMods.HardRock)) if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new TaikoModHardRock(); yield return new TaikoModHardRock();
if (mods.HasFlag(LegacyMods.Hidden)) if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new TaikoModHidden(); yield return new TaikoModHidden();
if (mods.HasFlag(LegacyMods.NoFail)) if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new TaikoModNoFail(); yield return new TaikoModNoFail();
if (mods.HasFlag(LegacyMods.Relax)) if (mods.HasFlagFast(LegacyMods.Relax))
yield return new TaikoModRelax(); yield return new TaikoModRelax();
if (mods.HasFlag(LegacyMods.Random)) if (mods.HasFlagFast(LegacyMods.Random))
yield return new TaikoModRandom(); yield return new TaikoModRandom();
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -13,19 +14,23 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
internal class DefaultHitExplosion : CircularContainer internal class DefaultHitExplosion : CircularContainer, IAnimatableHitExplosion
{ {
private readonly DrawableHitObject judgedObject;
private readonly HitResult result; private readonly HitResult result;
public DefaultHitExplosion(DrawableHitObject judgedObject, HitResult result) [CanBeNull]
private Box body;
[Resolved]
private OsuColour colours { get; set; }
public DefaultHitExplosion(HitResult result)
{ {
this.judgedObject = judgedObject;
this.result = result; this.result = result;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -40,26 +45,36 @@ namespace osu.Game.Rulesets.Taiko.UI
if (!result.IsHit()) if (!result.IsHit())
return; return;
bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;
InternalChildren = new[] InternalChildren = new[]
{ {
new Box body = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = isRim ? colours.BlueDarker : colours.PinkDarker,
} }
}; };
updateColour();
} }
protected override void LoadComplete() private void updateColour([CanBeNull] DrawableHitObject judgedObject = null)
{ {
base.LoadComplete(); if (body == null)
return;
bool isRim = (judgedObject?.HitObject as Hit)?.Type == HitType.Rim;
body.Colour = isRim ? colours.BlueDarker : colours.PinkDarker;
}
public void Animate(DrawableHitObject drawableHitObject)
{
updateColour(drawableHitObject);
this.ScaleTo(3f, 1000, Easing.OutQuint); this.ScaleTo(3f, 1000, Easing.OutQuint);
this.FadeOut(500); this.FadeOut(500);
}
Expire(true); public void AnimateSecondHit()
{
} }
} }
} }

View File

@ -3,7 +3,6 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
@ -12,16 +11,6 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary> /// </summary>
public class DrawableTaikoJudgement : DrawableJudgement public class DrawableTaikoJudgement : DrawableJudgement
{ {
/// <summary>
/// Creates a new judgement text.
/// </summary>
/// <param name="judgedObject">The object which is being judged.</param>
/// <param name="result">The judgement to visualise.</param>
public DrawableTaikoJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(result, judgedObject)
{
}
protected override void ApplyHitAnimations() protected override void ApplyHitAnimations()
{ {
this.MoveToY(-100, 500); this.MoveToY(-100, 500);

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using JetBrains.Annotations;
using osuTK; using osuTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -16,31 +18,37 @@ namespace osu.Game.Rulesets.Taiko.UI
/// <summary> /// <summary>
/// A circle explodes from the hit target to indicate a hitobject has been hit. /// A circle explodes from the hit target to indicate a hitobject has been hit.
/// </summary> /// </summary>
internal class HitExplosion : CircularContainer internal class HitExplosion : PoolableDrawable
{ {
public override bool RemoveWhenNotAlive => true; public override bool RemoveWhenNotAlive => true;
public override bool RemoveCompletedTransforms => false;
[Cached(typeof(DrawableHitObject))]
public readonly DrawableHitObject JudgedObject;
private readonly HitResult result; private readonly HitResult result;
private double? secondHitTime;
[CanBeNull]
public DrawableHitObject JudgedObject;
private SkinnableDrawable skinnable; private SkinnableDrawable skinnable;
public override double LifetimeStart => skinnable.Drawable.LifetimeStart; /// <summary>
/// This constructor only exists to meet the <c>new()</c> type constraint of <see cref="DrawablePool{T}"/>.
public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; /// </summary>
public HitExplosion()
public HitExplosion(DrawableHitObject judgedObject, HitResult result) : this(HitResult.Great)
{
}
public HitExplosion(HitResult result)
{ {
JudgedObject = judgedObject;
this.result = result; this.result = result;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE); Size = new Vector2(TaikoHitObject.DEFAULT_SIZE);
RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
} }
@ -48,7 +56,47 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result)); InternalChild = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(result));
skinnable.OnSkinChanged += runAnimation;
}
public void Apply([CanBeNull] DrawableHitObject drawableHitObject)
{
JudgedObject = drawableHitObject;
secondHitTime = null;
}
protected override void PrepareForUse()
{
base.PrepareForUse();
runAnimation();
}
private void runAnimation()
{
if (JudgedObject?.Result == null)
return;
double resultTime = JudgedObject.Result.TimeAbsolute;
LifetimeStart = resultTime;
ApplyTransformsAt(double.MinValue, true);
ClearTransforms(true);
using (BeginAbsoluteSequence(resultTime))
(skinnable.Drawable as IAnimatableHitExplosion)?.Animate(JudgedObject);
if (secondHitTime != null)
{
using (BeginAbsoluteSequence(secondHitTime.Value))
{
this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50);
(skinnable.Drawable as IAnimatableHitExplosion)?.AnimateSecondHit();
}
}
LifetimeEnd = skinnable.Drawable.LatestTransformEndTime;
} }
private static TaikoSkinComponents getComponentName(HitResult result) private static TaikoSkinComponents getComponentName(HitResult result)
@ -68,12 +116,10 @@ namespace osu.Game.Rulesets.Taiko.UI
throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}"); throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}");
} }
/// <summary> public void VisualiseSecondHit(JudgementResult judgementResult)
/// Transforms this hit explosion to visualise a secondary hit.
/// </summary>
public void VisualiseSecondHit()
{ {
this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); secondHitTime = judgementResult.TimeAbsolute;
runAnimation();
} }
} }
} }

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.UI
{
/// <summary>
/// Pool for hit explosions of a specific type.
/// </summary>
internal class HitExplosionPool : DrawablePool<HitExplosion>
{
private readonly HitResult hitResult;
public HitExplosionPool(HitResult hitResult)
: base(15)
{
this.hitResult = hitResult;
}
protected override HitExplosion CreateNewDrawable() => new HitExplosion(hitResult);
}
}

View File

@ -0,0 +1,23 @@
// 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.Taiko.UI
{
/// <summary>
/// A skinnable element of a hit explosion that supports playing an animation from the current point in time.
/// </summary>
public interface IAnimatableHitExplosion
{
/// <summary>
/// Shows the hit explosion for the supplied <paramref name="drawableHitObject"/>.
/// </summary>
void Animate(DrawableHitObject drawableHitObject);
/// <summary>
/// Transforms the hit explosion to visualise a secondary hit.
/// </summary>
void AnimateSecondHit();
}
}

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -17,6 +19,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -38,6 +41,9 @@ namespace osu.Game.Rulesets.Taiko.UI
internal Drawable HitTarget; internal Drawable HitTarget;
private SkinnableDrawable mascot; private SkinnableDrawable mascot;
private readonly IDictionary<HitResult, DrawablePool<DrawableTaikoJudgement>> judgementPools = new Dictionary<HitResult, DrawablePool<DrawableTaikoJudgement>>();
private readonly IDictionary<HitResult, HitExplosionPool> explosionPools = new Dictionary<HitResult, HitExplosionPool>();
private ProxyContainer topLevelHitContainer; private ProxyContainer topLevelHitContainer;
private Container rightArea; private Container rightArea;
private Container leftArea; private Container leftArea;
@ -159,6 +165,17 @@ namespace osu.Game.Rulesets.Taiko.UI
RegisterPool<Swell, DrawableSwell>(5); RegisterPool<Swell, DrawableSwell>(5);
RegisterPool<SwellTick, DrawableSwellTick>(100); RegisterPool<SwellTick, DrawableSwellTick>(100);
var hitWindows = new TaikoHitWindows();
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)))
{
judgementPools.Add(result, new DrawablePool<DrawableTaikoJudgement>(15));
explosionPools.Add(result, new HitExplosionPool(result));
}
AddRangeInternal(judgementPools.Values);
AddRangeInternal(explosionPools.Values);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -270,7 +287,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
case TaikoStrongJudgement _: case TaikoStrongJudgement _:
if (result.IsHit) if (result.IsHit)
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(); hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(result);
break; break;
case TaikoDrumRollTickJudgement _: case TaikoDrumRollTickJudgement _:
@ -283,13 +300,15 @@ namespace osu.Game.Rulesets.Taiko.UI
break; break;
default: default:
judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) judgementContainer.Add(judgementPools[result.Type].Get(j =>
{ {
Anchor = result.IsHit ? Anchor.TopLeft : Anchor.CentreLeft, j.Apply(result, judgedObject);
Origin = result.IsHit ? Anchor.BottomCentre : Anchor.Centre,
RelativePositionAxes = Axes.X, j.Anchor = result.IsHit ? Anchor.TopLeft : Anchor.CentreLeft;
X = result.IsHit ? judgedObject.Position.X : 0, j.Origin = result.IsHit ? Anchor.BottomCentre : Anchor.Centre;
}); j.RelativePositionAxes = Axes.X;
j.X = result.IsHit ? judgedObject.Position.X : 0;
}));
var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre;
addExplosion(judgedObject, result.Type, type); addExplosion(judgedObject, result.Type, type);
@ -302,7 +321,8 @@ namespace osu.Game.Rulesets.Taiko.UI
private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type)
{ {
hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); hitExplosionContainer.Add(explosionPools[result]
.Get(explosion => explosion.Apply(drawableObject)));
if (drawableObject.HitObject.Kiai) if (drawableObject.HitObject.Kiai)
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
} }

View File

@ -20,6 +20,9 @@
<ItemGroup> <ItemGroup>
<None Include="Properties\AndroidManifest.xml" /> <None Include="Properties\AndroidManifest.xml" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CA2007</NoWarn>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs"> <Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link> <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
@ -71,7 +74,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

Some files were not shown because too many files have changed in this diff Show More