diff --git a/appveyor.yml b/appveyor.yml index 0d5878aa77..cc6dfb9c88 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ install: - cmd: git submodule update --init --recursive - cmd: choco install resharper-clt -y - cmd: choco install nvika -y - - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.1/CodeFileSanity.exe + - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.2/CodeFileSanity.exe before_build: - cmd: CodeFileSanity.exe - cmd: nuget restore diff --git a/osu-framework b/osu-framework index 51737ec132..a7c99e06ff 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 51737ec1320b4ea9dce4978f24e1d77a28f0a98e +Subproject commit a7c99e06ff4c3f56fad24bec170eb93f42b1e149 diff --git a/osu-resources b/osu-resources index f85c594c18..0cba3cbc16 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit f85c594c182db2b01233e29ca52639b7baa00402 +Subproject commit 0cba3cbc167cfe94e07fe5b629c925e190be939e diff --git a/osu.Desktop.Deploy/Properties/AssemblyInfo.cs b/osu.Desktop.Deploy/Properties/AssemblyInfo.cs index 8df81400c1..5841c1b082 100644 --- a/osu.Desktop.Deploy/Properties/AssemblyInfo.cs +++ b/osu.Desktop.Deploy/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Desktop.Deploy")] @@ -16,8 +16,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -27,11 +27,11 @@ using System.Runtime.InteropServices; // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] diff --git a/osu.Desktop.Tests/BenchmarkTest.cs b/osu.Desktop.Tests/VisualTests.cs similarity index 83% rename from osu.Desktop.Tests/BenchmarkTest.cs rename to osu.Desktop.Tests/VisualTests.cs index 6d001655ec..6519dbe917 100644 --- a/osu.Desktop.Tests/BenchmarkTest.cs +++ b/osu.Desktop.Tests/VisualTests.cs @@ -13,10 +13,10 @@ using osu.Game.Modes.Taiko; namespace osu.Desktop.Tests { [TestFixture] - public class BenchmarkTest + public class VisualTests { [Test] - public void TestBenchmark() + public void TestVisualTests() { using (var host = new HeadlessGameHost()) { @@ -25,7 +25,7 @@ namespace osu.Desktop.Tests Ruleset.Register(new ManiaRuleset()); Ruleset.Register(new CatchRuleset()); - host.Run(new Benchmark()); + host.Run(new AutomatedVisualTestGame()); } } } diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index ad69994592..d1b20bd161 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -56,7 +56,7 @@ - + diff --git a/osu.Desktop.VisualTests/AutomatedVisualTestGame.cs b/osu.Desktop.VisualTests/AutomatedVisualTestGame.cs new file mode 100644 index 0000000000..b08588b29c --- /dev/null +++ b/osu.Desktop.VisualTests/AutomatedVisualTestGame.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Testing; +using osu.Game; + +namespace osu.Desktop.VisualTests +{ + public class AutomatedVisualTestGame : OsuGameBase + { + protected override void LoadComplete() + { + base.LoadComplete(); + + // Have to construct this here, rather than in the constructor, because + // we depend on some dependencies to be loaded within OsuGameBase.load(). + Add(new TestRunner(new TestBrowser())); + } + } +} \ No newline at end of file diff --git a/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs b/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs index f135a6affa..5e3f5b5133 100644 --- a/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Desktop.VisualTests/Beatmaps/TestWorkingBeatmap.cs @@ -16,7 +16,7 @@ namespace osu.Desktop.VisualTests.Beatmaps } private readonly Beatmap beatmap; - + protected override Beatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; protected override Track GetTrack() => null; diff --git a/osu.Desktop.VisualTests/Benchmark.cs b/osu.Desktop.VisualTests/Benchmark.cs deleted file mode 100644 index 0a15b38fc2..0000000000 --- a/osu.Desktop.VisualTests/Benchmark.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Screens.Testing; -using osu.Game; - -namespace osu.Desktop.VisualTests -{ - public class Benchmark : OsuGameBase - { - private const double time_per_test = 200; - - [BackgroundDependencyLoader] - private void load() - { - Host.MaximumDrawHz = int.MaxValue; - Host.MaximumUpdateHz = int.MaxValue; - Host.MaximumInactiveHz = int.MaxValue; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - TestBrowser f = new TestBrowser(); - Add(f); - - Console.WriteLine($@"{Time}: Running {f.TestCount} tests for {time_per_test}ms each..."); - - for (int i = 1; i < f.TestCount; i++) - { - int loadableCase = i; - Scheduler.AddDelayed(delegate - { - f.LoadTest(loadableCase); - Console.WriteLine($@"{Time}: Switching to test #{loadableCase}"); - }, loadableCase * time_per_test); - } - - Scheduler.AddDelayed(Host.Exit, f.TestCount * time_per_test); - } - } -} diff --git a/osu.Desktop.VisualTests/Platform/TestStorage.cs b/osu.Desktop.VisualTests/Platform/TestStorage.cs index c5502d5d5d..f711ddac24 100644 --- a/osu.Desktop.VisualTests/Platform/TestStorage.cs +++ b/osu.Desktop.VisualTests/Platform/TestStorage.cs @@ -15,7 +15,7 @@ namespace osu.Desktop.VisualTests.Platform public TestStorage(string baseName) : base(baseName) { } - + public override SQLiteConnection GetDatabase(string name) { ISQLitePlatform platform; diff --git a/osu.Desktop.VisualTests/Program.cs b/osu.Desktop.VisualTests/Program.cs index 6760852cf0..fe1cdfd7f0 100644 --- a/osu.Desktop.VisualTests/Program.cs +++ b/osu.Desktop.VisualTests/Program.cs @@ -27,7 +27,7 @@ namespace osu.Desktop.VisualTests Ruleset.Register(new CatchRuleset()); if (benchmark) - host.Run(new Benchmark()); + host.Run(new AutomatedVisualTestGame()); else host.Run(new VisualTestGame()); } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.cs b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.cs index bb7df19202..e755924a15 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetailArea.cs @@ -3,7 +3,7 @@ using OpenTK; using osu.Framework.Graphics; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Screens.Select; namespace osu.Desktop.VisualTests.Tests diff --git a/osu.Desktop.VisualTests/Tests/TestCaseBeatmapOptionsOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapOptionsOverlay.cs index a13f6005bf..7c211227c6 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapOptionsOverlay.cs @@ -3,7 +3,7 @@ using OpenTK.Graphics; using OpenTK.Input; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Screens.Select.Options; @@ -26,7 +26,7 @@ namespace osu.Desktop.VisualTests.Tests Add(overlay); - AddButton(@"Toggle", overlay.ToggleVisibility); + AddStep(@"Toggle", overlay.ToggleVisibility); } } } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs index 08765a9b0f..2663c952cf 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs @@ -1,17 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; using osu.Game.Overlays; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseChatDisplay : TestCase { - private ScheduledDelegate messageRequest; - public override string Description => @"Testing chat api and overlay"; public override void Reset() diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDialogOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCaseDialogOverlay.cs index c9edcb8a54..90e214c3c9 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseDialogOverlay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseDialogOverlay.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -20,7 +20,7 @@ namespace osu.Desktop.VisualTests.Tests Add(overlay = new DialogOverlay()); - AddButton("dialog #1", () => overlay.Push(new PopupDialog + AddStep("dialog #1", () => overlay.Push(new PopupDialog { Icon = FontAwesome.fa_trash_o, HeaderText = @"Confirm deletion of", @@ -40,7 +40,7 @@ namespace osu.Desktop.VisualTests.Tests }, })); - AddButton("dialog #2", () => overlay.Push(new PopupDialog + AddStep("dialog #2", () => overlay.Push(new PopupDialog { Icon = FontAwesome.fa_gear, HeaderText = @"What do you want to do with", diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawings.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawings.cs index 00e41de254..a0463516de 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseDrawings.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawings.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Screens.Tournament; using osu.Game.Screens.Tournament.Teams; using osu.Game.Users; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs index e876c21a12..3129cade63 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs @@ -5,7 +5,7 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Database; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index 2a20cad2db..99da7d1c73 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Modes.Objects; using osu.Game.Modes.Objects.Drawables; @@ -93,19 +93,28 @@ namespace osu.Desktop.VisualTests.Tests playbackSpeed.TriggerChange(); - AddButton(@"circles", () => load(HitObjectType.Circle)); - AddButton(@"slider", () => load(HitObjectType.Slider)); - AddButton(@"spinner", () => load(HitObjectType.Spinner)); + AddStep(@"circles", () => load(HitObjectType.Circle)); + AddStep(@"slider", () => load(HitObjectType.Slider)); + AddStep(@"spinner", () => load(HitObjectType.Spinner)); - AddToggle(@"auto", state => { auto = state; load(mode); }); + AddToggleStep(@"auto", state => { auto = state; load(mode); }); - ButtonsContainer.Add(new SpriteText { Text = "Playback Speed" }); - ButtonsContainer.Add(new BasicSliderBar + Add(new Container { - Width = 150, - Height = 10, - SelectionColor = Color4.Orange, - Bindable = playbackSpeed + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SpriteText { Text = "Playback Speed" }, + new BasicSliderBar + { + Width = 150, + Height = 10, + SelectionColor = Color4.Orange, + Value = playbackSpeed + } + } }); framedClock.ProcessFrame(); diff --git a/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs index 9ad439bfbe..7e7782662b 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs @@ -1,16 +1,16 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Graphics; using OpenTK.Input; using osu.Framework.Graphics.UserInterface; using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; using OpenTK; using OpenTK.Graphics; using osu.Framework.MathUtils; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; using osu.Game.Screens.Play; namespace osu.Desktop.VisualTests.Tests @@ -38,19 +38,30 @@ namespace osu.Desktop.VisualTests.Tests }; BindableInt bindable = new BindableInt { MinValue = 0, MaxValue = 200, Default = 50 }; bindable.ValueChanged += delegate { kc.FadeTime = bindable.Value; }; - AddButton("Add Random", () => + AddStep("Add Random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); kc.Add(new KeyCounterKeyboard(key)); }); - ButtonsContainer.Add(new SpriteText { Text = "FadeTime" }); - ButtonsContainer.Add(new TestSliderBar + + Add(new Container { - Width = 150, - Height = 10, - SelectionColor = Color4.Orange, - Bindable = bindable + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SpriteText { Text = "FadeTime" }, + new TestSliderBar + { + Width = 150, + Height = 10, + SelectionColor = Color4.Orange, + Value = bindable + } + } }); + Add(kc); } private class TestSliderBar : SliderBar where T : struct diff --git a/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs b/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs index cc30e3de95..44e52c237e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs @@ -3,10 +3,10 @@ using OpenTK; using osu.Framework.Graphics; -using osu.Framework.Screens.Testing; -using osu.Game.Modes; +using osu.Framework.Testing; using osu.Game.Modes.Mods; using osu.Game.Modes.Osu.Mods; +using osu.Game.Modes.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -218,7 +218,7 @@ namespace osu.Desktop.VisualTests.Tests Size = new Vector2(550f, 450f), }); - AddButton(@"New Scores", newScores); + AddStep(@"New Scores", newScores); newScores(); } } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs b/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs index 36dc3945e2..ddb62598cf 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseMenuButtonSystem.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Menu; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMenuOverlays.cs b/osu.Desktop.VisualTests/Tests/TestCaseMenuOverlays.cs new file mode 100644 index 0000000000..acf98ea86b --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseMenuOverlays.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; +using osu.Framework.Testing; +using osu.Game.Screens.Play; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseMenuOverlays : TestCase + { + public override string Description => @"Tests pause and fail overlays"; + + private PauseOverlay pauseOverlay; + private FailOverlay failOverlay; + private int retryCount; + + public override void Reset() + { + base.Reset(); + + retryCount = 0; + + Add(pauseOverlay = new PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }); + Add(failOverlay = new FailOverlay + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }); + + AddStep(@"Pause", delegate { + if(failOverlay.State == Visibility.Visible) + { + failOverlay.Hide(); + } + pauseOverlay.Show(); + }); + AddStep("Fail", delegate { + if (pauseOverlay.State == Visibility.Visible) + { + pauseOverlay.Hide(); + } + failOverlay.Show(); + }); + AddStep("Add Retry", delegate + { + retryCount++; + pauseOverlay.Retries = retryCount; + failOverlay.Retries = retryCount; + }); + } + } +} diff --git a/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs index eaaa531691..7677682ac8 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Mods; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Modes; namespace osu.Desktop.VisualTests.Tests @@ -25,11 +25,11 @@ namespace osu.Desktop.VisualTests.Tests Anchor = Anchor.BottomCentre, }); - AddButton("Toggle", modSelect.ToggleVisibility); - AddButton("osu!", () => modSelect.PlayMode.Value = PlayMode.Osu); - AddButton("osu!taiko", () => modSelect.PlayMode.Value = PlayMode.Taiko); - AddButton("osu!catch", () => modSelect.PlayMode.Value = PlayMode.Catch); - AddButton("osu!mania", () => modSelect.PlayMode.Value = PlayMode.Mania); + AddStep("Toggle", modSelect.ToggleVisibility); + AddStep("osu!", () => modSelect.PlayMode.Value = PlayMode.Osu); + AddStep("osu!taiko", () => modSelect.PlayMode.Value = PlayMode.Taiko); + AddStep("osu!catch", () => modSelect.PlayMode.Value = PlayMode.Catch); + AddStep("osu!mania", () => modSelect.PlayMode.Value = PlayMode.Mania); } } } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs b/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs index f44f662321..c0c17cd50e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Overlays; @@ -30,7 +30,7 @@ namespace osu.Desktop.VisualTests.Tests Anchor = Anchor.Centre }; Add(mc); - AddToggle(@"Show", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); + AddToggleStep(@"Show", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); } } } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs b/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs index 13f89153e9..8972040b06 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseNotificationManager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.MathUtils; using osu.Game.Overlays; using System.Linq; @@ -30,13 +30,13 @@ namespace osu.Desktop.VisualTests.Tests Origin = Anchor.TopRight, }); - AddToggle(@"show", state => manager.State = state ? Visibility.Visible : Visibility.Hidden); + AddToggleStep(@"show", state => manager.State = state ? Visibility.Visible : Visibility.Hidden); - AddButton(@"simple #1", sendNotification1); - AddButton(@"simple #2", sendNotification2); - AddButton(@"progress #1", sendProgress1); - AddButton(@"progress #2", sendProgress2); - AddButton(@"barrage", () => sendBarrage()); + AddStep(@"simple #1", sendNotification1); + AddStep(@"simple #2", sendNotification2); + AddStep(@"progress #1", sendProgress1); + AddStep(@"progress #2", sendProgress2); + AddStep(@"barrage", () => sendBarrage()); } private void sendBarrage(int remaining = 100) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseOptions.cs b/osu.Desktop.VisualTests/Tests/TestCaseOptions.cs index 1b4ecd726a..ff6bdc8a5a 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseOptions.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseOptions.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Overlays; namespace osu.Desktop.VisualTests.Tests diff --git a/osu.Desktop.VisualTests/Tests/TestCasePauseOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCasePauseOverlay.cs deleted file mode 100644 index ad8039bc66..0000000000 --- a/osu.Desktop.VisualTests/Tests/TestCasePauseOverlay.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Logging; -using osu.Framework.Screens.Testing; -using osu.Game.Screens.Play; - -namespace osu.Desktop.VisualTests.Tests -{ - internal class TestCasePauseOverlay : TestCase - { - public override string Description => @"Tests the pause overlay"; - - private PauseOverlay pauseOverlay; - private int retryCount; - - public override void Reset() - { - base.Reset(); - - Add(pauseOverlay = new PauseOverlay - { - Depth = -1, - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit") - }); - AddButton("Pause", pauseOverlay.Show); - AddButton("Add Retry", delegate - { - retryCount++; - pauseOverlay.Retries = retryCount; - }); - - retryCount = 0; - } - } -} diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs index c97ea929f3..1a43425dda 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Desktop.VisualTests.Platform; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.MathUtils; using osu.Game.Database; using osu.Game.Modes; @@ -14,7 +14,7 @@ namespace osu.Desktop.VisualTests.Tests { internal class TestCasePlaySongSelect : TestCase { - private BeatmapDatabase db, oldDb; + private BeatmapDatabase db; private TestStorage storage; private PlaySongSelect songSelect; @@ -23,12 +23,10 @@ namespace osu.Desktop.VisualTests.Tests public override void Reset() { base.Reset(); - oldDb = Dependencies.Get(); if (db == null) { storage = new TestStorage(@"TestCasePlaySongSelect"); db = new BeatmapDatabase(storage); - Dependencies.Cache(db, true); var sets = new List(); @@ -40,22 +38,19 @@ namespace osu.Desktop.VisualTests.Tests Add(songSelect = new PlaySongSelect()); - AddButton(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); - AddButton(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); - AddButton(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); - AddButton(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); + AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); + AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); + AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); + AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); } - protected override void Dispose(bool isDisposing) - { - if (oldDb != null) - { - Dependencies.Cache(oldDb, true); - db = null; - } + //protected override void Dispose(bool isDisposing) + //{ + // if (oldDb != null) + // db = null; - base.Dispose(isDisposing); - } + // base.Dispose(isDisposing); + //} private BeatmapSetInfo createTestBeatmapSet(int i) { diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs index a08cb3e97e..f36889b02a 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Beatmaps; using OpenTK; using osu.Framework.Graphics.Sprites; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseReplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseReplay.cs index c36fc0a47d..ffdca25bb3 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseReplay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseReplay.cs @@ -1,24 +1,15 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input.Handlers; using osu.Game.Beatmaps; using osu.Game.Modes.Mods; using osu.Game.Modes.Osu.Mods; using osu.Game.Screens.Play; -using System; -using System.IO; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseReplay : TestCasePlayer { - private WorkingBeatmap beatmap; - - private InputHandler replay; - - private Func getReplayStream; - public override string Description => @"Testing replay playback."; protected override Player CreatePlayer(WorkingBeatmap beatmap) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs index be313efed3..f3cca16678 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Modes.UI; @@ -22,8 +22,6 @@ namespace osu.Desktop.VisualTests.Tests int numerator = 0, denominator = 0; - bool maniaHold = false; - ScoreCounter score = new ScoreCounter(7) { Origin = Anchor.TopRight, @@ -68,7 +66,7 @@ namespace osu.Desktop.VisualTests.Tests }; Add(starsLabel); - AddButton(@"Reset all", delegate + AddStep(@"Reset all", delegate { score.Current.Value = 0; comboCounter.Current.Value = 0; @@ -78,7 +76,7 @@ namespace osu.Desktop.VisualTests.Tests starsLabel.Text = stars.Count.ToString("0.00"); }); - AddButton(@"Hit! :D", delegate + AddStep(@"Hit! :D", delegate { score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0); comboCounter.Increment(); @@ -86,20 +84,20 @@ namespace osu.Desktop.VisualTests.Tests accuracyCounter.SetFraction(numerator, denominator); }); - AddButton(@"miss...", delegate + AddStep(@"miss...", delegate { comboCounter.Current.Value = 0; denominator++; accuracyCounter.SetFraction(numerator, denominator); }); - AddButton(@"Alter stars", delegate + AddStep(@"Alter stars", delegate { stars.Count = RNG.NextSingle() * (stars.StarCount + 1); starsLabel.Text = stars.Count.ToString("0.00"); }); - AddButton(@"Stop counters", delegate + AddStep(@"Stop counters", delegate { score.StopRolling(); comboCounter.StopRolling(); diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs index da807d5e53..2d3969b822 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs @@ -3,7 +3,7 @@ using OpenTK; using osu.Framework.Graphics.Primitives; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; @@ -36,9 +36,9 @@ namespace osu.Desktop.VisualTests.Tests filter.PinItem(GroupMode.All); filter.PinItem(GroupMode.RecentlyPlayed); - filter.ItemChanged += (sender, mode) => + filter.SelectedItem.ValueChanged += newFilter => { - text.Text = "Currently Selected: " + mode.ToString(); + text.Text = "Currently Selected: " + newFilter.ToString(); }; } } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs new file mode 100644 index 0000000000..b3cb8c3457 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseTaikoHitObjects : TestCase + { + public override string Description => "Taiko hit objects"; + + private bool kiai; + + public override void Reset() + { + base.Reset(); + + AddToggleStep("Kiai", b => + { + kiai = !kiai; + updateKiaiState(); + }); + + Add(new CirclePiece + { + Position = new Vector2(100, 100), + AccentColour = Color4.DarkRed, + KiaiMode = kiai, + Children = new[] + { + new CentreHitSymbolPiece() + } + }); + + Add(new CirclePiece(true) + { + Position = new Vector2(350, 100), + AccentColour = Color4.DarkRed, + KiaiMode = kiai, + Children = new[] + { + new CentreHitSymbolPiece() + } + }); + + Add(new CirclePiece + { + Position = new Vector2(100, 300), + AccentColour = Color4.DarkBlue, + KiaiMode = kiai, + Children = new[] + { + new RimHitSymbolPiece() + } + }); + + Add(new CirclePiece(true) + { + Position = new Vector2(350, 300), + AccentColour = Color4.DarkBlue, + KiaiMode = kiai, + Children = new[] + { + new RimHitSymbolPiece() + } + }); + + Add(new CirclePiece + { + Position = new Vector2(100, 500), + AccentColour = Color4.Orange, + KiaiMode = kiai, + Children = new[] + { + new SwellSymbolPiece() + } + }); + + Add(new ElongatedCirclePiece + { + Position = new Vector2(575, 100), + AccentColour = Color4.Orange, + KiaiMode = kiai, + Length = 0.10f, + PlayfieldLengthReference = () => DrawSize.X + }); + + Add(new ElongatedCirclePiece(true) + { + Position = new Vector2(575, 300), + AccentColour = Color4.Orange, + KiaiMode = kiai, + Length = 0.10f, + PlayfieldLengthReference = () => DrawSize.X + }); + } + + private void updateKiaiState() + { + foreach (var c in Children.OfType()) + c.KiaiMode = kiai; + } + + private abstract class BaseCircle : Container + { + protected readonly CirclePiece Piece; + + protected BaseCircle(CirclePiece piece) + { + Piece = piece; + + Add(Piece); + } + } + } +} diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index 395a0cab13..88a037afee 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -1,11 +1,15 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.UI; namespace osu.Desktop.VisualTests.Tests @@ -16,16 +20,40 @@ namespace osu.Desktop.VisualTests.Tests private TaikoPlayfield playfield; + protected override double TimePerAction => default_duration * 2; + + private const double default_duration = 300; + + private const float scroll_time = 1000; + public override void Reset() { base.Reset(); - AddButton("Hit!", addHitJudgement); - AddButton("Miss :(", addMissJudgement); + AddStep("Hit!", addHitJudgement); + AddStep("Miss :(", addMissJudgement); + AddStep("DrumRoll", () => addDrumRoll(false)); + AddStep("Strong DrumRoll", () => addDrumRoll(true)); + AddStep("Swell", () => addSwell()); + AddStep("Centre", () => addCentreHit(false)); + AddStep("Strong Centre", () => addCentreHit(true)); + AddStep("Rim", () => addRimHit(false)); + AddStep("Strong Rim", () => addRimHit(true)); + AddStep("Add bar line", () => addBarLine(false)); + AddStep("Add major bar line", () => addBarLine(true)); - Add(playfield = new TaikoPlayfield + + var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; + + Add(new Container { - Y = 200 + Clock = new FramedClock(rateAdjustClock), + RelativeSizeAxes = Axes.X, + Y = 200, + Children = new[] + { + playfield = new TaikoPlayfield() + } }); } @@ -41,7 +69,6 @@ namespace osu.Desktop.VisualTests.Tests Result = HitResult.Hit, TaikoResult = hitResult, TimeOffset = 0, - ComboAtHit = 1, SecondHit = RNG.Next(10) == 0 } }); @@ -54,12 +81,76 @@ namespace osu.Desktop.VisualTests.Tests Judgement = new TaikoJudgement { Result = HitResult.Miss, - TimeOffset = 0, - ComboAtHit = 0 + TimeOffset = 0 } }); } + private void addBarLine(bool major, double delay = scroll_time) + { + BarLine bl = new BarLine + { + StartTime = playfield.Time.Current + delay, + ScrollTime = scroll_time + }; + + playfield.AddBarLine(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + } + + private void addSwell(double duration = default_duration) + { + playfield.Add(new DrawableSwell(new Swell + { + StartTime = playfield.Time.Current + scroll_time, + Duration = duration, + ScrollTime = scroll_time + })); + } + + private void addDrumRoll(bool strong, double duration = default_duration) + { + addBarLine(true); + addBarLine(true, scroll_time + duration); + + var d = new DrumRoll + { + StartTime = playfield.Time.Current + scroll_time, + IsStrong = strong, + Duration = duration, + ScrollTime = scroll_time, + }; + + playfield.Add(new DrawableDrumRoll(d)); + } + + private void addCentreHit(bool strong) + { + Hit h = new Hit + { + StartTime = playfield.Time.Current + scroll_time, + ScrollTime = scroll_time + }; + + if (strong) + playfield.Add(new DrawableCentreHitStrong(h)); + else + playfield.Add(new DrawableCentreHit(h)); + } + + private void addRimHit(bool strong) + { + Hit h = new Hit + { + StartTime = playfield.Time.Current + scroll_time, + ScrollTime = scroll_time + }; + + if (strong) + playfield.Add(new DrawableRimHitStrong(h)); + else + playfield.Add(new DrawableRimHit(h)); + } + private class DrawableTestHit : DrawableHitObject { public DrawableTestHit(TaikoHitObject hitObject) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTextAwesome.cs b/osu.Desktop.VisualTests/Tests/TestCaseTextAwesome.cs index 3ba657d60a..7182ee7c06 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTextAwesome.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTextAwesome.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTwoLayerButton.cs b/osu.Desktop.VisualTests/Tests/TestCaseTwoLayerButton.cs index 4694a6c6ea..2427b6d12c 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTwoLayerButton.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTwoLayerButton.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play; diff --git a/osu.Desktop.VisualTests/VisualTestGame.cs b/osu.Desktop.VisualTests/VisualTestGame.cs index c41bdeef66..e0d168390b 100644 --- a/osu.Desktop.VisualTests/VisualTestGame.cs +++ b/osu.Desktop.VisualTests/VisualTestGame.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Platform; -using osu.Framework.Screens.Testing; +using osu.Framework.Testing; using osu.Game; using osu.Game.Screens.Backgrounds; @@ -14,7 +14,7 @@ namespace osu.Desktop.VisualTests { base.LoadComplete(); - new BackgroundScreenDefault { Depth = 10 }.LoadAsync(this, AddInternal); + LoadComponentAsync(new BackgroundScreenDefault { Depth = 10 }, AddInternal); // Have to construct this here, rather than in the constructor, because // we depend on some dependencies to be loaded within OsuGameBase.load(). @@ -24,6 +24,11 @@ namespace osu.Desktop.VisualTests public override void SetHost(GameHost host) { base.SetHost(host); + + host.UpdateThread.InactiveHz = host.UpdateThread.ActiveHz; + host.DrawThread.InactiveHz = host.DrawThread.ActiveHz; + host.InputThread.InactiveHz = host.InputThread.ActiveHz; + host.Window.CursorState = CursorState.Hidden; } } diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index e3af9d9ca1..99abf3fa41 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -150,6 +150,10 @@ {65dc628f-a640-4111-ab35-3a5652bc1e17} osu.Framework.Desktop + + {007b2356-ab6f-4bd9-96d5-116fc2dce69a} + osu.Framework.Testing + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} osu.Framework @@ -180,11 +184,12 @@ - + + @@ -194,6 +199,7 @@ + @@ -201,7 +207,6 @@ - @@ -212,9 +217,7 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 95870125e3..c2bb39ac4a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -29,7 +29,7 @@ namespace osu.Desktop { base.LoadComplete(); - versionManager.LoadAsync(this); + LoadComponentAsync(versionManager); ScreenChanged += s => { if (!versionManager.IsAlive && s is Intro) diff --git a/osu.Desktop/Properties/AssemblyInfo.cs b/osu.Desktop/Properties/AssemblyInfo.cs index eacfc996d5..fe7ad20124 100644 --- a/osu.Desktop/Properties/AssemblyInfo.cs +++ b/osu.Desktop/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu!lazer")] @@ -16,8 +16,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 934004dfde..fbc342d695 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -123,7 +123,10 @@ $(SolutionDir)\packages\squirrel.windows.1.5.2\lib\Net45\NuGet.Squirrel.dll True - + + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll + True + $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll True @@ -150,6 +153,7 @@ osu.licenseheader + diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index bdeaf1de89..be9b65f0c6 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -7,6 +7,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste + \ No newline at end of file diff --git a/osu.Game.Modes.Catch/CatchRuleset.cs b/osu.Game.Modes.Catch/CatchRuleset.cs index 50224e3fdb..09d8bdb9e5 100644 --- a/osu.Game.Modes.Catch/CatchRuleset.cs +++ b/osu.Game.Modes.Catch/CatchRuleset.cs @@ -10,6 +10,8 @@ using osu.Game.Modes.Mods; using osu.Game.Modes.UI; using osu.Game.Screens.Play; using System.Collections.Generic; +using osu.Game.Modes.Catch.Scoring; +using osu.Game.Modes.Scoring; namespace osu.Game.Modes.Catch { diff --git a/osu.Game.Modes.Catch/Judgements/CatchJudgement.cs b/osu.Game.Modes.Catch/Judgements/CatchJudgement.cs index 8e18c68153..eaacedd7e0 100644 --- a/osu.Game.Modes.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Modes.Catch/Judgements/CatchJudgement.cs @@ -7,8 +7,8 @@ namespace osu.Game.Modes.Catch.Judgements { public class CatchJudgement : Judgement { - public override string ScoreString => string.Empty; + public override string ResultString => string.Empty; - public override string MaxScoreString => string.Empty; + public override string MaxResultString => string.Empty; } } diff --git a/osu.Game.Modes.Catch/Properties/AssemblyInfo.cs b/osu.Game.Modes.Catch/Properties/AssemblyInfo.cs index 07a088e1e9..1d25411e73 100644 --- a/osu.Game.Modes.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Modes.Catch/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Modes.Catch")] @@ -16,8 +16,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -27,11 +27,11 @@ using System.Runtime.InteropServices; // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] diff --git a/osu.Game.Modes.Catch/CatchScoreProcessor.cs b/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs similarity index 77% rename from osu.Game.Modes.Catch/CatchScoreProcessor.cs rename to osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs index 6563949fbc..1b9bedf7fb 100644 --- a/osu.Game.Modes.Catch/CatchScoreProcessor.cs +++ b/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs @@ -3,9 +3,10 @@ using osu.Game.Modes.Catch.Judgements; using osu.Game.Modes.Catch.Objects; +using osu.Game.Modes.Scoring; using osu.Game.Modes.UI; -namespace osu.Game.Modes.Catch +namespace osu.Game.Modes.Catch.Scoring { internal class CatchScoreProcessor : ScoreProcessor { @@ -18,7 +19,7 @@ namespace osu.Game.Modes.Catch { } - protected override void UpdateCalculations(CatchJudgement newJudgement) + protected override void OnNewJudgement(CatchJudgement judgement) { } } diff --git a/osu.Game.Modes.Catch/UI/CatchHitRenderer.cs b/osu.Game.Modes.Catch/UI/CatchHitRenderer.cs index 751a8291d4..90bd61a39f 100644 --- a/osu.Game.Modes.Catch/UI/CatchHitRenderer.cs +++ b/osu.Game.Modes.Catch/UI/CatchHitRenderer.cs @@ -5,7 +5,9 @@ using osu.Game.Beatmaps; using osu.Game.Modes.Catch.Beatmaps; using osu.Game.Modes.Catch.Judgements; using osu.Game.Modes.Catch.Objects; +using osu.Game.Modes.Catch.Scoring; using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Scoring; using osu.Game.Modes.UI; namespace osu.Game.Modes.Catch.UI diff --git a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj index 717e9175e4..593d8db4f6 100644 --- a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj +++ b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj @@ -50,7 +50,7 @@ - + diff --git a/osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs index 07c5fcbfef..3ef5b0f29b 100644 --- a/osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs @@ -7,8 +7,8 @@ namespace osu.Game.Modes.Mania.Judgements { public class ManiaJudgement : Judgement { - public override string ScoreString => string.Empty; + public override string ResultString => string.Empty; - public override string MaxScoreString => string.Empty; + public override string MaxResultString => string.Empty; } } diff --git a/osu.Game.Modes.Mania/ManiaRuleset.cs b/osu.Game.Modes.Mania/ManiaRuleset.cs index 27b3fcdf60..bd995d87d6 100644 --- a/osu.Game.Modes.Mania/ManiaRuleset.cs +++ b/osu.Game.Modes.Mania/ManiaRuleset.cs @@ -9,6 +9,8 @@ using osu.Game.Modes.Mods; using osu.Game.Modes.UI; using osu.Game.Screens.Play; using System.Collections.Generic; +using osu.Game.Modes.Mania.Scoring; +using osu.Game.Modes.Scoring; namespace osu.Game.Modes.Mania { diff --git a/osu.Game.Modes.Mania/Properties/AssemblyInfo.cs b/osu.Game.Modes.Mania/Properties/AssemblyInfo.cs index 6cfa3c42b3..11c8290f1b 100644 --- a/osu.Game.Modes.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Modes.Mania/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Modes.Mania")] @@ -16,8 +16,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -27,11 +27,11 @@ using System.Runtime.InteropServices; // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] diff --git a/osu.Game.Modes.Mania/ManiaScoreProcessor.cs b/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs similarity index 77% rename from osu.Game.Modes.Mania/ManiaScoreProcessor.cs rename to osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs index c694717edb..0f87030e25 100644 --- a/osu.Game.Modes.Mania/ManiaScoreProcessor.cs +++ b/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs @@ -3,9 +3,10 @@ using osu.Game.Modes.Mania.Judgements; using osu.Game.Modes.Mania.Objects; +using osu.Game.Modes.Scoring; using osu.Game.Modes.UI; -namespace osu.Game.Modes.Mania +namespace osu.Game.Modes.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { @@ -18,7 +19,7 @@ namespace osu.Game.Modes.Mania { } - protected override void UpdateCalculations(ManiaJudgement newJudgement) + protected override void OnNewJudgement(ManiaJudgement judgement) { } } diff --git a/osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs index 2a6629e25b..0415bc961a 100644 --- a/osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs @@ -5,7 +5,9 @@ using osu.Game.Beatmaps; using osu.Game.Modes.Mania.Beatmaps; using osu.Game.Modes.Mania.Judgements; using osu.Game.Modes.Mania.Objects; +using osu.Game.Modes.Mania.Scoring; using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Scoring; using osu.Game.Modes.UI; namespace osu.Game.Modes.Mania.UI diff --git a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj index d9af517eee..cc925d417a 100644 --- a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj +++ b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj @@ -51,7 +51,7 @@ - + diff --git a/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs index fec675be54..bae12a98e3 100644 --- a/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -43,7 +43,7 @@ namespace osu.Game.Modes.Osu.Beatmaps return new Slider { StartTime = original.StartTime, - Sample = original.Sample, + Samples = original.Samples, CurveObject = curveData, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false @@ -55,7 +55,7 @@ namespace osu.Game.Modes.Osu.Beatmaps return new Spinner { StartTime = original.StartTime, - Sample = original.Sample, + Samples = original.Samples, Position = new Vector2(512, 384) / 2, EndTime = endTimeData.EndTime }; @@ -64,7 +64,7 @@ namespace osu.Game.Modes.Osu.Beatmaps return new HitCircle { StartTime = original.StartTime, - Sample = original.Sample, + Samples = original.Samples, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false }; diff --git a/osu.Game.Modes.Osu/Judgements/OsuJudgement.cs b/osu.Game.Modes.Osu/Judgements/OsuJudgement.cs index bbb1754ce6..e65d3dde3a 100644 --- a/osu.Game.Modes.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Modes.Osu/Judgements/OsuJudgement.cs @@ -25,9 +25,9 @@ namespace osu.Game.Modes.Osu.Judgements /// public OsuScoreResult MaxScore = OsuScoreResult.Hit300; - public override string ScoreString => Score.GetDescription(); + public override string ResultString => Score.GetDescription(); - public override string MaxScoreString => MaxScore.GetDescription(); + public override string MaxResultString => MaxScore.GetDescription(); public int ScoreValue => scoreToInt(Score); diff --git a/osu.Game.Modes.Osu/Mods/OsuMod.cs b/osu.Game.Modes.Osu/Mods/OsuMod.cs index e80975aed1..db2ee26b7a 100644 --- a/osu.Game.Modes.Osu/Mods/OsuMod.cs +++ b/osu.Game.Modes.Osu/Mods/OsuMod.cs @@ -7,6 +7,7 @@ using osu.Game.Modes.Mods; using osu.Game.Modes.Osu.Objects; using System; using System.Linq; +using osu.Game.Modes.Scoring; namespace osu.Game.Modes.Osu.Mods { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs index 935f3e01fd..7815e3ba41 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; namespace osu.Game.Modes.Osu.Objects.Drawables.Connections { @@ -33,7 +32,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Connections Colour = Color4.White.Opacity(0.2f), Radius = 4, }; - + Children = new Drawable[] { new Box diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs index 3ed3124e14..68c5ec0a45 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Graphics.Transforms; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Objects.Drawables.Pieces; using OpenTK; @@ -39,7 +38,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Colour = AccentColour, Hit = () => { - if (Judgement.Result.HasValue) return false; + if (Judgement.Result != HitResult.None) return false; Judgement.PositionOffset = Vector2.Zero; //todo: set to correct value UpdateJudgement(true); diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 13937e3c39..647c8faef8 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics.Transforms; +using osu.Framework.Graphics; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Judgements; using OpenTK; diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs index e8f2154d7f..be326751ba 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs @@ -67,7 +67,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables ComboIndex = s.ComboIndex, Scale = s.Scale, ComboColour = s.ComboColour, - Sample = s.Sample, + Samples = s.Samples, }), }; @@ -111,7 +111,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables if (repeat > currentRepeat) { if (repeat < slider.RepeatCount && ball.Tracking) - PlaySample(); + PlaySamples(); currentRepeat = repeat; } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs index 1c9f1e617c..188306c857 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -2,13 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; -using osu.Game.Beatmaps.Samples; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Judgements; using OpenTK; @@ -54,22 +49,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables }; } - private SampleChannel sample; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - string sampleSet = (HitObject.Sample?.Set ?? SampleSet.Normal).ToString().ToLower(); - - sample = audio.Sample.Get($@"Gameplay/{sampleSet}-slidertick"); - } - - protected override void PlaySample() - { - sample?.Play(); - } - - protected override void CheckJudgement(bool userTriggered) { if (Judgement.TimeOffset >= 0) @@ -78,7 +57,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Judgement.Score = Tracking ? OsuScoreResult.SliderTick : OsuScoreResult.Miss; } } - + protected override void UpdatePreemptState() { var animIn = Math.Min(150, sliderTick.StartTime - FadeInTime); diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs index 81bf9f0bf1..d0136f717c 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; using osu.Framework.MathUtils; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Objects.Drawables.Pieces; diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs index 73a01dfce2..becbebf0c7 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -4,7 +4,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using OpenTK.Graphics; diff --git a/osu.Game.Modes.Osu/Objects/Slider.cs b/osu.Game.Modes.Osu/Objects/Slider.cs index 213a4a7bee..a01c517cb2 100644 --- a/osu.Game.Modes.Osu/Objects/Slider.cs +++ b/osu.Game.Modes.Osu/Objects/Slider.cs @@ -2,18 +2,24 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Beatmaps.Samples; using osu.Game.Beatmaps.Timing; using osu.Game.Modes.Objects.Types; using System; using System.Collections.Generic; using osu.Game.Modes.Objects; using osu.Game.Database; +using System.Linq; +using osu.Game.Audio; namespace osu.Game.Modes.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + public IHasCurve CurveObject { get; set; } public SliderCurve Curve => CurveObject.Curve; @@ -51,13 +57,10 @@ namespace osu.Game.Modes.Osu.Objects { base.ApplyDefaults(timing, difficulty); - ControlPoint overridePoint; - ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint); - var velocityAdjustment = overridePoint?.VelocityAdjustment ?? 1; - var baseVelocity = 100 * difficulty.SliderMultiplier / velocityAdjustment; + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / timing.SpeedMultiplierAt(StartTime); - Velocity = baseVelocity / timingPoint.BeatLength; - TickDistance = baseVelocity / difficulty.SliderTickRate; + Velocity = scoringDistance / timing.BeatLengthAt(StartTime); + TickDistance = scoringDistance / difficulty.SliderTickRate; } public IEnumerable Ticks @@ -93,11 +96,12 @@ namespace osu.Game.Modes.Osu.Objects StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, - Sample = new HitSampleInfo + Samples = Samples.Select(s => new SampleInfo { - Type = SampleType.None, - Set = SampleSet.Soft, - }, + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + }).ToList() }; } } diff --git a/osu.Game.Modes.Osu/OsuAutoReplay.cs b/osu.Game.Modes.Osu/OsuAutoReplay.cs index 61b7466d86..ae85bd72d8 100644 --- a/osu.Game.Modes.Osu/OsuAutoReplay.cs +++ b/osu.Game.Modes.Osu/OsuAutoReplay.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Framework.Graphics.Transforms; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Modes.Osu.Objects; @@ -10,11 +9,13 @@ using osu.Game.Modes.Osu.Objects.Drawables; using System; using System.Collections.Generic; using System.Diagnostics; +using osu.Framework.Graphics; using osu.Game.Modes.Objects.Types; +using osu.Game.Modes.Replays; namespace osu.Game.Modes.Osu { - public class OsuAutoReplay : LegacyReplay + public class OsuAutoReplay : Replay { private static readonly Vector2 spinner_centre = new Vector2(256, 192); @@ -29,17 +30,20 @@ namespace osu.Game.Modes.Osu createAutoReplay(); } - internal class LegacyReplayFrameComparer : IComparer + private class ReplayFrameComparer : IComparer { - public int Compare(LegacyReplayFrame f1, LegacyReplayFrame f2) + public int Compare(ReplayFrame f1, ReplayFrame f2) { + if (f1 == null) throw new NullReferenceException($@"{nameof(f1)} cannot be null"); + if (f2 == null) throw new NullReferenceException($@"{nameof(f2)} cannot be null"); + return f1.Time.CompareTo(f2.Time); } } - private static readonly IComparer replay_frame_comparer = new LegacyReplayFrameComparer(); + private static readonly IComparer replay_frame_comparer = new ReplayFrameComparer(); - private int findInsertionIndex(LegacyReplayFrame frame) + private int findInsertionIndex(ReplayFrame frame) { int index = Frames.BinarySearch(frame, replay_frame_comparer); @@ -59,7 +63,7 @@ namespace osu.Game.Modes.Osu return index; } - private void addFrameToReplay(LegacyReplayFrame frame) => Frames.Insert(findInsertionIndex(frame), frame); + private void addFrameToReplay(ReplayFrame frame) => Frames.Insert(findInsertionIndex(frame), frame); private static Vector2 circlePosition(double t, double radius) => new Vector2((float)(Math.Cos(t) * radius), (float)(Math.Sin(t) * radius)); @@ -74,9 +78,9 @@ namespace osu.Game.Modes.Osu EasingTypes preferredEasing = DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out; - addFrameToReplay(new LegacyReplayFrame(-100000, 256, 500, LegacyButtonState.None)); - addFrameToReplay(new LegacyReplayFrame(beatmap.HitObjects[0].StartTime - 1500, 256, 500, LegacyButtonState.None)); - addFrameToReplay(new LegacyReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 256, 192, LegacyButtonState.None)); + addFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None)); + addFrameToReplay(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None)); + addFrameToReplay(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None)); // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. float frameDelay = (float)applyModsToRate(1000.0 / 60.0); @@ -106,18 +110,18 @@ namespace osu.Game.Modes.Osu //Make the cursor stay at a hitObject as long as possible (mainly for autopilot). if (h.StartTime - h.HitWindowFor(OsuScoreResult.Miss) > endTime + h.HitWindowFor(OsuScoreResult.Hit50) + 50) { - if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None)); - if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Miss), h.Position.X, h.Position.Y, LegacyButtonState.None)); + if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, ReplayButtonState.None)); + if (!(h is Spinner)) addFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Miss), h.Position.X, h.Position.Y, ReplayButtonState.None)); } else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50) > endTime + h.HitWindowFor(OsuScoreResult.Hit50) + 50) { - if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None)); - if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50), h.Position.X, h.Position.Y, LegacyButtonState.None)); + if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, ReplayButtonState.None)); + if (!(h is Spinner)) addFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50), h.Position.X, h.Position.Y, ReplayButtonState.None)); } else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100) > endTime + h.HitWindowFor(OsuScoreResult.Hit100) + 50) { - if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit100), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None)); - if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100), h.Position.X, h.Position.Y, LegacyButtonState.None)); + if (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new ReplayFrame(endTime + h.HitWindowFor(OsuScoreResult.Hit100), last.EndPosition.X, last.EndPosition.Y, ReplayButtonState.None)); + if (!(h is Spinner)) addFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100), h.Position.X, h.Position.Y, ReplayButtonState.None)); } } @@ -173,13 +177,13 @@ namespace osu.Game.Modes.Osu // Do some nice easing for cursor movements if (Frames.Count > 0) { - LegacyReplayFrame lastFrame = Frames[Frames.Count - 1]; + ReplayFrame lastFrame = Frames[Frames.Count - 1]; // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, DrawableOsuHitObject.TIME_PREEMPT - reactionTime); if (waitTime > lastFrame.Time) { - lastFrame = new LegacyReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState); + lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState); addFrameToReplay(lastFrame); } @@ -196,7 +200,7 @@ namespace osu.Game.Modes.Osu for (double time = lastFrame.Time + frameDelay; time < h.StartTime; time += frameDelay) { Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPosition, lastFrame.Time, h.StartTime, easing); - addFrameToReplay(new LegacyReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState)); + addFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState)); } buttonIndex = 0; @@ -207,12 +211,12 @@ namespace osu.Game.Modes.Osu } } - LegacyButtonState button = buttonIndex % 2 == 0 ? LegacyButtonState.Left1 : LegacyButtonState.Right1; + ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1; double hEndTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; - LegacyReplayFrame newFrame = new LegacyReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button); - LegacyReplayFrame endFrame = new LegacyReplayFrame(hEndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, LegacyButtonState.None); + ReplayFrame newFrame = new ReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button); + ReplayFrame endFrame = new ReplayFrame(hEndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, ReplayButtonState.None); // Decrement because we want the previous frame, not the next one int index = findInsertionIndex(newFrame) - 1; @@ -220,19 +224,19 @@ namespace osu.Game.Modes.Osu // Do we have a previous frame? No need to check for < replay.Count since we decremented! if (index >= 0) { - LegacyReplayFrame previousFrame = Frames[index]; + ReplayFrame previousFrame = Frames[index]; var previousButton = previousFrame.ButtonState; // If a button is already held, then we simply alternate - if (previousButton != LegacyButtonState.None) + if (previousButton != ReplayButtonState.None) { - Debug.Assert(previousButton != (LegacyButtonState.Left1 | LegacyButtonState.Right1)); + Debug.Assert(previousButton != (ReplayButtonState.Left1 | ReplayButtonState.Right1)); // Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button. if (previousButton == button) { - button = (LegacyButtonState.Left1 | LegacyButtonState.Right1) & ~button; - newFrame.SetButtonStates(button); + button = (ReplayButtonState.Left1 | ReplayButtonState.Right1) & ~button; + newFrame.ButtonState = button; } // We always follow the most recent slider / spinner, so remove any other frames that occur while it exists. @@ -246,7 +250,7 @@ namespace osu.Game.Modes.Osu { // Don't affect frames which stop pressing a button! if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton) - Frames[j].SetButtonStates(button); + Frames[j].ButtonState = button; } } } @@ -270,13 +274,13 @@ namespace osu.Game.Modes.Osu t = applyModsToTime(j - h.StartTime) * spinnerDirection; Vector2 pos = spinner_centre + circlePosition(t / 20 + angle, spin_radius); - addFrameToReplay(new LegacyReplayFrame((int)j, pos.X, pos.Y, button)); + addFrameToReplay(new ReplayFrame((int)j, pos.X, pos.Y, button)); } t = applyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; Vector2 endPosition = spinner_centre + circlePosition(t / 20 + angle, spin_radius); - addFrameToReplay(new LegacyReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button)); + addFrameToReplay(new ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button)); endFrame.MouseX = endPosition.X; endFrame.MouseY = endPosition.Y; @@ -288,10 +292,10 @@ namespace osu.Game.Modes.Osu for (double j = frameDelay; j < s.Duration; j += frameDelay) { Vector2 pos = s.PositionAt(j / s.Duration); - addFrameToReplay(new LegacyReplayFrame(h.StartTime + j, pos.X, pos.Y, button)); + addFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button)); } - addFrameToReplay(new LegacyReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button)); + addFrameToReplay(new ReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button)); } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! diff --git a/osu.Game.Modes.Osu/OsuRuleset.cs b/osu.Game.Modes.Osu/OsuRuleset.cs index bbaf7eed5f..12df7d3f3c 100644 --- a/osu.Game.Modes.Osu/OsuRuleset.cs +++ b/osu.Game.Modes.Osu/OsuRuleset.cs @@ -12,6 +12,8 @@ using osu.Game.Modes.UI; using osu.Game.Screens.Play; using System.Collections.Generic; using System.Linq; +using osu.Game.Modes.Osu.Scoring; +using osu.Game.Modes.Scoring; namespace osu.Game.Modes.Osu { diff --git a/osu.Game.Modes.Osu/Properties/AssemblyInfo.cs b/osu.Game.Modes.Osu/Properties/AssemblyInfo.cs index 61e6ae6f7a..791c9b594d 100644 --- a/osu.Game.Modes.Osu/Properties/AssemblyInfo.cs +++ b/osu.Game.Modes.Osu/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Mode.Osu")] @@ -16,8 +16,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -27,11 +27,11 @@ using System.Runtime.InteropServices; // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] diff --git a/osu.Game.Modes.Osu/OsuScore.cs b/osu.Game.Modes.Osu/Scoring/OsuScore.cs similarity index 72% rename from osu.Game.Modes.Osu/OsuScore.cs rename to osu.Game.Modes.Osu/Scoring/OsuScore.cs index dddf826887..a0a639a59e 100644 --- a/osu.Game.Modes.Osu/OsuScore.cs +++ b/osu.Game.Modes.Osu/Scoring/OsuScore.cs @@ -1,7 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Osu +using osu.Game.Modes.Scoring; + +namespace osu.Game.Modes.Osu.Scoring { internal class OsuScore : Score { diff --git a/osu.Game.Modes.Osu/OsuScoreProcessor.cs b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs similarity index 88% rename from osu.Game.Modes.Osu/OsuScoreProcessor.cs rename to osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs index f473a578bd..0bd587e8ea 100644 --- a/osu.Game.Modes.Osu/OsuScoreProcessor.cs +++ b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs @@ -4,9 +4,10 @@ using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Judgements; using osu.Game.Modes.Osu.Objects; +using osu.Game.Modes.Scoring; using osu.Game.Modes.UI; -namespace osu.Game.Modes.Osu +namespace osu.Game.Modes.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { @@ -27,7 +28,7 @@ namespace osu.Game.Modes.Osu Accuracy.Value = 1; } - protected override void UpdateCalculations(OsuJudgement judgement) + protected override void OnNewJudgement(OsuJudgement judgement) { if (judgement != null) { diff --git a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs index 18f8fbb8b9..ca9ff6fc61 100644 --- a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs +++ b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs @@ -7,6 +7,8 @@ using osu.Game.Modes.Osu.Beatmaps; using osu.Game.Modes.Osu.Judgements; using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.Objects.Drawables; +using osu.Game.Modes.Osu.Scoring; +using osu.Game.Modes.Scoring; using osu.Game.Modes.UI; using osu.Game.Screens.Play; diff --git a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs index 8090263fe1..d89bbfd131 100644 --- a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs @@ -21,6 +21,8 @@ namespace osu.Game.Modes.Osu.UI private readonly Container judgementLayer; private readonly ConnectionRenderer connectionLayer; + public override bool ProvidingUserCursor => true; + public override Vector2 Size { get diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj index a56bb799c4..55322e855e 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj @@ -72,8 +72,8 @@ - - + + diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 0606ee4d5a..aee06ad796 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -2,75 +2,160 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; using osu.Game.Modes.Objects; using osu.Game.Modes.Objects.Types; using osu.Game.Modes.Taiko.Objects; +using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Database; +using osu.Game.IO.Serialization; +using osu.Game.Audio; namespace osu.Game.Modes.Taiko.Beatmaps { internal class TaikoBeatmapConverter : IBeatmapConverter { - private const float legacy_velocity_scale = 1.4f; - private const float bash_convert_factor = 1.65f; + /// + /// osu! is generally slower than taiko, so a factor is added to increase + /// speed. This must be used everywhere slider length or beat length is used. + /// + private const float legacy_velocity_multiplier = 1.4f; + + /// + /// Because swells are easier in taiko than spinners are in osu!, + /// legacy taiko multiplies a factor when converting the number of required hits. + /// + private const float swell_hit_multiplier = 1.65f; + + /// + /// Base osu! slider scoring distance. + /// + private const float osu_base_scoring_distance = 100; + + /// + /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. + /// + private const float taiko_base_distance = 100; public Beatmap Convert(Beatmap original) { - if (original is LegacyBeatmap) - original.TimingInfo.ControlPoints.ForEach(c => c.VelocityAdjustment /= legacy_velocity_scale); + BeatmapInfo info = original.BeatmapInfo.DeepClone(); + info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; return new Beatmap(original) { - HitObjects = convertHitObjects(original.HitObjects) + BeatmapInfo = info, + HitObjects = original.HitObjects.SelectMany(h => convertHitObject(h, original)).ToList() }; } - private List convertHitObjects(List hitObjects) - { - return hitObjects.Select(convertHitObject).ToList(); - } - - private TaikoHitObject convertHitObject(HitObject original) + private IEnumerable convertHitObject(HitObject obj, Beatmap beatmap) { // Check if this HitObject is already a TaikoHitObject, and return it if so - TaikoHitObject originalTaiko = original as TaikoHitObject; + var originalTaiko = obj as TaikoHitObject; if (originalTaiko != null) - return originalTaiko; + yield return originalTaiko; - IHasDistance distanceData = original as IHasDistance; - IHasRepeats repeatsData = original as IHasRepeats; - IHasEndTime endTimeData = original as IHasEndTime; + var distanceData = obj as IHasDistance; + var repeatsData = obj as IHasRepeats; + var endTimeData = obj as IHasEndTime; + + // Old osu! used hit sounding to determine various hit type information + List samples = obj.Samples; + + bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); if (distanceData != null) { - return new DrumRoll - { - StartTime = original.StartTime, - Sample = original.Sample, + int repeats = repeatsData?.RepeatCount ?? 1; - Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) + double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime); + double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment; + + // The true distance, accounting for any repeats. This ends up being the drum roll distance later + double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; + + // The velocity of the taiko hit object - calculated as the velocity of a drum roll + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + // The duration of the taiko hit object + double taikoDuration = distance / taikoVelocity; + + // For some reason, old osu! always uses speedAdjustment to determine the taiko velocity, but + // only uses it to determine osu! velocity if beatmap version < 8. Let's account for that here. + if (beatmap.BeatmapInfo.BeatmapVersion >= 8) + speedAdjustedBeatLength /= speedAdjustment; + + // The velocity of the osu! hit object - calculated as the velocity of a slider + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + // The duration of the osu! hit object + double osuDuration = distance / osuVelocity; + + // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats) / 8; + + if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) + { + for (double j = obj.StartTime; j <= distanceData.EndTime + tickSpacing; j += tickSpacing) + { + // Todo: This should generate different type of hits (including strongs) + // depending on hitobject sound additions (not implemented fully yet) + yield return new CentreHit + { + StartTime = j, + Samples = obj.Samples, + IsStrong = strong, + }; + } + } + else + { + yield return new DrumRoll + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + Duration = taikoDuration, + TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4, + }; + } + } + else if (endTimeData != null) + { + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + + yield return new Swell + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + Duration = endTimeData.Duration, + RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier), }; } - - if (endTimeData != null) + else { - // We compute the end time manually to add in the Bash convert factor - return new Bash + bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + + if (isRim) { - StartTime = original.StartTime, - Sample = original.Sample, - - EndTime = original.StartTime + endTimeData.Duration * bash_convert_factor - }; + yield return new RimHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + }; + } + else + { + yield return new CentreHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + }; + } } - - return new Hit - { - StartTime = original.StartTime, - Sample = original.Sample, - }; } } } diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 7faf9db02f..6ae476b265 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -8,12 +8,14 @@ namespace osu.Game.Modes.Taiko.Judgements /// /// Drum roll ticks don't display judgement text. /// - public override string ScoreString => string.Empty; + public override string ResultString => string.Empty; /// /// Drum roll ticks don't display judgement text. /// - public override string MaxScoreString => string.Empty; + public override string MaxResultString => string.Empty; + + public override bool AffectsCombo => false; protected override int NumericResultForScore(TaikoHitResult result) { diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs index d715fd0183..7676ef8c29 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs @@ -3,56 +3,57 @@ using osu.Game.Modes.Judgements; using osu.Framework.Extensions; +using osu.Game.Modes.Objects.Drawables; namespace osu.Game.Modes.Taiko.Judgements { public class TaikoJudgement : Judgement { /// - /// The maximum score value. + /// The maximum result. /// public const TaikoHitResult MAX_HIT_RESULT = TaikoHitResult.Great; /// - /// The score value. + /// The result. /// public TaikoHitResult TaikoResult; /// - /// The score value for the combo portion of the score. + /// The result value for the combo portion of the score. /// - public int ScoreValue => NumericResultForScore(TaikoResult); - - /// - /// The score value for the accuracy portion of the score. - /// - public int AccuracyScoreValue => NumericResultForAccuracy(TaikoResult); + public int ResultValueForScore => Result == HitResult.Miss ? 0 : NumericResultForScore(TaikoResult); /// - /// The maximum score value for the combo portion of the score. + /// The result value for the accuracy portion of the score. /// - public int MaxScoreValue => NumericResultForScore(MAX_HIT_RESULT); - - /// - /// The maximum score value for the accuracy portion of the score. - /// - public int MaxAccuracyScoreValue => NumericResultForAccuracy(MAX_HIT_RESULT); - - public override string ScoreString => TaikoResult.GetDescription(); - - public override string MaxScoreString => MAX_HIT_RESULT.GetDescription(); + public int ResultValueForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(TaikoResult); /// - /// Whether this Judgement has a secondary hit in the case of finishers. + /// The maximum result value for the combo portion of the score. /// - public bool SecondHit; + public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT); /// - /// Computes the numeric score value for the combo portion of the score. + /// The maximum result value for the accuracy portion of the score. + /// + public int MaxResultValueForAccuracy => NumericResultForAccuracy(MAX_HIT_RESULT); + + public override string ResultString => TaikoResult.GetDescription(); + + public override string MaxResultString => MAX_HIT_RESULT.GetDescription(); + + /// + /// Whether this Judgement has a secondary hit in the case of strong hits. + /// + public virtual bool SecondHit { get; set; } + + /// + /// Computes the numeric result value for the combo portion of the score. /// For the accuracy portion of the score (including accuracy percentage), see . /// - /// The result to compute the score value for. - /// The numeric score value. + /// The result to compute the value for. + /// The numeric result value. protected virtual int NumericResultForScore(TaikoHitResult result) { switch (result) @@ -67,11 +68,11 @@ namespace osu.Game.Modes.Taiko.Judgements } /// - /// Computes the numeric score value for the accuracy portion of the score. + /// Computes the numeric result value for the accuracy portion of the score. /// For the combo portion of the score, see . /// - /// The result to compute the score value for. - /// The numeric score value. + /// The result to compute the value for. + /// The numeric result value. protected virtual int NumericResultForAccuracy(TaikoHitResult result) { switch (result) diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs new file mode 100644 index 0000000000..ee978d0026 --- /dev/null +++ b/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Judgements; + +namespace osu.Game.Modes.Taiko.Judgements +{ + public class TaikoStrongHitJudgement : TaikoJudgement, IPartialJudgement + { + public bool Changed { get; set; } + + public override bool SecondHit + { + get { return base.SecondHit; } + set + { + if (base.SecondHit == value) + return; + base.SecondHit = value; + + Changed = true; + } + } + } +} diff --git a/osu.Game.Modes.Taiko/Mods/TaikoMod.cs b/osu.Game.Modes.Taiko/Mods/TaikoMod.cs index c929ebffdd..422f0ec250 100644 --- a/osu.Game.Modes.Taiko/Mods/TaikoMod.cs +++ b/osu.Game.Modes.Taiko/Mods/TaikoMod.cs @@ -1,7 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Beatmaps; using osu.Game.Modes.Mods; +using osu.Game.Modes.Scoring; +using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Taiko.Replays; +using osu.Game.Users; namespace osu.Game.Modes.Taiko.Mods { @@ -61,4 +66,13 @@ namespace osu.Game.Modes.Taiko.Mods { } + + public class TaikoModAutoplay : ModAutoplay + { + protected override Score CreateReplayScore(Beatmap beatmap) => new Score + { + User = new User { Username = "mekkadosu!" }, + Replay = new TaikoAutoReplay(beatmap) + }; + } } diff --git a/osu.Game/Beatmaps/Samples/HitSampleInfo.cs b/osu.Game.Modes.Taiko/Objects/BarLine.cs similarity index 55% rename from osu.Game/Beatmaps/Samples/HitSampleInfo.cs rename to osu.Game.Modes.Taiko/Objects/BarLine.cs index c1cf1bd5e5..ae3c03de5e 100644 --- a/osu.Game/Beatmaps/Samples/HitSampleInfo.cs +++ b/osu.Game.Modes.Taiko/Objects/BarLine.cs @@ -1,10 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Beatmaps.Samples +namespace osu.Game.Modes.Taiko.Objects { - public class HitSampleInfo : SampleInfo + public class BarLine : TaikoHitObject { - public SampleType Type { get; set; } } } diff --git a/osu.Game.Modes.Taiko/Objects/Bash.cs b/osu.Game.Modes.Taiko/Objects/Bash.cs deleted file mode 100644 index b8b4eea6a9..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Bash.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Beatmaps.Timing; -using osu.Game.Database; -using osu.Game.Modes.Objects.Types; - -namespace osu.Game.Modes.Taiko.Objects -{ - public class Bash : TaikoHitObject, IHasEndTime - { - public double EndTime { get; set; } - - public double Duration => EndTime - StartTime; - - /// - /// The number of hits required to complete the bash successfully. - /// - public int RequiredHits { get; protected set; } - - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) - { - base.ApplyDefaults(timing, difficulty); - - double spinnerRotationRatio = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); - RequiredHits = (int)Math.Max(1, Duration / 1000f * spinnerRotationRatio); - } - } -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Samples/SampleInfo.cs b/osu.Game.Modes.Taiko/Objects/CentreHit.cs similarity index 55% rename from osu.Game/Beatmaps/Samples/SampleInfo.cs rename to osu.Game.Modes.Taiko/Objects/CentreHit.cs index 5f9572c871..258112f045 100644 --- a/osu.Game/Beatmaps/Samples/SampleInfo.cs +++ b/osu.Game.Modes.Taiko/Objects/CentreHit.cs @@ -1,11 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Beatmaps.Samples +namespace osu.Game.Modes.Taiko.Objects { - public class SampleInfo + public class CentreHit : Hit { - public SampleBank Bank; - public SampleSet Set; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs deleted file mode 100644 index c77c7762e3..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; - -namespace osu.Game.Modes.Taiko.Objects.Drawable -{ - public abstract class DrawableTaikoHitObject : DrawableHitObject - { - protected DrawableTaikoHitObject(TaikoHitObject hitObject) - : base(hitObject) - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - - RelativePositionAxes = Axes.X; - } - - protected override void LoadComplete() - { - LifetimeStart = HitObject.StartTime - HitObject.PreEmpt * 2; - LifetimeEnd = HitObject.StartTime + HitObject.PreEmpt; - - base.LoadComplete(); - } - - protected override TaikoJudgement CreateJudgement() => new TaikoJudgement(); - - /// - /// Sets the scroll position of the DrawableHitObject relative to the offset between - /// a time value and the HitObject's StartTime. - /// - /// - protected virtual void UpdateScrollPosition(double time) - { - MoveToX((float)((HitObject.StartTime - time) / HitObject.PreEmpt)); - } - - protected override void Update() - { - UpdateScrollPosition(Time.Current); - } - } -} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLine.cs new file mode 100644 index 0000000000..59f8aca867 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + /// + /// A line that scrolls alongside hit objects in the playfield and visualises control points. + /// + public class DrawableBarLine : Container + { + /// + /// The width of the line tracker. + /// + private const float tracker_width = 2f; + + /// + /// Fade out time calibrated to a pre-empt of 1000ms. + /// + private const float base_fadeout_time = 100f; + + /// + /// The visual line tracker. + /// + protected Box Tracker; + + /// + /// The bar line. + /// + protected readonly BarLine BarLine; + + public DrawableBarLine(BarLine barLine) + { + BarLine = barLine; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Width = tracker_width; + + Children = new[] + { + Tracker = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + Alpha = 0.75f + } + }; + + LifetimeStart = BarLine.StartTime - BarLine.ScrollTime * 2; + LifetimeEnd = BarLine.StartTime + BarLine.ScrollTime; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Delay(BarLine.StartTime - Time.Current); + FadeOut(base_fadeout_time * BarLine.ScrollTime / 1000); + } + + private void updateScrollPosition(double time) => MoveToX((float)((BarLine.StartTime - time) / BarLine.ScrollTime)); + + protected override void Update() + { + base.Update(); + + updateScrollPosition(Time.Current); + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLineMajor.cs new file mode 100644 index 0000000000..73565e6948 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableBarLineMajor : DrawableBarLine + { + /// + /// The vertical offset of the triangles from the line tracker. + /// + private const float triangle_offfset = 10f; + + /// + /// The size of the triangles. + /// + private const float triangle_size = 20f; + + public DrawableBarLineMajor(BarLine barLine) + : base(barLine) + { + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new EquilateralTriangle + { + Name = "Top", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, -triangle_offfset), + Size = new Vector2(-triangle_size), + EdgeSmoothness = new Vector2(1), + }, + new EquilateralTriangle + { + Name = "Bottom", + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, triangle_offfset), + Size = new Vector2(triangle_size), + EdgeSmoothness = new Vector2(1), + } + } + }); + + Tracker.Alpha = 1f; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs new file mode 100644 index 0000000000..ff5ac859b4 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableCentreHit : DrawableHit + { + protected override Key[] HitKeys { get; } = { Key.F, Key.J }; + + public DrawableCentreHit(Hit hit) + : base(hit) + { + MainPiece.Add(new CentreHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.PinkDarker; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs new file mode 100644 index 0000000000..bc24e2aa65 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableCentreHitStrong : DrawableHitStrong + { + protected override Key[] HitKeys { get; } = { Key.F, Key.J }; + + public DrawableCentreHitStrong(Hit hit) + : base(hit) + { + MainPiece.Add(new CentreHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.PinkDarker; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRoll.cs new file mode 100644 index 0000000000..0a0098dd34 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableDrumRoll : DrawableTaikoHitObject + { + /// + /// Number of rolling hits required to reach the dark/final accent colour. + /// + private const int rolling_hits_for_dark_accent = 5; + + private Color4 accentDarkColour; + + /// + /// Rolling number of tick hits. This increases for hits and decreases for misses. + /// + private int rollingHits; + + public DrawableDrumRoll(DrumRoll drumRoll) + : base(drumRoll) + { + foreach (var tick in drumRoll.Ticks) + { + var newTick = new DrawableDrumRollTick(tick) + { + X = (float)((tick.StartTime - HitObject.StartTime) / HitObject.Duration) + }; + + newTick.OnJudgement += onTickJudgement; + + AddNested(newTick); + MainPiece.Add(newTick); + } + } + + protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = HitObject.IsStrong }; + + protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(HitObject.IsStrong) + { + Length = (float)(HitObject.Duration / HitObject.ScrollTime), + PlayfieldLengthReference = () => Parent.DrawSize.X + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = AccentColour = colours.YellowDark; + accentDarkColour = colours.YellowDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // This is naive, however it's based on the reasoning that the hit target + // is further than mid point of the play field, so the time taken to scroll in should always + // be greater than the time taken to scroll out to the left of the screen. + // Thus, using PreEmpt here is enough for the drum roll to completely scroll out. + LifetimeEnd = HitObject.EndTime + HitObject.ScrollTime; + } + + private void onTickJudgement(DrawableHitObject obj) + { + if (obj.Judgement.Result == HitResult.Hit) + rollingHits++; + else + rollingHits--; + + rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_dark_accent); + + Color4 newAccent = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_dark_accent, AccentColour, accentDarkColour, 0, 1); + MainPiece.FadeAccent(newAccent, 100); + } + + protected override void CheckJudgement(bool userTriggered) + { + if (userTriggered) + return; + + if (Judgement.TimeOffset < 0) + return; + + int countHit = NestedHitObjects.Count(o => o.Judgement.Result == HitResult.Hit); + + if (countHit > HitObject.RequiredGoodHits) + { + Judgement.Result = HitResult.Hit; + Judgement.TaikoResult = countHit >= HitObject.RequiredGreatHits ? TaikoHitResult.Great : TaikoHitResult.Good; + } + else + Judgement.Result = HitResult.Miss; + } + + protected override void UpdateState(ArmedState state) + { + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs new file mode 100644 index 0000000000..296affedaf --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using OpenTK.Input; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableDrumRollTick : DrawableTaikoHitObject + { + public DrawableDrumRollTick(DrumRollTick tick) + : base(tick) + { + } + + protected override TaikoPiece CreateMainPiece() => new TickPiece + { + Filled = HitObject.FirstTick + }; + + protected override TaikoJudgement CreateJudgement() => new TaikoDrumRollTickJudgement { SecondHit = HitObject.IsStrong }; + + protected override void CheckJudgement(bool userTriggered) + { + if (!userTriggered) + { + if (Judgement.TimeOffset > HitObject.HitWindow) + Judgement.Result = HitResult.Miss; + return; + } + + if (Math.Abs(Judgement.TimeOffset) < HitObject.HitWindow) + { + Judgement.Result = HitResult.Hit; + Judgement.TaikoResult = TaikoHitResult.Great; + } + } + + protected override void UpdateState(ArmedState state) + { + switch (state) + { + case ArmedState.Hit: + Content.ScaleTo(0, 100, EasingTypes.OutQuint); + break; + } + } + + protected override void UpdateScrollPosition(double time) + { + // Ticks don't move + } + + protected override bool HandleKeyPress(Key key) + { + return Judgement.Result == HitResult.None && UpdateJudgement(true); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs new file mode 100644 index 0000000000..f325026be9 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs @@ -0,0 +1,94 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public abstract class DrawableHit : DrawableTaikoHitObject + { + /// + /// A list of keys which can result in hits for this HitObject. + /// + protected abstract Key[] HitKeys { get; } + + /// + /// Whether the last key pressed is a valid hit key. + /// + private bool validKeyPressed; + + protected DrawableHit(Hit hit) + : base(hit) + { + } + + protected override void CheckJudgement(bool userTriggered) + { + if (!userTriggered) + { + if (Judgement.TimeOffset > HitObject.HitWindowGood) + Judgement.Result = HitResult.Miss; + return; + } + + double hitOffset = Math.Abs(Judgement.TimeOffset); + + if (hitOffset > HitObject.HitWindowMiss) + return; + + if (!validKeyPressed) + Judgement.Result = HitResult.Miss; + else if (hitOffset < HitObject.HitWindowGood) + { + Judgement.Result = HitResult.Hit; + Judgement.TaikoResult = hitOffset < HitObject.HitWindowGreat ? TaikoHitResult.Great : TaikoHitResult.Good; + } + else + Judgement.Result = HitResult.Miss; + } + + protected override bool HandleKeyPress(Key key) + { + if (Judgement.Result != HitResult.None) + return false; + + validKeyPressed = HitKeys.Contains(key); + + return UpdateJudgement(true); + } + + protected override void UpdateState(ArmedState state) + { + Delay(HitObject.StartTime - Time.Current + Judgement.TimeOffset, true); + + switch (State) + { + case ArmedState.Idle: + Delay(HitObject.HitWindowMiss); + break; + case ArmedState.Miss: + FadeOut(100); + break; + case ArmedState.Hit: + FadeOut(600); + + const float gravity_time = 300; + const float gravity_travel_height = 200; + + Content.ScaleTo(0.8f, gravity_time * 2, EasingTypes.OutQuad); + + MoveToY(-gravity_travel_height, gravity_time, EasingTypes.Out); + Delay(gravity_time, true); + MoveToY(gravity_travel_height * 2, gravity_time * 2, EasingTypes.In); + break; + } + + Expire(); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs new file mode 100644 index 0000000000..4ab029acb3 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Input; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using OpenTK.Input; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public abstract class DrawableHitStrong : DrawableHit + { + /// + /// The lenience for the second key press. + /// This does not adjust by map difficulty in ScoreV2 yet. + /// + private const double second_hit_window = 30; + + private double firstHitTime; + private bool firstKeyHeld; + private Key firstHitKey; + + protected DrawableHitStrong(Hit hit) + : base(hit) + { + } + + protected override TaikoPiece CreateMainPiece() => new CirclePiece(true); + + protected override TaikoJudgement CreateJudgement() => new TaikoStrongHitJudgement(); + + protected override void CheckJudgement(bool userTriggered) + { + if (Judgement.Result == HitResult.None) + { + base.CheckJudgement(userTriggered); + return; + } + + if (!userTriggered) + return; + + // If we get here, we're assured that the key pressed is the correct secondary key + + if (Math.Abs(firstHitTime - Time.Current) < second_hit_window) + Judgement.SecondHit = true; + } + + protected override bool HandleKeyPress(Key key) + { + // Check if we've handled the first key + if (Judgement.Result == HitResult.None) + { + // First key hasn't been handled yet, attempt to handle it + bool handled = base.HandleKeyPress(key); + + if (handled) + { + firstHitTime = Time.Current; + firstHitKey = key; + } + + return handled; + } + + // If we've already hit the second key, don't handle this object any further + if (Judgement.SecondHit) + return false; + + // Don't handle represses of the first key + if (firstHitKey == key) + return false; + + // Don't handle invalid hit key presses + if (!HitKeys.Contains(key)) + return false; + + // Assume the intention was to hit the strong hit with both keys only if the first key is still being held down + return firstKeyHeld && UpdateJudgement(true); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + firstKeyHeld = state.Keyboard.Keys.Contains(firstHitKey); + + return base.OnKeyDown(state, args); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs new file mode 100644 index 0000000000..5a311d51ef --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableRimHit : DrawableHit + { + protected override Key[] HitKeys { get; } = { Key.D, Key.K }; + + public DrawableRimHit(Hit hit) + : base(hit) + { + MainPiece.Add(new RimHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.BlueDarker; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHitStrong.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHitStrong.cs new file mode 100644 index 0000000000..5789dfb140 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHitStrong.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableRimHitStrong : DrawableHitStrong + { + protected override Key[] HitKeys { get; } = { Key.D, Key.K }; + + public DrawableRimHitStrong(Hit hit) + : base(hit) + { + MainPiece.Add(new RimHitSymbolPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + MainPiece.AccentColour = colours.BlueDarker; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs new file mode 100644 index 0000000000..e1a590a025 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs @@ -0,0 +1,243 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public class DrawableSwell : DrawableTaikoHitObject + { + /// + /// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime. + /// This is only ever invoked once. + /// + public event Action OnStart; + + private const float target_ring_thick_border = 1.4f; + private const float target_ring_thin_border = 1f; + private const float target_ring_scale = 5f; + private const float inner_ring_alpha = 0.65f; + + private readonly Container bodyContainer; + private readonly CircularContainer targetRing; + private readonly CircularContainer expandingRing; + + private readonly CirclePiece circlePiece; + + private readonly Key[] rimKeys = { Key.D, Key.K }; + private readonly Key[] centreKeys = { Key.F, Key.J }; + private Key[] lastKeySet; + + /// + /// The amount of times the user has hit this swell. + /// + private int userHits; + + private bool hasStarted; + private readonly SwellSymbolPiece symbol; + + public DrawableSwell(Swell swell) + : base(swell) + { + Children = new Drawable[] + { + bodyContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + expandingRing = new CircularContainer + { + Name = "Expanding ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + BlendingMode = BlendingMode.Additive, + Masking = true, + Children = new [] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = inner_ring_alpha, + } + } + }, + targetRing = new CircularContainer + { + Name = "Target ring (thick border)", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Masking = true, + BorderThickness = target_ring_thick_border, + BlendingMode = BlendingMode.Additive, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new CircularContainer + { + Name = "Target ring (thin border)", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = target_ring_thin_border, + BorderColour = Color4.White, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } + }, + circlePiece = new CirclePiece + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new [] + { + symbol = new SwellSymbolPiece() + } + } + } + } + }; + + circlePiece.KiaiMode = HitObject.Kiai; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + circlePiece.AccentColour = colours.YellowDark; + expandingRing.Colour = colours.YellowLight; + targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); + } + + protected override void CheckJudgement(bool userTriggered) + { + if (userTriggered) + { + userHits++; + + var completion = (float)userHits / HitObject.RequiredHits; + + expandingRing.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50); + expandingRing.Delay(50); + expandingRing.FadeTo(completion / 8, 2000, EasingTypes.OutQuint); + expandingRing.DelayReset(); + + symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, EasingTypes.OutQuint); + + expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, EasingTypes.OutQuint); + + if (userHits == HitObject.RequiredHits) + { + Judgement.Result = HitResult.Hit; + Judgement.TaikoResult = TaikoHitResult.Great; + } + } + else + { + if (Judgement.TimeOffset < 0) + return; + + //TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP + if (userHits > HitObject.RequiredHits / 2) + { + Judgement.Result = HitResult.Hit; + Judgement.TaikoResult = TaikoHitResult.Good; + } + else + Judgement.Result = HitResult.Miss; + } + } + + protected override void UpdateState(ArmedState state) + { + const float preempt = 100; + + Delay(HitObject.StartTime - Time.Current - preempt, true); + + targetRing.ScaleTo(target_ring_scale, preempt * 4, EasingTypes.OutQuint); + + Delay(preempt, true); + + Delay(Judgement.TimeOffset + HitObject.Duration, true); + + const float out_transition_time = 300; + + switch (state) + { + case ArmedState.Hit: + bodyContainer.ScaleTo(1.4f, out_transition_time); + break; + } + + FadeOut(out_transition_time, EasingTypes.Out); + + Expire(); + } + + protected override void UpdateScrollPosition(double time) + { + // Make the swell stop at the hit target + double t = Math.Min(HitObject.StartTime, time); + + if (t == HitObject.StartTime && !hasStarted) + { + OnStart?.Invoke(); + hasStarted = true; + } + + base.UpdateScrollPosition(t); + } + + protected override bool HandleKeyPress(Key key) + { + if (Judgement.Result != HitResult.None) + return false; + + // Don't handle keys before the swell starts + if (Time.Current < HitObject.StartTime) + return false; + + // Find the keyset which this key corresponds to + var keySet = rimKeys.Contains(key) ? rimKeys : centreKeys; + + // Ensure alternating keysets + if (keySet == lastKeySet) + return false; + lastKeySet = keySet; + + UpdateJudgement(true); + + return true; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs new file mode 100644 index 0000000000..f15f2bd152 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Objects.Drawables +{ + public abstract class DrawableTaikoHitObject : DrawableHitObject + where TaikoHitType : TaikoHitObject + { + /// + /// A list of keys which this hit object will accept. These are the standard Taiko keys for now. + /// These should be moved to bindings later. + /// + private readonly List validKeys = new List(new[] { Key.D, Key.F, Key.J, Key.K }); + + public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); + + protected override Container Content => bodyContainer; + + protected readonly TaikoPiece MainPiece; + + private readonly Container bodyContainer; + + public new TaikoHitType HitObject; + + protected DrawableTaikoHitObject(TaikoHitType hitObject) + : base(hitObject) + { + HitObject = hitObject; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.Custom; + + AutoSizeAxes = Axes.Both; + + RelativePositionAxes = Axes.X; + + AddInternal(bodyContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new[] + { + MainPiece = CreateMainPiece() + } + }); + + MainPiece.KiaiMode = HitObject.Kiai; + + LifetimeStart = HitObject.StartTime - HitObject.ScrollTime * 2; + } + + protected override TaikoJudgement CreateJudgement() => new TaikoJudgement(); + + protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(HitObject.IsStrong); + + /// + /// Sets the scroll position of the DrawableHitObject relative to the offset between + /// a time value and the HitObject's StartTime. + /// + /// + protected virtual void UpdateScrollPosition(double time) => X = (float)((HitObject.StartTime - time) / HitObject.ScrollTime); + + protected override void Update() + { + UpdateScrollPosition(Time.Current); + } + + protected virtual bool HandleKeyPress(Key key) => false; + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + // Make sure we don't handle held-down keys + if (args.Repeat) + return false; + + // Check if we've pressed a valid taiko key + if (!validKeys.Contains(args.Key)) + return false; + + // Handle it! + return HandleKeyPress(args.Key); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs new file mode 100644 index 0000000000..0cf4e97b41 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + /// + /// The symbol used for centre hit pieces. + /// + public class CentreHitSymbolPiece : CircularContainer + { + public CentreHitSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(CirclePiece.SYMBOL_INNER_SIZE); + Masking = true; + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both + } + }; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs new file mode 100644 index 0000000000..6ea1494ea7 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -0,0 +1,153 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Backgrounds; +using OpenTK.Graphics; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + /// + /// A circle piece which is used uniformly through osu!taiko to visualise hitobjects. + /// + /// Note that this can actually be non-circle if the width is changed. See + /// for a usage example. + /// + /// + public class CirclePiece : TaikoPiece + { + public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; + public const float SYMBOL_BORDER = 8; + public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER; + + /// + /// The amount to scale up the base circle to show it as a "strong" piece. + /// + private const float strong_scale = 1.5f; + + /// + /// The colour of the inner circle and outer glows. + /// + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + + background.Colour = AccentColour; + + resetEdgeEffects(); + } + } + + /// + /// Whether Kiai mode effects are enabled for this circle piece. + /// + public override bool KiaiMode + { + get { return base.KiaiMode; } + set + { + base.KiaiMode = value; + + resetEdgeEffects(); + } + } + + protected override Container Content => content; + + private readonly Container content; + + private readonly Container background; + + public CirclePiece(bool isStrong = false) + { + AddInternal(new Drawable[] + { + background = new CircularContainer + { + Name = "Background", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + new Triangles + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + ColourLight = Color4.White, + ColourDark = Color4.White.Darken(0.1f) + } + } + }, + new CircularContainer + { + Name = "Ring", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + BorderThickness = 8, + BorderColour = Color4.White, + Masking = true, + Children = new[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Name = "Content", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + + if (isStrong) + { + Size *= strong_scale; + + //default for symbols etc. + Content.Scale *= strong_scale; + } + } + + protected override void Update() + { + base.Update(); + + //we want to allow for width of content to remain mapped to the area inside us, regardless of the scale applied above. + Content.Width = 1 / Content.Scale.X; + } + + private void resetEdgeEffects() + { + background.EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Colour = AccentColour, + Radius = KiaiMode ? 50 : 8 + }; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs new file mode 100644 index 0000000000..5431507614 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics.Primitives; +using osu.Game.Modes.Taiko.UI; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + public class ElongatedCirclePiece : CirclePiece + { + /// + /// As we are being used to define the absolute size of hits, we need to be given a relative reference of our containing . + /// + public Func PlayfieldLengthReference; + + /// + /// The length of this piece as a multiple of the value returned by + /// + public float Length; + + public ElongatedCirclePiece(bool isStrong = false) : base(isStrong) + { + } + + protected override void Update() + { + base.Update(); + + var padding = Content.DrawHeight * Content.Width / 2; + + Content.Padding = new MarginPadding + { + Left = padding, + Right = padding, + }; + + Width = (PlayfieldLengthReference?.Invoke() ?? 0) * Length + DrawHeight; + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs new file mode 100644 index 0000000000..6e19497978 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + /// + /// The symbol used for rim hit pieces. + /// + public class RimHitSymbolPiece : CircularContainer + { + public RimHitSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(CirclePiece.SYMBOL_SIZE); + BorderThickness = CirclePiece.SYMBOL_BORDER; + BorderColour = Color4.White; + Masking = true; + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + }; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs new file mode 100644 index 0000000000..e491793902 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Graphics; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + /// + /// The symbol used for swell pieces. + /// + public class SwellSymbolPiece : TextAwesome + { + public SwellSymbolPiece() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + UseFullGlyphHeight = true; + TextSize = CirclePiece.SYMBOL_INNER_SIZE; + Icon = FontAwesome.fa_asterisk; + Shadow = false; + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs new file mode 100644 index 0000000000..a0c8865c59 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + public class TaikoPiece : Container, IHasAccentColour + { + private Color4 accentColour; + /// + /// The colour of the inner circle and outer glows. + /// + public virtual Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + } + } + + private bool kiaiMode; + /// + /// Whether Kiai mode effects are enabled for this circle piece. + /// + public virtual bool KiaiMode + { + get { return kiaiMode; } + set + { + kiaiMode = value; + } + } + + public TaikoPiece() + { + //just a default + Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs new file mode 100644 index 0000000000..697102eb22 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +{ + public class TickPiece : TaikoPiece + { + /// + /// Any tick that is not the first for a drumroll is not filled, but is instead displayed + /// as a hollow circle. This is what controls the border width of that circle. + /// + private const float tick_border_width = TaikoHitObject.CIRCLE_RADIUS / 2 / 4; + + /// + /// The size of a tick. + /// + private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2; + + private bool filled; + public bool Filled + { + get { return filled; } + set + { + filled = value; + fillBox.Alpha = filled ? 1 : 0; + } + } + + private readonly Box fillBox; + + public TickPiece() + { + Size = new Vector2(tick_size); + + Add(new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = tick_border_width, + BorderColour = Color4.White, + Children = new[] + { + fillBox = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }); + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs index cdb8ef2405..4f26ffd3a1 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs @@ -1,37 +1,31 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps.Samples; using osu.Game.Modes.Objects.Types; using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.Timing; using osu.Game.Database; +using osu.Game.Audio; namespace osu.Game.Modes.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasDistance + public class DrumRoll : TaikoHitObject, IHasEndTime { - public double EndTime => StartTime + Distance / Velocity; + /// + /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. + /// + private const float base_distance = 100; - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; + + public double Duration { get; set; } /// - /// Raw length of the drum roll in positional length units. + /// Numer of ticks per beat length. /// - public double Distance { get; set; } - - /// - /// Velocity of the drum roll in positional length units per millisecond. - /// - public double Velocity { get; protected set; } - - /// - /// The distance between ticks of this drumroll. - /// Half of this value is the hit window of the ticks. - /// - public double TickTimeDistance { get; protected set; } + public int TickRate = 1; /// /// Number of drum roll ticks required for a "Good" hit. @@ -55,18 +49,17 @@ namespace osu.Game.Modes.Taiko.Objects private List ticks; + /// + /// The length (in milliseconds) between ticks of this drumroll. + /// Half of this value is the hit window of the ticks. + /// + private double tickSpacing = 100; + public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) { base.ApplyDefaults(timing, difficulty); - Velocity = timing.SliderVelocityAt(StartTime) * difficulty.SliderMultiplier / 1000; - TickTimeDistance = timing.BeatLengthAt(StartTime); - - //TODO: move this to legacy conversion code to allow for direct division without special case. - if (difficulty.SliderTickRate == 3) - TickTimeDistance /= 3; - else - TickTimeDistance /= 4; + tickSpacing = timing.BeatLengthAt(StartTime) / TickRate; RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); @@ -76,23 +69,25 @@ namespace osu.Game.Modes.Taiko.Objects { var ret = new List(); - if (TickTimeDistance == 0) + if (tickSpacing == 0) return ret; bool first = true; - for (double t = StartTime; t < EndTime + (int)TickTimeDistance; t += TickTimeDistance) + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { ret.Add(new DrumRollTick { FirstTick = first, - PreEmpt = PreEmpt, - TickTimeDistance = TickTimeDistance, + ScrollTime = ScrollTime, + TickSpacing = tickSpacing, StartTime = t, - Sample = new HitSampleInfo + IsStrong = IsStrong, + Samples = Samples.Select(s => new SampleInfo { - Type = SampleType.None, - Set = SampleSet.Soft - } + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + }).ToList() }); first = false; @@ -101,4 +96,4 @@ namespace osu.Game.Modes.Taiko.Objects return ret; } } -} +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs index 66a2d16fe1..32e8851b66 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs @@ -11,9 +11,14 @@ namespace osu.Game.Modes.Taiko.Objects public bool FirstTick; /// - /// The distance between this tick and the next in milliseconds. + /// The length (in milliseconds) between this tick and the next. /// Half of this value is the hit window of the tick. /// - public double TickTimeDistance; + public double TickSpacing; + + /// + /// The time allowed to hit this tick. + /// + public double HitWindow => TickSpacing / 2; } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/Timing/SampleChange.cs b/osu.Game.Modes.Taiko/Objects/RimHit.cs similarity index 51% rename from osu.Game/Beatmaps/Timing/SampleChange.cs rename to osu.Game.Modes.Taiko/Objects/RimHit.cs index 2946fd749b..aae93ec10d 100644 --- a/osu.Game/Beatmaps/Timing/SampleChange.cs +++ b/osu.Game.Modes.Taiko/Objects/RimHit.cs @@ -1,12 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps.Samples; - -namespace osu.Game.Beatmaps.Timing +namespace osu.Game.Modes.Taiko.Objects { - public class SampleChange : ControlPoint + public class RimHit : Hit { - public SampleInfo Sample; } } diff --git a/osu.Game.Modes.Taiko/Objects/Swell.cs b/osu.Game.Modes.Taiko/Objects/Swell.cs new file mode 100644 index 0000000000..97101ea797 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Swell.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Objects.Types; + +namespace osu.Game.Modes.Taiko.Objects +{ + public class Swell : TaikoHitObject, IHasEndTime + { + public double EndTime => StartTime + Duration; + + public double Duration { get; set; } + + /// + /// The number of hits required to complete the swell successfully. + /// + public int RequiredHits = 10; + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs index 4505065489..54ab8c5300 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs @@ -12,12 +12,23 @@ namespace osu.Game.Modes.Taiko.Objects /// /// HitCircle radius. /// - public const float CIRCLE_RADIUS = 64; + public const float CIRCLE_RADIUS = 42f; /// - /// The time to scroll in the HitObject. + /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. /// - public double PreEmpt; + private const double scroll_time = 6000; + + /// + /// Our adjusted taking into consideration local and other speed multipliers. + /// + public double ScrollTime; + + /// + /// Whether this HitObject is a "strong" type. + /// Strong hit objects give more points for hitting the hit object with both keys. + /// + public bool IsStrong; /// /// Whether this HitObject is in Kiai time. @@ -28,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects { base.ApplyDefaults(timing, difficulty); - PreEmpt = 600 / (timing.SliderVelocityAt(StartTime) * difficulty.SliderMultiplier) * 1000; + ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000) / difficulty.SliderMultiplier; ControlPoint overridePoint; Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode; diff --git a/osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs index 61eca30b5f..94ec895707 100644 --- a/osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Modes.Taiko")] @@ -16,8 +16,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] @@ -27,11 +27,11 @@ using System.Runtime.InteropServices; // Version information for an assembly consists of the following four values: // // Major Version -// Minor Version +// Minor Version // Build Number // Revision // -// You can specify all the values or you can default the Build and Revision Numbers +// You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] diff --git a/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs b/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs new file mode 100644 index 0000000000..89d974baf9 --- /dev/null +++ b/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Modes.Objects.Types; +using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Replays; + +namespace osu.Game.Modes.Taiko.Replays +{ + public class TaikoAutoReplay : Replay + { + private readonly Beatmap beatmap; + + public TaikoAutoReplay(Beatmap beatmap) + { + this.beatmap = beatmap; + + createAutoReplay(); + } + + private void createAutoReplay() + { + bool hitButton = true; + + Frames.Add(new ReplayFrame(-100000, 320, 240, ReplayButtonState.None)); + Frames.Add(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 320, 240, ReplayButtonState.None)); + + for (int i = 0; i < beatmap.HitObjects.Count; i++) + { + TaikoHitObject h = beatmap.HitObjects[i]; + + ReplayButtonState button; + + IHasEndTime endTimeData = h as IHasEndTime; + double endTime = endTimeData?.EndTime ?? h.StartTime; + + Swell swell = h as Swell; + DrumRoll drumRoll = h as DrumRoll; + Hit hit = h as Hit; + + if (swell != null) + { + int d = 0; + int count = 0; + int req = swell.RequiredHits; + double hitRate = swell.Duration / req; + for (double j = h.StartTime; j < endTime; j += hitRate) + { + switch (d) + { + default: + button = ReplayButtonState.Left1; + break; + case 1: + button = ReplayButtonState.Right1; + break; + case 2: + button = ReplayButtonState.Left2; + break; + case 3: + button = ReplayButtonState.Right2; + break; + } + + Frames.Add(new ReplayFrame(j, 0, 0, button)); + d = (d + 1) % 4; + if (++count > req) + break; + } + } + else if (drumRoll != null) + { + foreach (var tick in drumRoll.Ticks) + { + Frames.Add(new ReplayFrame(tick.StartTime, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); + hitButton = !hitButton; + } + } + else if (hit != null) + { + if (hit is CentreHit) + { + if (h.IsStrong) + button = ReplayButtonState.Right1 | ReplayButtonState.Right2; + else + button = hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2; + } + else + { + if (h.IsStrong) + button = ReplayButtonState.Left1 | ReplayButtonState.Left2; + else + button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2; + } + + Frames.Add(new ReplayFrame(h.StartTime, 0, 0, button)); + } + else + throw new Exception("Unknown hit object type."); + + Frames.Add(new ReplayFrame(endTime + 1, 0, 0, ReplayButtonState.None)); + + if (i < beatmap.HitObjects.Count - 1) + { + double waitTime = beatmap.HitObjects[i + 1].StartTime - 1000; + if (waitTime > endTime) + Frames.Add(new ReplayFrame(waitTime, 0, 0, ReplayButtonState.None)); + } + + hitButton = !hitButton; + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Modes.Taiko/Replays/TaikoFramedReplayInputHandler.cs new file mode 100644 index 0000000000..44fca4abe7 --- /dev/null +++ b/osu.Game.Modes.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Replays; +using System.Collections.Generic; +using osu.Framework.Input; +using OpenTK.Input; + +namespace osu.Game.Modes.Taiko.Replays +{ + internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler + { + public TaikoFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override List GetPendingStates() + { + var keys = new List(); + + if (CurrentFrame?.MouseRight1 == true) + keys.Add(Key.F); + if (CurrentFrame?.MouseRight2 == true) + keys.Add(Key.J); + if (CurrentFrame?.MouseLeft1 == true) + keys.Add(Key.D); + if (CurrentFrame?.MouseLeft2 == true) + keys.Add(Key.K); + + return new List + { + new InputState { Keyboard = new ReplayKeyboardState(keys) } + }; + } + } +} diff --git a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs new file mode 100644 index 0000000000..987c3181a4 --- /dev/null +++ b/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs @@ -0,0 +1,278 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Scoring; +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.UI; +using OpenTK; + +namespace osu.Game.Modes.Taiko.Scoring +{ + internal class TaikoScoreProcessor : ScoreProcessor + { + /// + /// The maximum score achievable. + /// Does _not_ include bonus score - for bonus score see . + /// + private const int max_score = 1000000; + + /// + /// The amount of the score attributed to combo. + /// + private const double combo_portion_max = max_score * 0.2; + + /// + /// The amount of the score attributed to accuracy. + /// + private const double accuracy_portion_max = max_score * 0.8; + + /// + /// The factor used to determine relevance of combos. + /// + private const double combo_base = 4; + + /// + /// The HP awarded by a hit. + /// + private const double hp_hit_great = 0.03; + + /// + /// The HP awarded for a hit. + /// + private const double hp_hit_good = 0.011; + + /// + /// The minimum HP deducted for a . + /// This occurs when HP Drain = 0. + /// + private const double hp_miss_min = -0.0018; + + /// + /// The median HP deducted for a . + /// This occurs when HP Drain = 5. + /// + private const double hp_miss_mid = -0.0075; + + /// + /// The maximum HP deducted for a . + /// This occurs when HP Drain = 10. + /// + private const double hp_miss_max = -0.12; + + /// + /// The HP awarded for a hit. + /// + /// hits award less HP as they're more spammable, although in hindsight + /// this probably awards too little HP and is kept at this value for now for compatibility. + /// + /// + private const double hp_hit_tick = 0.00000003; + + /// + /// Taiko fails at the end of the map if the player has not half-filled their HP bar. + /// + public override bool HasFailed => totalHits == maxTotalHits && Health.Value <= 0.5; + + /// + /// The cumulative combo portion of the score. + /// + private double comboScore => combo_portion_max * comboPortion / maxComboPortion; + + /// + /// The cumulative accuracy portion of the score. + /// + private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 3.6) * totalHits / maxTotalHits; + + /// + /// The cumulative bonus score. + /// This is added on top of , thus the total score can exceed . + /// + private double bonusScore; + + /// + /// The multiple of the original score added to the combo portion of the score + /// for correctly hitting a strong hit object with both keys. + /// + private double strongHitScale; + + private double hpIncreaseTick; + private double hpIncreaseGreat; + private double hpIncreaseGood; + private double hpIncreaseMiss; + + private double maxComboPortion; + private double comboPortion; + private int maxTotalHits; + private int totalHits; + + public TaikoScoreProcessor() + { + } + + public TaikoScoreProcessor(HitRenderer hitRenderer) + : base(hitRenderer) + { + } + + protected override void ComputeTargets(Beatmap beatmap) + { + double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98)); + + hpIncreaseTick = hp_hit_tick; + hpIncreaseGreat = hpMultiplierNormal * hp_hit_great; + hpIncreaseGood = hpMultiplierNormal * hp_hit_good; + hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); + + var strongHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong); + + // This is a linear function that awards: + // 10 times bonus points for hitting a strong hit object with both keys with 30 strong hit objects in the map + // 3 times bonus points for hitting a strong hit object with both keys with 120 strong hit objects in the map + strongHitScale = -7d / 90d * MathHelper.Clamp(strongHits.Count, 30, 120) + 111d / 9d; + + foreach (var obj in beatmap.HitObjects) + { + if (obj is Hit) + { + AddJudgement(new TaikoJudgement + { + Result = HitResult.Hit, + TaikoResult = TaikoHitResult.Great, + SecondHit = obj.IsStrong + }); + } + else if (obj is DrumRoll) + { + for (int i = 0; i < ((DrumRoll)obj).TotalTicks; i++) + { + AddJudgement(new TaikoDrumRollTickJudgement + { + Result = HitResult.Hit, + TaikoResult = TaikoHitResult.Great, + SecondHit = obj.IsStrong + }); + } + + AddJudgement(new TaikoJudgement + { + Result = HitResult.Hit, + TaikoResult = TaikoHitResult.Great, + SecondHit = obj.IsStrong + }); + } + else if (obj is Swell) + { + AddJudgement(new TaikoJudgement + { + Result = HitResult.Hit, + TaikoResult = TaikoHitResult.Great + }); + } + } + + maxTotalHits = totalHits; + maxComboPortion = comboPortion; + } + + protected override void OnNewJudgement(TaikoJudgement judgement) + { + bool isTick = judgement is TaikoDrumRollTickJudgement; + + // Don't consider ticks as a type of hit that counts towards map completion + if (!isTick) + totalHits++; + + // Apply score changes + addHitScore(judgement); + + // Apply HP changes + switch (judgement.Result) + { + case HitResult.Miss: + // Missing ticks shouldn't drop HP + if (!isTick) + Health.Value += hpIncreaseMiss; + break; + case HitResult.Hit: + switch (judgement.TaikoResult) + { + case TaikoHitResult.Good: + Health.Value += hpIncreaseGood; + break; + case TaikoHitResult.Great: + if (isTick) + Health.Value += hpIncreaseTick; + else + Health.Value += hpIncreaseGreat; + break; + } + break; + } + + calculateScore(); + } + + protected override void OnJudgementChanged(TaikoJudgement judgement) + { + // Apply score changes + addHitScore(judgement); + + calculateScore(); + } + + private void addHitScore(TaikoJudgement judgement) + { + if (judgement.Result != HitResult.Hit) + return; + + double baseValue = judgement.ResultValueForScore; + + // Add increased score for hitting a strong hit object with the second key + if (judgement.SecondHit) + baseValue *= strongHitScale; + + // Add score to portions + if (judgement is TaikoDrumRollTickJudgement) + bonusScore += baseValue; + else + { + // A relevance factor that needs to be applied to make higher combos more relevant + // Value is capped at 400 combo + double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base))); + + comboPortion += baseValue * comboRelevance; + } + } + + private void calculateScore() + { + int scoreForAccuracy = 0; + int maxScoreForAccuracy = 0; + + foreach (var j in Judgements) + { + scoreForAccuracy += j.ResultValueForAccuracy; + maxScoreForAccuracy += j.MaxResultValueForAccuracy; + } + + Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy; + TotalScore.Value = comboScore + accuracyScore + bonusScore; + } + + protected override void Reset() + { + base.Reset(); + + Health.Value = 0; + + bonusScore = 0; + comboPortion = 0; + totalHits = 0; + } + } +} diff --git a/osu.Game.Modes.Taiko/TaikoRuleset.cs b/osu.Game.Modes.Taiko/TaikoRuleset.cs index ff481e9162..1b3c3fc0eb 100644 --- a/osu.Game.Modes.Taiko/TaikoRuleset.cs +++ b/osu.Game.Modes.Taiko/TaikoRuleset.cs @@ -10,6 +10,8 @@ using osu.Game.Modes.Taiko.UI; using osu.Game.Modes.UI; using osu.Game.Screens.Play; using System.Collections.Generic; +using osu.Game.Modes.Scoring; +using osu.Game.Modes.Taiko.Scoring; namespace osu.Game.Modes.Taiko { @@ -63,7 +65,7 @@ namespace osu.Game.Modes.Taiko { Mods = new Mod[] { - new ModAutoplay(), + new TaikoModAutoplay(), new ModCinema(), }, }, diff --git a/osu.Game.Modes.Taiko/TaikoScoreProcessor.cs b/osu.Game.Modes.Taiko/TaikoScoreProcessor.cs deleted file mode 100644 index fc414c3382..0000000000 --- a/osu.Game.Modes.Taiko/TaikoScoreProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.UI; - -namespace osu.Game.Modes.Taiko -{ - internal class TaikoScoreProcessor : ScoreProcessor - { - public TaikoScoreProcessor() - { - } - - public TaikoScoreProcessor(HitRenderer hitRenderer) - : base(hitRenderer) - { - } - - protected override void UpdateCalculations(TaikoJudgement newJudgement) - { - } - } -} diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Modes.Taiko/UI/HitExplosion.cs index adc6a77c5d..eb43c1a5d0 100644 --- a/osu.Game.Modes.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Modes.Taiko/UI/HitExplosion.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; using osu.Game.Graphics; using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; @@ -19,12 +18,21 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitExplosion : CircularContainer { - private readonly TaikoJudgement judgement; + /// + /// The size multiplier of a hit explosion if a hit object has been hit with the second key. + /// + private const float secondhit_size_multiplier = 1.5f; + + /// + /// The judgement this hit explosion visualises. + /// + public readonly TaikoJudgement Judgement; + private readonly Box innerFill; public HitExplosion(TaikoJudgement judgement) { - this.judgement = judgement; + Judgement = judgement; Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); @@ -51,10 +59,7 @@ namespace osu.Game.Modes.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (judgement.SecondHit) - Size *= 1.5f; - - switch (judgement.TaikoResult) + switch (Judgement.TaikoResult) { case TaikoHitResult.Good: innerFill.Colour = colours.Green; @@ -74,5 +79,13 @@ namespace osu.Game.Modes.Taiko.UI Expire(); } + + /// + /// Transforms this hit explosion to visualise a secondary hit. + /// + public void VisualiseSecondHit() + { + ResizeTo(Size * secondhit_size_multiplier, 50); + } } } diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Modes.Taiko/UI/HitTarget.cs index d38af3390e..a17480628d 100644 --- a/osu.Game.Modes.Taiko/UI/HitTarget.cs +++ b/osu.Game.Modes.Taiko/UI/HitTarget.cs @@ -18,12 +18,12 @@ namespace osu.Game.Modes.Taiko.UI /// /// Diameter of normal hit object circles. /// - private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2 * TaikoPlayfield.PLAYFIELD_SCALE; - + private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2; + /// - /// Diameter of finisher hit object circles. + /// Diameter of strong hit object circles. /// - private const float finisher_diameter = normal_diameter * 1.5f; + private const float strong_hit_diameter = normal_diameter * 1.5f; /// /// The 1px inner border of the taiko playfield. @@ -47,15 +47,15 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PlayfieldHeight - finisher_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), Alpha = 0.1f }, new CircularContainer { - Name = "Finisher Ring", + Name = "Strong Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(finisher_diameter), + Size = new Vector2(strong_hit_diameter), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -72,7 +72,7 @@ namespace osu.Game.Modes.Taiko.UI }, new CircularContainer { - Name = "Normal Ring", + Name = "Normal Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(normal_diameter), @@ -96,7 +96,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Y = -border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PlayfieldHeight - finisher_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), Alpha = 0.1f }, }; diff --git a/osu.Game.Modes.Taiko/UI/InputDrum.cs b/osu.Game.Modes.Taiko/UI/InputDrum.cs index 1787670c7a..0c1e1105cb 100644 --- a/osu.Game.Modes.Taiko/UI/InputDrum.cs +++ b/osu.Game.Modes.Taiko/UI/InputDrum.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Game.Graphics; @@ -22,7 +21,7 @@ namespace osu.Game.Modes.Taiko.UI { public InputDrum() { - Size = new Vector2(TaikoPlayfield.PlayfieldHeight); + Size = new Vector2(TaikoPlayfield.PLAYFIELD_HEIGHT); const float middle_split = 10; @@ -61,7 +60,7 @@ namespace osu.Game.Modes.Taiko.UI /// The key to be used for the rim of the half-drum. /// public Key RimKey; - + /// /// The key to be used for the centre of the half-drum. /// @@ -129,17 +128,36 @@ namespace osu.Game.Modes.Taiko.UI return false; Drawable target = null; + Drawable back = null; if (args.Key == CentreKey) + { target = centreHit; + back = centre; + } else if (args.Key == RimKey) + { target = rimHit; + back = rim; + } if (target != null) { - target.FadeTo(Math.Min(target.Alpha + 0.4f, 1), 40, EasingTypes.OutQuint); - target.Delay(40); - target.FadeOut(600, EasingTypes.OutQuint); + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, EasingTypes.OutQuint); + back.Delay(down_time); + back.ScaleTo(1, up_time, EasingTypes.OutQuint); + + target.ScaleTo(target.Scale.X - scale_amount, down_time, EasingTypes.OutQuint); + target.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, EasingTypes.OutQuint); + target.Delay(down_time); + target.ScaleTo(1, up_time, EasingTypes.OutQuint); + target.FadeOut(up_time, EasingTypes.OutQuint); } return false; diff --git a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs index 21ee3434d3..29fa693d58 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs @@ -1,12 +1,22 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; +using osu.Framework.MathUtils; +using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Objects.Types; +using osu.Game.Modes.Replays; +using osu.Game.Modes.Scoring; using osu.Game.Modes.Taiko.Beatmaps; using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; +using osu.Game.Modes.Taiko.Objects.Drawables; +using osu.Game.Modes.Taiko.Scoring; using osu.Game.Modes.UI; +using osu.Game.Modes.Taiko.Replays; namespace osu.Game.Modes.Taiko.UI { @@ -17,14 +27,122 @@ namespace osu.Game.Modes.Taiko.UI { } + [BackgroundDependencyLoader] + private void load() + { + loadBarLines(); + } + + private void loadBarLines() + { + var taikoPlayfield = Playfield as TaikoPlayfield; + + if (taikoPlayfield == null) + return; + + TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; + double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; + + var timingPoints = Beatmap.TimingInfo.ControlPoints.FindAll(cp => cp.TimingChange); + + if (timingPoints.Count == 0) + return; + + int currentIndex = 0; + + while (currentIndex < timingPoints.Count && Precision.AlmostEquals(timingPoints[currentIndex].BeatLength, 0)) + currentIndex++; + + double time = timingPoints[currentIndex].Time; + double measureLength = timingPoints[currentIndex].BeatLength * (int)timingPoints[currentIndex].TimeSignature; + + // Find the bar line time closest to 0 + time -= measureLength * (int)(time / measureLength); + + // Always start barlines from a positive time + while (time < 0) + time += measureLength; + + int currentBeat = 0; + while (time <= lastHitTime) + { + ControlPoint current = timingPoints[currentIndex]; + + if (time > current.Time || current.OmitFirstBarLine) + { + bool isMajor = currentBeat % (int)current.TimeSignature == 0; + + var barLine = new BarLine + { + StartTime = time, + }; + + barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty); + + taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); + + currentBeat++; + } + + double bl = current.BeatLength; + + if (bl < 800) + bl *= (int)current.TimeSignature; + + time += bl; + + if (currentIndex + 1 >= timingPoints.Count || time < timingPoints[currentIndex + 1].Time) + continue; + + currentBeat = 0; + currentIndex++; + time = timingPoints[currentIndex].Time; + } + } + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); protected override IBeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override IBeatmapProcessor CreateBeatmapProcessor() => new TaikoBeatmapProcessor(); - protected override Playfield CreatePlayfield() => new TaikoPlayfield(); + protected override Playfield CreatePlayfield() => new TaikoPlayfield + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }; - protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) => null; + protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + { + var centreHit = h as CentreHit; + if (centreHit != null) + { + if (h.IsStrong) + return new DrawableCentreHitStrong(centreHit); + return new DrawableCentreHit(centreHit); + } + + var rimHit = h as RimHit; + if (rimHit != null) + { + if (h.IsStrong) + return new DrawableRimHitStrong(rimHit); + return new DrawableRimHit(rimHit); + } + + var drumRoll = h as DrumRoll; + if (drumRoll != null) + { + return new DrawableDrumRoll(drumRoll); + } + + var swell = h as Swell; + if (swell != null) + return new DrawableSwell(swell); + + return null; + } + + protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); } } diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs index b7fac507d6..9e7eb571a1 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -14,30 +14,23 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Primitives; +using System.Linq; +using osu.Game.Modes.Taiko.Objects.Drawables; namespace osu.Game.Modes.Taiko.UI { public class TaikoPlayfield : Playfield { /// - /// The default play field height. + /// The play field height. This is relative to the size of hit objects + /// such that the playfield is just a bit larger than strong hits. /// - public const float PLAYFIELD_BASE_HEIGHT = 242; - - /// - /// The play field height scale. - /// - public const float PLAYFIELD_SCALE = 0.65f; - - /// - /// The play field height after scaling. - /// - public static float PlayfieldHeight => PLAYFIELD_BASE_HEIGHT * PLAYFIELD_SCALE; + public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; /// /// The offset from which the center of the hit target lies at. /// - private const float hit_target_offset = 80; + private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40; /// /// The size of the left area of the playfield. This area contains the input drum. @@ -47,11 +40,11 @@ namespace osu.Game.Modes.Taiko.UI protected override Container Content => hitObjectContainer; private readonly Container hitExplosionContainer; - //private Container barLineContainer; + private readonly Container barLineContainer; private readonly Container judgementContainer; private readonly Container hitObjectContainer; - //private Container topLevelHitContainer; + private readonly Container topLevelHitContainer; private readonly Container leftBackgroundContainer; private readonly Container rightBackgroundContainer; private readonly Box leftBackground; @@ -60,7 +53,7 @@ namespace osu.Game.Modes.Taiko.UI public TaikoPlayfield() { RelativeSizeAxes = Axes.X; - Height = PlayfieldHeight; + Height = PLAYFIELD_HEIGHT; AddInternal(new Drawable[] { @@ -92,7 +85,7 @@ namespace osu.Game.Modes.Taiko.UI { new Container { - Padding = new MarginPadding { Left = hit_target_offset }, + X = hit_target_offset, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -101,13 +94,12 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), - Scale = new Vector2(PLAYFIELD_SCALE), BlendingMode = BlendingMode.Additive }, - //barLineContainer = new Container - //{ - // RelativeSizeAxes = Axes.Both, - //}, + barLineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, new HitTarget { Anchor = Anchor.CentreLeft, @@ -128,7 +120,7 @@ namespace osu.Game.Modes.Taiko.UI }, leftBackgroundContainer = new Container { - Size = new Vector2(left_area_size, PlayfieldHeight), + Size = new Vector2(left_area_size, PLAYFIELD_HEIGHT), BorderThickness = 1, Children = new Drawable[] { @@ -153,10 +145,10 @@ namespace osu.Game.Modes.Taiko.UI }, } }, - //topLevelHitContainer = new Container - //{ - // RelativeSizeAxes = Axes.Both, - //} + topLevelHitContainer = new Container + { + RelativeSizeAxes = Axes.Both, + } }); } @@ -175,14 +167,22 @@ namespace osu.Game.Modes.Taiko.UI h.Depth = (float)h.HitObject.StartTime; base.Add(h); + + // Swells should be moved at the very top of the playfield when they reach the hit target + var swell = h as DrawableSwell; + if (swell != null) + swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); + } + + public void AddBarLine(DrawableBarLine barLine) + { + barLineContainer.Add(barLine); } public override void OnJudgement(DrawableHitObject judgedObject) { bool wasHit = judgedObject.Judgement.Result == HitResult.Hit; - - if (wasHit) - hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement)); + bool secondHit = judgedObject.Judgement.SecondHit; judgementContainer.Add(new DrawableTaikoJudgement(judgedObject.Judgement) { @@ -191,6 +191,22 @@ namespace osu.Game.Modes.Taiko.UI RelativePositionAxes = Axes.X, X = wasHit ? judgedObject.Position.X : 0, }); + + if (!wasHit) + return; + + if (!secondHit) + { + if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell)) + { + // If we're far enough away from the left stage, we should bring outselves in front of it + topLevelHitContainer.Add(judgedObject.CreateProxy()); + } + + hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement)); + } + else + hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); } } } \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index e1c10fe8f4..d0981c2500 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -50,17 +50,41 @@ + - - + + + + + + + + + + + + + + + + + + + + + - + + + + + - + diff --git a/osu.Game.Modes.Taiko/packages.config b/osu.Game.Modes.Taiko/packages.config index 08fca09c35..4031dd62a8 100644 --- a/osu.Game.Modes.Taiko/packages.config +++ b/osu.Game.Modes.Taiko/packages.config @@ -1,5 +1,4 @@  -