diff --git a/.vscode/launch.json b/.vscode/launch.json index b981556649..c836ff97bc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "preLaunchTask": "build", "runtimeExecutable": null, "env": {}, - "externalConsole": false + "console": "internalConsole" }, { "name": "Launch Desktop", @@ -23,7 +23,7 @@ "preLaunchTask": "build", "runtimeExecutable": null, "env": {}, - "externalConsole": false + "console": "internalConsole" }, { "name": "Attach", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0c0e79f7fb..03f5bc4c6c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,25 +2,23 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "0.1.0", - "windows": { - "command": "msbuild" - }, - "linux": { - "command": "xbuild" - }, - "args": [ - // Ask msbuild to generate full paths for file names. - "/property:GenerateFullPaths=true" - ], "taskSelector": "/t:", - "showOutput": "silent", "tasks": [ { "taskName": "build", - // Show the output window only if unrecognized errors occur. + "isShellCommand": true, "showOutput": "silent", + "command": "xbuild", + "windows": { + "command": "msbuild" + }, + "args": [ + // Ask msbuild to generate full paths for file names. + "/property:GenerateFullPaths=true" + ], // Use the standard MS compiler pattern to detect errors, warnings and infos - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "isBuildCommand": true } ] } \ No newline at end of file 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 bf6a3dc401..2234013e59 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit bf6a3dc40176ee4f921012808070e014fc4a5779 +Subproject commit 2234013e59a99116ee9f9e56a95ff8a6667db2a7 diff --git a/osu-resources b/osu-resources index 12bbab717d..0cba3cbc16 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 12bbab717d372dadbd3220d38da862276ac97e98 +Subproject commit 0cba3cbc167cfe94e07fe5b629c925e190be939e diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config index d1da144f50..45685a74a8 100644 --- a/osu.Desktop.Deploy/App.config +++ b/osu.Desktop.Deploy/App.config @@ -21,4 +21,16 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste + + + + + + + + + + + + \ No newline at end of file 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.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index 7a3719a25b..901117b026 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -68,9 +68,8 @@ $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll True - - $(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll $(SolutionDir)\packages\squirrel.windows.1.5.2\lib\Net45\NuGet.Squirrel.dll @@ -120,7 +119,7 @@ - - - + + 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/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/Tests/TestCaseBeatmapDetails.cs b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetails.cs new file mode 100644 index 0000000000..4a59ad9534 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseBeatmapDetails.cs @@ -0,0 +1,65 @@ +// 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.Primitives; +using osu.Framework.Testing; +using osu.Game.Database; +using osu.Game.Screens.Select; +using System.Linq; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseBeatmapDetails : TestCase + { + public override string Description => "BeatmapDetails tab of BeatmapDetailArea"; + + private BeatmapDetails details; + + public override void Reset() + { + base.Reset(); + + Add(details = new BeatmapDetails + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(150), + Beatmap = new BeatmapInfo + { + Version = "VisualTest", + Metadata = new BeatmapMetadata + { + Source = "Some guy", + Tags = "beatmap metadata example with a very very long list of tags and not much creativity", + }, + Difficulty = new BeatmapDifficulty + { + CircleSize = 7, + ApproachRate = 3.5f, + OverallDifficulty = 5.7f, + DrainRate = 1, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0,10), + Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6), + }, + }, + }); + + AddRepeatStep("fail values", newRetryAndFailValues, 10); + } + + private int lastRange = 1; + + private void newRetryAndFailValues() + { + details.Beatmap.Metrics.Fails = Enumerable.Range(lastRange, 100).Select(i => i % 12 - 6); + details.Beatmap.Metrics.Retries = Enumerable.Range(lastRange - 3, 100).Select(i => i % 12 - 6); + details.Beatmap = details.Beatmap; + lastRange += 100; + } + } +} \ No newline at end of file diff --git a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs index 2cb63ba7a0..2663c952cf 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs @@ -3,15 +3,12 @@ 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/TestCaseGraph.cs b/osu.Desktop.VisualTests/Tests/TestCaseGraph.cs new file mode 100644 index 0000000000..7ac795f6f9 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseGraph.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using System.Linq; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseGraph : TestCase + { + public override string Description => "graph"; + + private BarGraph graph; + + public override void Reset() + { + base.Reset(); + + Children = new[] + { + graph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f), + }, + }; + + AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1,10).Select(i => (float)i)); + AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); + AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); + AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop); + AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); + AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); + AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); + } + } +} \ No newline at end of file diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index cb7a3e3f84..3d9f3aad79 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -99,6 +99,7 @@ namespace osu.Desktop.VisualTests.Tests AddToggleStep(@"auto", state => { auto = state; load(mode); }); + BasicSliderBar sliderBar; Add(new Container { Anchor = Anchor.TopRight, @@ -107,16 +108,17 @@ namespace osu.Desktop.VisualTests.Tests Children = new Drawable[] { new SpriteText { Text = "Playback Speed" }, - new BasicSliderBar + sliderBar = new BasicSliderBar { Width = 150, Height = 10, SelectionColor = Color4.Orange, - Bindable = playbackSpeed } } }); + sliderBar.Current.BindTo(playbackSpeed); + framedClock.ProcessFrame(); var clockAdjustContainer = new Container diff --git a/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs index 3dba201f5d..b1b9ddbcda 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseKeyCounter.cs @@ -44,6 +44,8 @@ namespace osu.Desktop.VisualTests.Tests kc.Add(new KeyCounterKeyboard(key)); }); + TestSliderBar sliderBar; + Add(new Container { Anchor = Anchor.TopRight, @@ -52,16 +54,17 @@ namespace osu.Desktop.VisualTests.Tests Children = new Drawable[] { new SpriteText { Text = "FadeTime" }, - new TestSliderBar + sliderBar =new TestSliderBar { Width = 150, Height = 10, SelectionColor = Color4.Orange, - Bindable = bindable } } }); + sliderBar.Current.BindTo(bindable); + Add(kc); } private class TestSliderBar : SliderBar where T : struct 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/TestCaseMusicController.cs b/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs index c0c17cd50e..5665bf859a 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseMusicController.cs @@ -30,7 +30,9 @@ namespace osu.Desktop.VisualTests.Tests Anchor = Anchor.Centre }; Add(mc); - AddToggleStep(@"Show", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); + + AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); + AddStep(@"show", () => mc.State = Visibility.Visible); } } } diff --git a/osu.Desktop.VisualTests/Tests/TestCasePauseOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCasePauseOverlay.cs deleted file mode 100644 index ebf6e0c350..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.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") - }); - AddStep("Pause", pauseOverlay.Show); - AddStep("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 aedab7e895..1a43425dda 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs @@ -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; @@ -44,13 +44,13 @@ namespace osu.Desktop.VisualTests.Tests AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); } - protected override void Dispose(bool isDisposing) - { - if (oldDb != null) - 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 f36889b02a..624723ed35 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs @@ -83,10 +83,7 @@ namespace osu.Desktop.VisualTests.Tests Colour = Color4.Black, }); - Add(new PlayerLoader(Player = CreatePlayer(beatmap)) - { - Beatmap = beatmap - }); + Add(Player = CreatePlayer(beatmap)); } protected virtual Player CreatePlayer(WorkingBeatmap beatmap) 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 55fc969217..f3cca16678 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs @@ -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, diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs index 23e7f8a74d..b72abd1992 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs @@ -36,9 +36,9 @@ namespace osu.Desktop.VisualTests.Tests filter.PinItem(GroupMode.All); filter.PinItem(GroupMode.RecentlyPlayed); - filter.ItemChanged += (sender, mode) => + filter.Current.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 index 4005c94b5a..b3cb8c3457 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs @@ -1,13 +1,12 @@ // 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.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Graphics; -using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; namespace osu.Desktop.VisualTests.Tests { @@ -24,13 +23,12 @@ namespace osu.Desktop.VisualTests.Tests AddToggleStep("Kiai", b => { kiai = !kiai; - Reset(); + updateKiaiState(); }); Add(new CirclePiece { Position = new Vector2(100, 100), - Width = 0, AccentColour = Color4.DarkRed, KiaiMode = kiai, Children = new[] @@ -39,10 +37,9 @@ namespace osu.Desktop.VisualTests.Tests } }); - Add(new StrongCirclePiece + Add(new CirclePiece(true) { Position = new Vector2(350, 100), - Width = 0, AccentColour = Color4.DarkRed, KiaiMode = kiai, Children = new[] @@ -54,7 +51,6 @@ namespace osu.Desktop.VisualTests.Tests Add(new CirclePiece { Position = new Vector2(100, 300), - Width = 0, AccentColour = Color4.DarkBlue, KiaiMode = kiai, Children = new[] @@ -63,10 +59,9 @@ namespace osu.Desktop.VisualTests.Tests } }); - Add(new StrongCirclePiece + Add(new CirclePiece(true) { Position = new Vector2(350, 300), - Width = 0, AccentColour = Color4.DarkBlue, KiaiMode = kiai, Children = new[] @@ -78,7 +73,6 @@ namespace osu.Desktop.VisualTests.Tests Add(new CirclePiece { Position = new Vector2(100, 500), - Width = 0, AccentColour = Color4.Orange, KiaiMode = kiai, Children = new[] @@ -87,37 +81,29 @@ namespace osu.Desktop.VisualTests.Tests } }); - Add(new DrumRollCircle(new CirclePiece + Add(new ElongatedCirclePiece { - KiaiMode = kiai - }) - { - Width = 250, - Position = new Vector2(575, 100) + Position = new Vector2(575, 100), + AccentColour = Color4.Orange, + KiaiMode = kiai, + Length = 0.10f, + PlayfieldLengthReference = () => DrawSize.X }); - Add(new DrumRollCircle(new StrongCirclePiece + Add(new ElongatedCirclePiece(true) { - KiaiMode = kiai - }) - { - Width = 250, - Position = new Vector2(575, 300) + Position = new Vector2(575, 300), + AccentColour = Color4.Orange, + KiaiMode = kiai, + Length = 0.10f, + PlayfieldLengthReference = () => DrawSize.X }); } - private class DrumRollCircle : BaseCircle + private void updateKiaiState() { - public DrumRollCircle(CirclePiece piece) - : base(piece) - { - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Piece.AccentColour = colours.YellowDark; - } + foreach (var c in Children.OfType()) + c.KiaiMode = kiai; } private abstract class BaseCircle : Container diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index c0aa3af176..4e9ff4980e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -1,24 +1,33 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.MathUtils; 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.Drawable; +using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.UI; +using System; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseTaikoPlayfield : TestCase { + private const double default_duration = 300; + private const float scroll_time = 1000; + public override string Description => "Taiko playfield"; + protected override double TimePerAction => default_duration * 2; + + private readonly Random rng = new Random(1337); private TaikoPlayfield playfield; + private Container playfieldContainer; public override void Reset() { @@ -26,17 +35,31 @@ namespace osu.Desktop.VisualTests.Tests AddStep("Hit!", addHitJudgement); AddStep("Miss :(", addMissJudgement); - AddStep("Swell", addSwell); + 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)); + AddStep("Height test 1", () => changePlayfieldSize(1)); + AddStep("Height test 2", () => changePlayfieldSize(2)); + AddStep("Height test 3", () => changePlayfieldSize(3)); + AddStep("Height test 4", () => changePlayfieldSize(4)); + AddStep("Height test 5", () => changePlayfieldSize(5)); + AddStep("Reset height", () => changePlayfieldSize(6)); - Add(new Container + var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; + + Add(playfieldContainer = new Container { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Y = 200, - Padding = new MarginPadding { Left = 200 }, + Height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT, + Clock = new FramedClock(rateAdjustClock), Children = new[] { playfield = new TaikoPlayfield() @@ -44,21 +67,63 @@ namespace osu.Desktop.VisualTests.Tests }); } + private void changePlayfieldSize(int step) + { + // Add new hits + switch (step) + { + case 1: + addCentreHit(false); + break; + case 2: + addCentreHit(true); + break; + case 3: + addDrumRoll(false); + break; + case 4: + addDrumRoll(true); + break; + case 5: + addSwell(1000); + playfieldContainer.Delay(scroll_time - 100); + break; + } + + // Tween playfield height + switch (step) + { + default: + playfieldContainer.ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); + break; + case 6: + playfieldContainer.ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT), 500); + break; + } + } + private void addHitJudgement() { TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great; - playfield.OnJudgement(new DrawableTestHit(new Hit()) + var h = new DrawableTestHit(new Hit()) { X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f), Judgement = new TaikoJudgement { Result = HitResult.Hit, TaikoResult = hitResult, - TimeOffset = 0, - SecondHit = RNG.Next(10) == 0 + TimeOffset = 0 } - }); + }; + + playfield.OnJudgement(h); + + if (RNG.Next(10) == 0) + { + h.Judgement.SecondHit = true; + playfield.OnJudgement(h); + } } private void addMissJudgement() @@ -73,26 +138,53 @@ namespace osu.Desktop.VisualTests.Tests }); } - private void addSwell() + 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 = Time.Current + 1000, - EndTime = Time.Current + 5000, - PreEmpt = 1000 + 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 = Time.Current + 1000, - PreEmpt = 1000 + StartTime = playfield.Time.Current + scroll_time, + ScrollTime = scroll_time }; if (strong) - playfield.Add(new DrawableStrongCentreHit(h)); + playfield.Add(new DrawableCentreHitStrong(h)); else playfield.Add(new DrawableCentreHit(h)); } @@ -101,12 +193,12 @@ namespace osu.Desktop.VisualTests.Tests { Hit h = new Hit { - StartTime = Time.Current + 1000, - PreEmpt = 1000 + StartTime = playfield.Time.Current + scroll_time, + ScrollTime = scroll_time }; if (strong) - playfield.Add(new DrawableStrongRimHit(h)); + playfield.Add(new DrawableRimHitStrong(h)); else playfield.Add(new DrawableRimHit(h)); } diff --git a/osu.Desktop.VisualTests/VisualTestGame.cs b/osu.Desktop.VisualTests/VisualTestGame.cs index 0392dc5443..e0d168390b 100644 --- a/osu.Desktop.VisualTests/VisualTestGame.cs +++ b/osu.Desktop.VisualTests/VisualTestGame.cs @@ -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(). diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index b117515433..bfabf0081c 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -83,22 +83,20 @@ - - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\packages\SharpCompress.0.15.1\lib\net45\SharpCompress.dll - True + + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll + + + $(SolutionDir)\packages\SharpCompress.0.15.2\lib\net45\SharpCompress.dll False $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll - - $(SolutionDir)\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - $(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll @@ -187,8 +185,11 @@ + + + @@ -207,7 +208,6 @@ - diff --git a/osu.Desktop.VisualTests/packages.config b/osu.Desktop.VisualTests/packages.config index 5a30c50600..cad2ffff0d 100644 --- a/osu.Desktop.VisualTests/packages.config +++ b/osu.Desktop.VisualTests/packages.config @@ -4,9 +4,9 @@ Copyright (c) 2007-2017 ppy Pty Ltd . Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE --> - - - + + + diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs index 0ef448cafe..abc45d82ec 100644 --- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs @@ -3,10 +3,7 @@ using System.IO; using System.Linq; -using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.IO; -using osu.Game.Beatmaps; -using osu.Game.Database; namespace osu.Desktop.Beatmaps.IO { @@ -18,20 +15,17 @@ namespace osu.Desktop.Beatmaps.IO public static void Register() => AddReader((storage, path) => Directory.Exists(path)); private string basePath { get; } - private Beatmap firstMap { get; } public LegacyFilesystemReader(string path) { basePath = path; + BeatmapFilenames = Directory.GetFiles(basePath, @"*.osu").Select(Path.GetFileName).ToArray(); + if (BeatmapFilenames.Length == 0) throw new FileNotFoundException(@"This directory contains no beatmaps"); + StoryboardFilename = Directory.GetFiles(basePath, @"*.osb").Select(Path.GetFileName).FirstOrDefault(); - using (var stream = new StreamReader(GetStream(BeatmapFilenames[0]))) - { - var decoder = BeatmapDecoder.GetDecoder(stream); - firstMap = decoder.Decode(stream); - } } public override Stream GetStream(string name) @@ -39,11 +33,6 @@ namespace osu.Desktop.Beatmaps.IO return File.OpenRead(Path.Combine(basePath, name)); } - public override BeatmapMetadata ReadMetadata() - { - return firstMap.BeatmapInfo.Metadata; - } - public override void Dispose() { // no-op 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/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 70925f6cf4..9532652bfe 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -189,19 +189,24 @@ namespace osu.Desktop.Overlays private class UpdateProgressNotification : ProgressNotification { + private OsuGame game; + protected override Notification CreateCompletionNotification() => new ProgressCompletionNotification() { Text = @"Update ready to install. Click to restart!", Activated = () => { - UpdateManager.RestartApp(); + UpdateManager.RestartAppWhenExited(); + game.GracefullyExit(); return true; } }; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OsuGame game) { + this.game = game; + IconContent.Add(new Drawable[] { new Box 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 fbc342d695..dbd26b4640 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -124,8 +124,7 @@ True - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index be9b65f0c6..60e8182c82 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -7,7 +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/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/UI/CatchPlayfield.cs b/osu.Game.Modes.Catch/UI/CatchPlayfield.cs index cf1a665470..f8792a7fd5 100644 --- a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Modes.Catch/UI/CatchPlayfield.cs @@ -14,8 +14,7 @@ namespace osu.Game.Modes.Catch.UI { public CatchPlayfield() { - RelativeSizeAxes = Axes.Y; - Size = new Vector2(512, 0.9f); + Size = new Vector2(1, 0.9f); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; diff --git a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj index 593d8db4f6..50b1a095af 100644 --- a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj +++ b/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj @@ -33,8 +33,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -84,7 +83,7 @@ - - + \ No newline at end of file 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/UI/ManiaPlayfield.cs b/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs index 670d18f71f..deb4ebac25 100644 --- a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs @@ -15,8 +15,7 @@ namespace osu.Game.Modes.Mania.UI { public ManiaPlayfield(int columns) { - RelativeSizeAxes = Axes.Both; - Size = new Vector2(columns / 20f, 1f); + Size = new Vector2(0.8f, 1f); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; diff --git a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj index cc925d417a..896e9c68c6 100644 --- a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj +++ b/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj @@ -33,8 +33,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -89,7 +88,7 @@ - - + \ No newline at end of file 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/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs index efbd5b291a..7815e3ba41 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -32,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/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 be66689054..188306c857 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -2,12 +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.Game.Beatmaps.Samples; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Judgements; using OpenTK; @@ -53,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) @@ -77,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/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/OsuKeyConversionInputManager.cs b/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs index 986240b37f..567c7a35b1 100644 --- a/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs +++ b/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs @@ -42,14 +42,14 @@ namespace osu.Game.Modes.Osu { if (mouseDisabled.Value) { - mouse.PressedButtons.Remove(MouseButton.Left); - mouse.PressedButtons.Remove(MouseButton.Right); + mouse.SetPressed(MouseButton.Left, false); + mouse.SetPressed(MouseButton.Right, false); } if (leftViaKeyboard) - mouse.PressedButtons.Add(MouseButton.Left); + mouse.SetPressed(MouseButton.Left, true); if (rightViaKeyboard) - mouse.PressedButtons.Add(MouseButton.Right); + mouse.SetPressed(MouseButton.Right, true); } } } 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/Scoring/OsuScoreProcessor.cs b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs index 0bd587e8ea..3b798a2fad 100644 --- a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs @@ -35,11 +35,9 @@ namespace osu.Game.Modes.Osu.Scoring switch (judgement.Result) { case HitResult.Hit: - Combo.Value++; Health.Value += 0.1f; break; case HitResult.Miss: - Combo.Value = 0; Health.Value -= 0.2f; break; } diff --git a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs index ca9ff6fc61..7e314c5ba1 100644 --- a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs +++ b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK; using osu.Game.Beatmaps; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Beatmaps; @@ -46,5 +47,7 @@ namespace osu.Game.Modes.Osu.UI return new DrawableSpinner(spinner); return null; } + + protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); } } diff --git a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs index 8090263fe1..4164607b4d 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 @@ -36,8 +38,6 @@ namespace osu.Game.Modes.Osu.UI { Anchor = Anchor.Centre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(0.75f); Add(new Drawable[] { diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj index 55322e855e..21f0f03d8c 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj @@ -34,8 +34,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -104,7 +103,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index cc361628a3..aee06ad796 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -2,96 +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.Beatmaps.Samples; 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 - SampleType sample = original.Sample?.Type ?? SampleType.None; + List samples = obj.Samples; - bool strong = (sample & SampleType.Finish) > 0; + bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); if (distanceData != null) { - return new DrumRoll + int repeats = 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) { - StartTime = original.StartTime, - Sample = original.Sample, + 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, - - Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) + 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 Swell + bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + + if (isRim) { - StartTime = original.StartTime, - Sample = original.Sample, - IsStrong = strong, - - EndTime = original.StartTime + endTimeData.Duration * bash_convert_factor - }; - } - - bool isCentre = (sample & ~(SampleType.Finish | SampleType.Normal)) == 0; - - if (isCentre) - { - return new CentreHit + yield return new RimHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + }; + } + else { - StartTime = original.StartTime, - Sample = original.Sample, - IsStrong = strong - }; + yield return new CentreHit + { + StartTime = obj.StartTime, + Samples = obj.Samples, + IsStrong = strong, + }; + } } - - return new RimHit - { - StartTime = original.StartTime, - Sample = original.Sample, - IsStrong = strong, - }; } } } diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index b6a727aeb4..6ae476b265 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -15,6 +15,8 @@ namespace osu.Game.Modes.Taiko.Judgements /// public override string MaxResultString => string.Empty; + public override bool AffectsCombo => false; + protected override int NumericResultForScore(TaikoHitResult result) { switch (result) diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs index f4745730db..7676ef8c29 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs @@ -3,6 +3,7 @@ using osu.Game.Modes.Judgements; using osu.Framework.Extensions; +using osu.Game.Modes.Objects.Drawables; namespace osu.Game.Modes.Taiko.Judgements { @@ -21,12 +22,12 @@ namespace osu.Game.Modes.Taiko.Judgements /// /// The result value for the combo portion of the score. /// - public int ResultValueForScore => NumericResultForScore(TaikoResult); + public int ResultValueForScore => Result == HitResult.Miss ? 0 : NumericResultForScore(TaikoResult); /// /// The result value for the accuracy portion of the score. /// - public int ResultValueForAccuracy => NumericResultForAccuracy(TaikoResult); + public int ResultValueForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(TaikoResult); /// /// The maximum result value for the combo portion of the score. @@ -43,7 +44,7 @@ namespace osu.Game.Modes.Taiko.Judgements public override string MaxResultString => MAX_HIT_RESULT.GetDescription(); /// - /// Whether this Judgement has a secondary hit in the case of finishers. + /// Whether this Judgement has a secondary hit in the case of strong hits. /// public virtual bool SecondHit { get; set; } 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/Drawable/DrawableDrumRoll.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs deleted file mode 100644 index 3551538fe7..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRoll.cs +++ /dev/null @@ -1,57 +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.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; -using System.Linq; - -namespace osu.Game.Modes.Taiko.Objects.Drawable -{ - public class DrawableDrumRoll : DrawableTaikoHitObject - { - private readonly DrumRoll drumRoll; - - public DrawableDrumRoll(DrumRoll drumRoll) - : base(drumRoll) - { - this.drumRoll = drumRoll; - - int tickIndex = 0; - foreach (var tick in drumRoll.Ticks) - { - var newTick = new DrawableDrumRollTick(tick) - { - Depth = tickIndex, - X = (float)((tick.StartTime - HitObject.StartTime) / drumRoll.Duration) - }; - - AddNested(newTick); - - tickIndex++; - } - } - - protected override void UpdateState(ArmedState state) - { - } - - 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 > drumRoll.RequiredGoodHits) - { - Judgement.Result = HitResult.Hit; - Judgement.TaikoResult = countHit >= drumRoll.RequiredGreatHits ? TaikoHitResult.Great : TaikoHitResult.Good; - } - else - Judgement.Result = HitResult.Miss; - } - } -} diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs deleted file mode 100644 index b4ec0b108a..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongCentreHit.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Input; -using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Modes.Taiko.Objects.Drawable -{ - public class DrawableStrongCentreHit : DrawableStrongHit - { - protected override Key[] HitKeys { get; } = { Key.F, Key.J }; - - private readonly CirclePiece circlePiece; - - public DrawableStrongCentreHit(Hit hit) - : base(hit) - { - Add(circlePiece = new StrongCirclePiece - { - Children = new [] - { - new CentreHitSymbolPiece() - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - circlePiece.AccentColour = colours.PinkDarker; - } - } -} diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs deleted file mode 100644 index 1f77ad0409..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongRimHit.cs +++ /dev/null @@ -1,35 +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.Allocation; -using osu.Game.Graphics; -using OpenTK.Input; -using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; - -namespace osu.Game.Modes.Taiko.Objects.Drawable -{ - public class DrawableStrongRimHit : DrawableStrongHit - { - protected override Key[] HitKeys { get; } = { Key.D, Key.K }; - - private readonly CirclePiece circlePiece; - - public DrawableStrongRimHit(Hit hit) - : base(hit) - { - Add(circlePiece = new StrongCirclePiece - { - Children = new[] - { - new RimHitSymbolPiece() - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - circlePiece.AccentColour = colours.BlueDarker; - } - } -} diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs deleted file mode 100644 index 2321ad30ee..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs +++ /dev/null @@ -1,161 +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.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Backgrounds; -using OpenTK.Graphics; -using System; -using osu.Game.Graphics; - -namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces -{ - /// - /// A circle piece which is used uniformly through osu!taiko to visualise hitobjects. - /// - /// The body of this piece will overshoot its parent by to form - /// a rounded (_[-Width-]_) figure such that a regular "circle" is the result of a parent with Width = 0. - /// - /// - public class CirclePiece : Container, IHasAccentColour - { - 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; - - private Color4 accentColour; - /// - /// The colour of the inner circle and outer glows. - /// - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - - innerBackground.Colour = AccentColour; - - triangles.ColourLight = AccentColour; - triangles.ColourDark = AccentColour.Darken(0.1f); - - resetEdgeEffects(); - } - } - - private bool kiaiMode; - /// - /// Whether Kiai mode effects are enabled for this circle piece. - /// - public bool KiaiMode - { - get { return kiaiMode; } - set - { - kiaiMode = value; - - resetEdgeEffects(); - } - } - - public override Anchor Origin - { - get { return Anchor.CentreLeft; } - set { throw new InvalidOperationException($"{nameof(CirclePiece)} must always use CentreLeft origin."); } - } - - protected override Container Content => SymbolContainer; - protected readonly Container SymbolContainer; - - private readonly Container innerLayer; - private readonly Container innerCircleContainer; - private readonly Box innerBackground; - private readonly Triangles triangles; - - public CirclePiece() - { - RelativeSizeAxes = Axes.X; - Height = TaikoHitObject.CIRCLE_RADIUS * 2; - - // The "inner layer" is the body of the CirclePiece that overshoots it by Height/2 px on both sides - AddInternal(innerLayer = new Container - { - Name = "Inner Layer", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Children = new Framework.Graphics.Drawable[] - { - innerCircleContainer = new CircularContainer - { - Name = "Inner Circle", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Framework.Graphics.Drawable[] - { - innerBackground = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }, - triangles = new Triangles - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - } - } - }, - 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 - } - } - }, - SymbolContainer = new Container - { - Name = "Symbol", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - } - }); - } - - protected override void Update() - { - // Add the overshoot to compensate for corner radius - innerLayer.Width = DrawWidth + DrawHeight; - } - - private void resetEdgeEffects() - { - innerCircleContainer.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/Drawable/Pieces/StrongCirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/StrongCirclePiece.cs deleted file mode 100644 index 319ca17cb8..0000000000 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/StrongCirclePiece.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 OpenTK; - -namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces -{ - /// - /// A type of circle piece which is drawn at a higher scale to represent a "strong" piece. - /// - public class StrongCirclePiece : CirclePiece - { - /// - /// The amount to scale up the base circle to show it as a "strong" piece. - /// - private const float strong_scale = 1.5f; - - public StrongCirclePiece() - { - SymbolContainer.Scale = new Vector2(strong_scale); - } - - public override Vector2 Size => new Vector2(base.Size.X, base.Size.Y * strong_scale); - } -} 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/Drawable/DrawableCentreHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs similarity index 56% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableCentreHit.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs index 683d48df10..ff5ac859b4 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableCentreHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -1,35 +1,27 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; -using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; -using osu.Game.Graphics; 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.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { public class DrawableCentreHit : DrawableHit { protected override Key[] HitKeys { get; } = { Key.F, Key.J }; - private readonly CirclePiece circlePiece; - public DrawableCentreHit(Hit hit) : base(hit) { - Add(circlePiece = new CirclePiece - { - Children = new[] - { - new CentreHitSymbolPiece() - } - }); + MainPiece.Add(new CentreHitSymbolPiece()); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - circlePiece.AccentColour = colours.PinkDarker; + 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/Drawable/DrawableDrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs similarity index 58% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 5217fd9085..296affedaf 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -1,35 +1,39 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; -using osu.Game.Modes.Taiko.Judgements; 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.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { - public class DrawableDrumRollTick : DrawableTaikoHitObject + public class DrawableDrumRollTick : DrawableTaikoHitObject { - private readonly DrumRollTick tick; - public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { - this.tick = tick; } - protected override TaikoJudgement CreateJudgement() => new TaikoDrumRollTickJudgement(); + 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 > tick.HitWindow) + if (Judgement.TimeOffset > HitObject.HitWindow) Judgement.Result = HitResult.Miss; return; } - if (Math.Abs(Judgement.TimeOffset) < tick.HitWindow) + if (Math.Abs(Judgement.TimeOffset) < HitObject.HitWindow) { Judgement.Result = HitResult.Hit; Judgement.TaikoResult = TaikoHitResult.Great; @@ -38,11 +42,17 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override void UpdateState(ArmedState state) { + switch (state) + { + case ArmedState.Hit: + Content.ScaleTo(0, 100, EasingTypes.OutQuint); + break; + } } protected override void UpdateScrollPosition(double time) { - // Drum roll ticks shouldn't move + // Ticks don't move } protected override bool HandleKeyPress(Key key) diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs similarity index 59% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs index c8a7355e3c..167fbebd7b 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs @@ -1,66 +1,53 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; using System; using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { - public abstract class DrawableHit : DrawableTaikoHitObject + public abstract class DrawableHit : DrawableTaikoHitObject { /// /// A list of keys which can result in hits for this HitObject. /// protected abstract Key[] HitKeys { get; } - protected override Container Content => bodyContainer; - - private readonly Hit hit; - /// /// Whether the last key pressed is a valid hit key. /// private bool validKeyPressed; - private readonly Container bodyContainer; - protected DrawableHit(Hit hit) : base(hit) { - this.hit = hit; - - AddInternal(bodyContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); } protected override void CheckJudgement(bool userTriggered) { if (!userTriggered) { - if (Judgement.TimeOffset > hit.HitWindowGood) + if (Judgement.TimeOffset > HitObject.HitWindowGood) Judgement.Result = HitResult.Miss; return; } double hitOffset = Math.Abs(Judgement.TimeOffset); - if (hitOffset > hit.HitWindowMiss) + if (hitOffset > HitObject.HitWindowMiss) return; if (!validKeyPressed) Judgement.Result = HitResult.Miss; - else if (hitOffset < hit.HitWindowGood) + else if (hitOffset < HitObject.HitWindowGood) { Judgement.Result = HitResult.Hit; - Judgement.TaikoResult = hitOffset < hit.HitWindowGreat ? TaikoHitResult.Great : TaikoHitResult.Good; + Judgement.TaikoResult = hitOffset < HitObject.HitWindowGreat ? TaikoHitResult.Great : TaikoHitResult.Good; } else Judgement.Result = HitResult.Miss; @@ -80,21 +67,39 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { Delay(HitObject.StartTime - Time.Current + Judgement.TimeOffset, true); + var circlePiece = MainPiece as CirclePiece; + + circlePiece?.FlashBox.Flush(); + switch (State) { case ArmedState.Idle: - Delay(hit.HitWindowMiss); + Delay(HitObject.HitWindowMiss); break; case ArmedState.Miss: FadeOut(100); break; case ArmedState.Hit: - bodyContainer.ScaleTo(0.8f, 400, EasingTypes.OutQuad); - bodyContainer.MoveToY(-200, 250, EasingTypes.Out); - bodyContainer.Delay(250); - bodyContainer.MoveToY(0, 500, EasingTypes.In); - FadeOut(600); + + var flash = circlePiece?.FlashBox; + if (flash != null) + { + flash.FadeTo(0.9f); + flash.FadeOut(300); + } + + + FadeOut(800); + + 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; } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs similarity index 87% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs index a6cb6ae7fa..4ab029acb3 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -1,16 +1,17 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; 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.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { - public abstract class DrawableStrongHit : DrawableHit + public abstract class DrawableHitStrong : DrawableHit { /// /// The lenience for the second key press. @@ -22,11 +23,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable private bool firstKeyHeld; private Key firstHitKey; - protected DrawableStrongHit(Hit hit) + 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) diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs similarity index 55% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs index cab6819300..5a311d51ef 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableRimHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -3,33 +3,25 @@ using osu.Framework.Allocation; using osu.Game.Graphics; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; -using osu.Game.Modes.Taiko.Objects.Drawable.Pieces; -namespace osu.Game.Modes.Taiko.Objects.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { public class DrawableRimHit : DrawableHit { protected override Key[] HitKeys { get; } = { Key.D, Key.K }; - private readonly CirclePiece circlePiece; - public DrawableRimHit(Hit hit) : base(hit) { - Add(circlePiece = new CirclePiece - { - Children = new[] - { - new RimHitSymbolPiece() - } - }); + MainPiece.Add(new RimHitSymbolPiece()); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - circlePiece.AccentColour = colours.BlueDarker; + 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/Drawable/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs similarity index 85% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs index ccd6380542..1e440df69a 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs @@ -1,9 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,13 +11,14 @@ 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.Drawable.Pieces; -using System; -using System.Linq; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { - public class DrawableSwell : DrawableTaikoHitObject + public class DrawableSwell : DrawableTaikoHitObject { /// /// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime. @@ -31,8 +31,6 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable private const float target_ring_scale = 5f; private const float inner_ring_alpha = 0.65f; - private readonly Swell swell; - private readonly Container bodyContainer; private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; @@ -54,13 +52,12 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable public DrawableSwell(Swell swell) : base(swell) { - this.swell = swell; - - Children = new Framework.Graphics.Drawable[] + Children = new Drawable[] { bodyContainer = new Container { - Children = new Framework.Graphics.Drawable[] + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { expandingRing = new CircularContainer { @@ -68,7 +65,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), BlendingMode = BlendingMode.Additive, Masking = true, Children = new [] @@ -85,11 +82,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable Name = "Target ring (thick border)", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Masking = true, BorderThickness = target_ring_thick_border, BlendingMode = BlendingMode.Additive, - Children = new Framework.Graphics.Drawable[] + Children = new Drawable[] { new Box { @@ -120,6 +117,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable }, circlePiece = new CirclePiece { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Children = new [] { symbol = new SwellSymbolPiece() @@ -128,6 +127,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable } } }; + + circlePiece.KiaiMode = HitObject.Kiai; } [BackgroundDependencyLoader] @@ -144,18 +145,18 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { userHits++; - var completion = (float)userHits / swell.RequiredHits; + 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 * swell.Duration / 8), 4000, EasingTypes.OutQuint); + 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 == swell.RequiredHits) + if (userHits == HitObject.RequiredHits) { Judgement.Result = HitResult.Hit; Judgement.TaikoResult = TaikoHitResult.Great; @@ -167,7 +168,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable return; //TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP - if (userHits > swell.RequiredHits / 2) + if (userHits > HitObject.RequiredHits / 2) { Judgement.Result = HitResult.Hit; Judgement.TaikoResult = TaikoHitResult.Good; @@ -187,7 +188,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable Delay(preempt, true); - Delay(Judgement.TimeOffset + swell.Duration, true); + Delay(Judgement.TimeOffset + HitObject.Duration, true); const float out_transition_time = 300; diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs similarity index 55% rename from osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5d6d669dc1..f15f2bd152 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableTaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -1,16 +1,20 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; +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 System.Collections.Generic; -using osu.Framework.Input; +using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using OpenTK; +using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawable +namespace osu.Game.Modes.Taiko.Objects.Drawables { - public abstract class DrawableTaikoHitObject : DrawableHitObject + 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. @@ -18,33 +22,52 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable /// private readonly List validKeys = new List(new[] { Key.D, Key.F, Key.J, Key.K }); - protected DrawableTaikoHitObject(TaikoHitObject hitObject) + 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.Centre; + Origin = Anchor.Custom; + + AutoSizeAxes = Axes.Both; RelativePositionAxes = Axes.X; - } - protected override void LoadComplete() - { - LifetimeStart = HitObject.StartTime - HitObject.PreEmpt * 2; + AddInternal(bodyContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new[] + { + MainPiece = CreateMainPiece() + } + }); - base.LoadComplete(); + 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) - { - MoveToX((float)((HitObject.StartTime - time) / HitObject.PreEmpt)); - } + protected virtual void UpdateScrollPosition(double time) => X = (float)((HitObject.StartTime - time) / HitObject.ScrollTime); protected override void Update() { diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs similarity index 90% rename from osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CentreHitSymbolPiece.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs index e62ca6b073..0cf4e97b41 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CentreHitSymbolPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -1,12 +1,12 @@ // 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.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using OpenTK; -namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces { /// /// The symbol used for centre hit pieces. 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..216e05ebc4 --- /dev/null +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -0,0 +1,152 @@ +// 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.DEFAULT_CIRCLE_DIAMETER * 0.45f; + public const float SYMBOL_BORDER = 8; + public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER; + + /// + /// 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 Box FlashBox; + + 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[] + { + FlashBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + BlendingMode = BlendingMode.Additive, + Alpha = 0, + AlwaysPresent = true + } + } + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Name = "Content", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + + if (isStrong) + { + Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; + + //default for symbols etc. + Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_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/Drawable/Pieces/RimHitSymbolPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/Drawable/Pieces/RimHitSymbolPiece.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs index 6999634108..6e19497978 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/RimHitSymbolPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs @@ -1,13 +1,13 @@ // 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.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces { /// /// The symbol used for rim hit pieces. diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/SwellSymbolPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs similarity index 88% rename from osu.Game.Modes.Taiko/Objects/Drawable/Pieces/SwellSymbolPiece.cs rename to osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index 2bd86406a7..e491793902 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -1,10 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Graphics; using osu.Framework.Graphics; +using osu.Game.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces +namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces { /// /// The symbol used for swell pieces. 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..2220438a4a --- /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.DEFAULT_CIRCLE_DIAMETER); + } + } +} 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..53e795e2e2 --- /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.DEFAULT_CIRCLE_DIAMETER / 16; + + /// + /// The size of a tick. + /// + private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 4; + + 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 1f9241268b..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; diff --git a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs index 2ca0d71fc1..32e8851b66 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs @@ -11,14 +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 => TickTimeDistance / 2; + public double HitWindow => TickSpacing / 2; } } \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/Swell.cs b/osu.Game.Modes.Taiko/Objects/Swell.cs index 4cbb5904c7..97101ea797 100644 --- a/osu.Game.Modes.Taiko/Objects/Swell.cs +++ b/osu.Game.Modes.Taiko/Objects/Swell.cs @@ -1,30 +1,19 @@ // 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 Swell : TaikoHitObject, IHasEndTime { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } /// /// The number of hits required to complete the swell successfully. /// - public int RequiredHits { get; protected set; } = 10; - - 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); - } + 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 ac47a3bc88..ebc9b19d3a 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs @@ -4,20 +4,41 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Modes.Objects; +using osu.Game.Modes.Taiko.UI; namespace osu.Game.Modes.Taiko.Objects { public abstract class TaikoHitObject : HitObject { /// - /// HitCircle radius. + /// Diameter of a circle relative to the size of the . /// - public const float CIRCLE_RADIUS = 42f; + public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f; /// - /// The time to scroll in the HitObject. + /// Scale multiplier for a strong circle. /// - public double PreEmpt; + public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f; + + /// + /// Default circle diameter. + /// + public const float DEFAULT_CIRCLE_DIAMETER = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT * PLAYFIELD_RELATIVE_DIAMETER; + + /// + /// Default strong circle diameter. + /// + public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE; + + /// + /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. + /// + 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. @@ -34,7 +55,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 index c8a93c9068..89d974baf9 100644 --- a/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs +++ b/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs @@ -72,14 +72,9 @@ namespace osu.Game.Modes.Taiko.Replays } else if (drumRoll != null) { - double delay = drumRoll.TickTimeDistance; - - double time = drumRoll.StartTime; - - for (int j = 0; j < drumRoll.TotalTicks; j++) + foreach (var tick in drumRoll.Ticks) { - Frames.Add(new ReplayFrame((int)time, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); - time += delay; + Frames.Add(new ReplayFrame(tick.StartTime, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); hitButton = !hitButton; } } diff --git a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs index fa7e18cadb..987c3181a4 100644 --- a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs @@ -187,10 +187,6 @@ namespace osu.Game.Modes.Taiko.Scoring if (!isTick) totalHits++; - // Apply combo changes, must be done before the hit score is added - if (!isTick && judgement.Result == HitResult.Hit) - Combo.Value++; - // Apply score changes addHitScore(judgement); @@ -261,7 +257,7 @@ namespace osu.Game.Modes.Taiko.Scoring foreach (var j in Judgements) { scoreForAccuracy += j.ResultValueForAccuracy; - maxScoreForAccuracy = j.MaxResultValueForAccuracy; + maxScoreForAccuracy += j.MaxResultValueForAccuracy; } Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy; diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Modes.Taiko/UI/HitExplosion.cs index 94938a700d..e4e329523f 100644 --- a/osu.Game.Modes.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Modes.Taiko/UI/HitExplosion.cs @@ -18,14 +18,18 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitExplosion : CircularContainer { - private readonly TaikoJudgement judgement; + /// + /// 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); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -50,10 +54,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; @@ -73,5 +74,13 @@ namespace osu.Game.Modes.Taiko.UI Expire(); } + + /// + /// Transforms this hit explosion to visualise a secondary hit. + /// + public void VisualiseSecondHit() + { + ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50); + } } } diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Modes.Taiko/UI/HitTarget.cs index 32b2877545..b22dc1d647 100644 --- a/osu.Game.Modes.Taiko/UI/HitTarget.cs +++ b/osu.Game.Modes.Taiko/UI/HitTarget.cs @@ -15,16 +15,6 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitTarget : Container { - /// - /// Diameter of normal hit object circles. - /// - private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2; - - /// - /// Diameter of finisher hit object circles. - /// - private const float finisher_diameter = normal_diameter * 1.5f; - /// /// The 1px inner border of the taiko playfield. /// @@ -37,7 +27,7 @@ namespace osu.Game.Modes.Taiko.UI public HitTarget() { - RelativeSizeAxes = Axes.Y; + Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT); Children = new Drawable[] { @@ -47,15 +37,15 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - finisher_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_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(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -72,10 +62,10 @@ 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), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -96,7 +86,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Y = -border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - finisher_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_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 e7470ee913..d238c38e74 100644 --- a/osu.Game.Modes.Taiko/UI/InputDrum.cs +++ b/osu.Game.Modes.Taiko/UI/InputDrum.cs @@ -21,7 +21,7 @@ namespace osu.Game.Modes.Taiko.UI { public InputDrum() { - Size = new Vector2(TaikoPlayfield.PLAYFIELD_HEIGHT); + Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT); const float middle_split = 10; @@ -60,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. /// @@ -128,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(1000, 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 e70e2d3811..32476dff7f 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs @@ -1,16 +1,23 @@ // 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.Replays; +using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.Scoring; using osu.Game.Modes.UI; +using osu.Game.Modes.Taiko.Replays; +using OpenTK; namespace osu.Game.Modes.Taiko.UI { @@ -21,15 +28,132 @@ 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; + } + } + + protected override Vector2 GetPlayfieldAspectAdjust() + { + const float default_relative_height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT / 768; + const float default_aspect = 16f / 9f; + + float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect; + + return new Vector2(1, default_relative_height * aspectAdjust); + } + + 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 b6165b785b..db3a1bc84e 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -14,22 +14,23 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Primitives; -using osu.Game.Modes.Taiko.Objects.Drawable; +using System.Linq; +using osu.Game.Modes.Taiko.Objects.Drawables; +using System; namespace osu.Game.Modes.Taiko.UI { public class TaikoPlayfield : Playfield { /// - /// The play field height. This is relative to the size of hit objects - /// such that the playfield is just a bit larger than finishers. + /// The default play field height. /// - public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; + public const float DEFAULT_PLAYFIELD_HEIGHT = 168f; /// /// The offset from which the center of the hit target lies at. /// - private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40; + private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40; /// /// The size of the left area of the playfield. This area contains the input drum. @@ -39,7 +40,7 @@ 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; @@ -51,13 +52,11 @@ namespace osu.Game.Modes.Taiko.UI public TaikoPlayfield() { - RelativeSizeAxes = Axes.X; - Height = PLAYFIELD_HEIGHT; - AddInternal(new Drawable[] { rightBackgroundContainer = new Container { + Name = "Transparent playfield background", RelativeSizeAxes = Axes.Both, BorderThickness = 2, Masking = true, @@ -76,76 +75,88 @@ namespace osu.Game.Modes.Taiko.UI }, } }, - new Container + new ScaleFixContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_HEIGHT, + Children = new[] { new Container { - Padding = new MarginPadding { Left = hit_target_offset }, + Name = "Transparent playfield elements", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, Children = new Drawable[] { - hitExplosionContainer = new Container + new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), - BlendingMode = BlendingMode.Additive + Name = "Hit target container", + X = hit_target_offset, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + BlendingMode = BlendingMode.Additive + }, + barLineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }, + hitObjectContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + judgementContainer = new Container + { + RelativeSizeAxes = Axes.Y, + BlendingMode = BlendingMode.Additive + }, + }, }, - //barLineContainer = new Container - //{ - // RelativeSizeAxes = Axes.Both, - //}, - new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - }, - hitObjectContainer = new Container + } + }, + leftBackgroundContainer = new Container + { + Name = "Left overlay", + Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT), + BorderThickness = 1, + Children = new Drawable[] + { + leftBackground = new Box { RelativeSizeAxes = Axes.Both, }, - judgementContainer = new Container + new InputDrum { - RelativeSizeAxes = Axes.Both, - BlendingMode = BlendingMode.Additive + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + Position = new Vector2(0.10f, 0), + Scale = new Vector2(0.9f) }, - }, - }, - } - }, - leftBackgroundContainer = new Container - { - Size = new Vector2(left_area_size, PLAYFIELD_HEIGHT), - BorderThickness = 1, - Children = new Drawable[] - { - leftBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - Position = new Vector2(0.10f, 0), - Scale = new Vector2(0.9f) - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } }, } }, topLevelHitContainer = new Container { + Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, } }); @@ -173,12 +184,15 @@ namespace osu.Game.Modes.Taiko.UI 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) { @@ -187,6 +201,73 @@ 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(); + } + + /// + /// This is a very special type of container. It serves a similar purpose to , however unlike , + /// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent. + /// + /// + /// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable + /// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this + /// container which takes care of reversing the width adjustment while appearing transparent to the user. + /// + /// + private class ScaleFixContainer : Container + { + protected override Container Content => widthAdjustmentContainer; + private readonly WidthAdjustmentContainer widthAdjustmentContainer; + + /// + /// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and doesn't care about having its width adjusted. + /// + protected override Vector2 DrawScale => Scale * RelativeToAbsoluteFactor.Y / DrawHeight; + + public ScaleFixContainer() + { + AddInternal(widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = () => DrawScale.X }); + } + + /// + /// The container type that reverses the width adjustment. + /// + private class WidthAdjustmentContainer : Container + { + /// + /// This container needs to know its parent's so it can reverse the width adjustment caused by . + /// + public Func ParentDrawScaleReference; + + public WidthAdjustmentContainer() + { + // This container doesn't care about height, it should always fill its parent + RelativeSizeAxes = Axes.Y; + } + + protected override void Update() + { + base.Update(); + + // Reverse the DrawScale adjustment + Width = Parent.DrawSize.X / ParentDrawScaleReference(); + } + } } } } \ 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 917be7a084..19ba5c77e4 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -33,8 +33,7 @@ - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll @@ -53,22 +52,27 @@ + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -107,7 +111,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs index 356fa5a6c1..8183bc952e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs @@ -6,11 +6,12 @@ using NUnit.Framework; using OpenTK; using OpenTK.Graphics; using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.Samples; using osu.Game.Modes; using osu.Game.Tests.Resources; using osu.Game.Modes.Osu; using osu.Game.Modes.Objects.Legacy; +using System.Linq; +using osu.Game.Audio; namespace osu.Game.Tests.Beatmaps.Formats { @@ -55,7 +56,6 @@ namespace osu.Game.Tests.Beatmaps.Formats var beatmapInfo = decoder.Decode(new StreamReader(stream)).BeatmapInfo; Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(false, beatmapInfo.Countdown); - Assert.AreEqual(SampleSet.Soft, beatmapInfo.SampleSet); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); Assert.AreEqual(PlayMode.Osu, beatmapInfo.Mode); @@ -137,12 +137,12 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsNotNull(slider); Assert.AreEqual(new Vector2(192, 168), slider.Position); Assert.AreEqual(956, slider.StartTime); - Assert.AreEqual(SampleType.None, slider.Sample.Type); + Assert.IsTrue(slider.Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); var hit = beatmap.HitObjects[1] as LegacyHit; Assert.IsNotNull(hit); Assert.AreEqual(new Vector2(304, 56), hit.Position); Assert.AreEqual(1285, hit.StartTime); - Assert.AreEqual(SampleType.Clap, hit.Sample.Type); + Assert.IsTrue(hit.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); } } } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 39fb1bfa8a..5d15b43761 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); Assert.IsTrue(File.Exists(temp)); - + File.Delete(temp); Assert.IsFalse(File.Exists(temp)); diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 2a69be92ca..b9c4cf780a 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -7,6 +7,8 @@ using osu.Game.Beatmaps.IO; using osu.Game.Modes; using osu.Game.Modes.Osu; using osu.Game.Tests.Resources; +using osu.Game.Beatmaps.Formats; +using osu.Game.Database; namespace osu.Game.Tests.Beatmaps.IO { @@ -53,7 +55,11 @@ namespace osu.Game.Tests.Beatmaps.IO using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) { var reader = new OszArchiveReader(osz); - var meta = reader.ReadMetadata(); + + BeatmapMetadata meta; + using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + meta = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; + Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index d01aa77e02..2844528d0c 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -29,13 +29,11 @@ false - - $(SolutionDir)\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll - True + + $(SolutionDir)\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll - $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index ca53ef08b0..9972fb41a1 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -1,12 +1,11 @@  - - - + + \ No newline at end of file diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs new file mode 100644 index 0000000000..171a1bdf75 --- /dev/null +++ b/osu.Game/Audio/SampleInfo.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Audio +{ + public class SampleInfo + { + public const string HIT_WHISTLE = @"hitwhistle"; + public const string HIT_FINISH = @"hitfinish"; + public const string HIT_NORMAL = @"hitnormal"; + public const string HIT_CLAP = @"hitclap"; + + /// + /// The bank to load the sample from. + /// + public string Bank; + + /// + /// The name of the sample to load. + /// + public string Name; + + /// + /// The sample volume. + /// + public int Volume; + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index ac0ab9966f..8c1378cae4 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Drawables GainedSelection = headerGainedSelection, RelativeSizeAxes = Axes.X, }; - + BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList(); BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b) { diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index e26dcac16b..534578337f 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -22,8 +22,9 @@ namespace osu.Game.Beatmaps.Drawables public Action GainedSelection; private readonly SpriteText title; private readonly SpriteText artist; - private OsuConfigManager config; + private Bindable preferUnicode; + private readonly WorkingBeatmap beatmap; private readonly FillFlowContainer difficultyIcons; @@ -33,19 +34,15 @@ namespace osu.Game.Beatmaps.Drawables Children = new Drawable[] { - new DelayedLoadContainer - { - RelativeSizeAxes = Axes.Both, - TimeBeforeLoad = 300, - FinishedLoading = d => d.FadeInFromZero(400, EasingTypes.Out), - Children = new[] + new DelayedLoadWrapper( + new PanelBackground(beatmap) { - new PanelBackground(beatmap) - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - } + RelativeSizeAxes = Axes.Both, + OnLoadComplete = d => d.FadeInFromZero(400, EasingTypes.Out), } + ) + { + TimeBeforeLoad = 300, }, new FillFlowContainer { @@ -87,24 +84,13 @@ namespace osu.Game.Beatmaps.Drawables [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - this.config = config; - preferUnicode = config.GetBindable(OsuConfig.ShowUnicode); - preferUnicode.ValueChanged += preferUnicode_changed; - preferUnicode_changed(preferUnicode, null); - } - - private void preferUnicode_changed(object sender, EventArgs e) - { - title.Text = config.GetUnicodeString(beatmap.BeatmapSetInfo.Metadata.Title, beatmap.BeatmapSetInfo.Metadata.TitleUnicode); - artist.Text = config.GetUnicodeString(beatmap.BeatmapSetInfo.Metadata.Artist, beatmap.BeatmapSetInfo.Metadata.ArtistUnicode); - } - - protected override void Dispose(bool isDisposing) - { - if (preferUnicode != null) - preferUnicode.ValueChanged -= preferUnicode_changed; - base.Dispose(isDisposing); + preferUnicode.ValueChanged += unicode => + { + title.Text = unicode ? beatmap.BeatmapSetInfo.Metadata.TitleUnicode : beatmap.BeatmapSetInfo.Metadata.Title; + artist.Text = unicode ? beatmap.BeatmapSetInfo.Metadata.ArtistUnicode : beatmap.BeatmapSetInfo.Metadata.Artist; + }; + preferUnicode.TriggerChange(); } private class PanelBackground : BufferedContainer diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 425c6cc5dc..452bd595c7 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -13,13 +13,13 @@ namespace osu.Game.Beatmaps.Formats { private static Dictionary decoders { get; } = new Dictionary(); - public static BeatmapDecoder GetDecoder(TextReader stream) + public static BeatmapDecoder GetDecoder(StreamReader stream) { - var line = stream.ReadLine()?.Trim(); + string line = stream.ReadLine()?.Trim(); if (line == null || !decoders.ContainsKey(line)) throw new IOException(@"Unknown file format"); - return (BeatmapDecoder)Activator.CreateInstance(decoders[line]); + return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line); } protected static void AddDecoder(string magic) where T : BeatmapDecoder @@ -27,17 +27,17 @@ namespace osu.Game.Beatmaps.Formats decoders[magic] = typeof(T); } - public virtual Beatmap Decode(TextReader stream) + public virtual Beatmap Decode(StreamReader stream) { return ParseFile(stream); } - public virtual void Decode(TextReader stream, Beatmap beatmap) + public virtual void Decode(StreamReader stream, Beatmap beatmap) { ParseFile(stream, beatmap); } - protected virtual Beatmap ParseFile(TextReader stream) + protected virtual Beatmap ParseFile(StreamReader stream) { var beatmap = new Beatmap { @@ -48,9 +48,11 @@ namespace osu.Game.Beatmaps.Formats Difficulty = new BeatmapDifficulty(), }, }; + ParseFile(stream, beatmap); return beatmap; } - protected abstract void ParseFile(TextReader stream, Beatmap beatmap); + + protected abstract void ParseFile(StreamReader stream, Beatmap beatmap); } } diff --git a/osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs deleted file mode 100644 index 3e7dbb4d1b..0000000000 --- a/osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs +++ /dev/null @@ -1,16 +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 System.IO; - -namespace osu.Game.Beatmaps.Formats -{ - public class ConstructableBeatmapDecoder : BeatmapDecoder - { - protected override void ParseFile(TextReader stream, Beatmap beatmap) - { - throw new NotImplementedException(); - } - } -} diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 20b977499e..35d81311d2 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.IO; using OpenTK.Graphics; using osu.Game.Beatmaps.Events; -using osu.Game.Beatmaps.Samples; using osu.Game.Beatmaps.Timing; using osu.Game.Modes; using osu.Game.Modes.Objects; @@ -31,6 +30,20 @@ namespace osu.Game.Beatmaps.Formats // TODO: Not sure how far back to go, or differences between versions } + private LegacySampleBank defaultSampleBank; + private int defaultSampleVolume = 100; + + private readonly int beatmapVersion; + + public OsuLegacyDecoder() + { + } + + public OsuLegacyDecoder(string header) + { + beatmapVersion = int.Parse(header.Substring(17)); + } + private enum Section { None, @@ -62,7 +75,10 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Countdown = int.Parse(val) == 1; break; case @"SampleSet": - beatmap.BeatmapInfo.SampleSet = (SampleSet)Enum.Parse(typeof(SampleSet), val); + defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), val); + break; + case @"SampleVolume": + defaultSampleVolume = int.Parse(val); break; case @"StackLeniency": beatmap.BeatmapInfo.StackLeniency = float.Parse(val, NumberFormatInfo.InvariantInfo); @@ -192,28 +208,56 @@ namespace osu.Game.Beatmaps.Formats private void handleTimingPoints(Beatmap beatmap, string val) { - ControlPoint cp = null; - string[] split = val.Split(','); - if (split.Length > 2) + double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); + double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + + TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + if (split.Length >= 3) + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); + + LegacySampleBank sampleSet = defaultSampleBank; + if (split.Length >= 4) + sampleSet = (LegacySampleBank)int.Parse(split[3]); + + //SampleBank sampleBank = SampleBank.Default; + //if (split.Length >= 5) + // sampleBank = (SampleBank)int.Parse(split[4]); + + int sampleVolume = defaultSampleVolume; + if (split.Length >= 6) + sampleVolume = int.Parse(split[5]); + + bool timingChange = true; + if (split.Length >= 7) + timingChange = split[6][0] == '1'; + + bool kiaiMode = false; + bool omitFirstBarSignature = false; + if (split.Length >= 8) { - int effectFlags = split.Length > 7 ? Convert.ToInt32(split[7], NumberFormatInfo.InvariantInfo) : 0; - double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); - cp = new ControlPoint - { - Time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo), - BeatLength = beatLength > 0 ? beatLength : 0, - VelocityAdjustment = beatLength < 0 ? -beatLength / 100.0 : 1, - TimingChange = split.Length <= 6 || split[6][0] == '1', - KiaiMode = (effectFlags & 1) > 0, - OmitFirstBarLine = (effectFlags & 8) > 0, - TimeSignature = (TimeSignatures)int.Parse(split[2]) - }; + int effectFlags = int.Parse(split[7]); + kiaiMode = (effectFlags & 1) > 0; + omitFirstBarSignature = (effectFlags & 8) > 0; } - if (cp != null) - beatmap.TimingInfo.ControlPoints.Add(cp); + string stringSampleSet = sampleSet.ToString().ToLower(); + if (stringSampleSet == @"none") + stringSampleSet = @"normal"; + + beatmap.TimingInfo.ControlPoints.Add(new ControlPoint + { + Time = time, + BeatLength = beatLength, + SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1, + TimingChange = timingChange, + TimeSignature = timeSignature, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature + }); } private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours) @@ -246,32 +290,36 @@ namespace osu.Game.Beatmaps.Formats } } - protected override Beatmap ParseFile(TextReader stream) + protected override Beatmap ParseFile(StreamReader stream) { return new LegacyBeatmap(base.ParseFile(stream)); } - public override Beatmap Decode(TextReader stream) + public override Beatmap Decode(StreamReader stream) { return new LegacyBeatmap(base.Decode(stream)); } - protected override void ParseFile(TextReader stream, Beatmap beatmap) + protected override void ParseFile(StreamReader stream, Beatmap beatmap) { - HitObjectParser parser = null; + beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion; + HitObjectParser parser = new LegacyHitObjectParser(); + + Section section = Section.None; bool hasCustomColours = false; - var section = Section.None; - while (true) + string line; + while ((line = stream.ReadLine()) != null) { - var line = stream.ReadLine(); - if (line == null) - break; if (string.IsNullOrEmpty(line)) continue; + if (line.StartsWith(@"osu file format v")) + { + beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17)); continue; + } if (line.StartsWith(@"[") && line.EndsWith(@"]")) { @@ -290,7 +338,6 @@ namespace osu.Game.Beatmaps.Formats { case Section.General: handleGeneral(beatmap, key, val); - parser = new LegacyHitObjectParser(); break; case Section.Editor: handleEditor(beatmap, key, val); @@ -311,7 +358,7 @@ namespace osu.Game.Beatmaps.Formats handleColours(beatmap, key, val, ref hasCustomColours); break; case Section.HitObjects: - var obj = parser?.Parse(val); + var obj = parser.Parse(val); if (obj != null) beatmap.HitObjects.Add(obj); @@ -320,5 +367,13 @@ namespace osu.Game.Beatmaps.Formats } } } + + internal enum LegacySampleBank + { + None = 0, + Normal = 1, + Soft = 2, + Drum = 3 + } } } diff --git a/osu.Game/Beatmaps/IBeatmapCoverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs similarity index 100% rename from osu.Game/Beatmaps/IBeatmapCoverter.cs rename to osu.Game/Beatmaps/IBeatmapConverter.cs diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/Beatmaps/IO/ArchiveReader.cs index bbf4de20f5..6c6b6be23c 100644 --- a/osu.Game/Beatmaps/IO/ArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/ArchiveReader.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using osu.Framework.IO.Stores; using osu.Framework.Platform; -using osu.Game.Database; namespace osu.Game.Beatmaps.IO { @@ -35,11 +34,6 @@ namespace osu.Game.Beatmaps.IO readers.Add(new Reader { Test = test, Type = typeof(T) }); } - /// - /// Reads the beatmap metadata from this archive. - /// - public abstract BeatmapMetadata ReadMetadata(); - /// /// Gets a list of beatmap file names. /// diff --git a/osu.Game/Beatmaps/IO/OszArchiveReader.cs b/osu.Game/Beatmaps/IO/OszArchiveReader.cs index 5c0f29fb86..6c550def8d 100644 --- a/osu.Game/Beatmaps/IO/OszArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/OszArchiveReader.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using Ionic.Zip; using osu.Game.Beatmaps.Formats; -using osu.Game.Database; namespace osu.Game.Beatmaps.IO { @@ -23,23 +22,18 @@ namespace osu.Game.Beatmaps.IO private readonly Stream archiveStream; private readonly ZipFile archive; - private readonly Beatmap firstMap; public OszArchiveReader(Stream archiveStream) { this.archiveStream = archiveStream; archive = ZipFile.Read(archiveStream); - BeatmapFilenames = archive.Entries.Where(e => e.FileName.EndsWith(@".osu")) - .Select(e => e.FileName).ToArray(); + + BeatmapFilenames = archive.Entries.Where(e => e.FileName.EndsWith(@".osu")).Select(e => e.FileName).ToArray(); + if (BeatmapFilenames.Length == 0) throw new FileNotFoundException(@"This directory contains no beatmaps"); - StoryboardFilename = archive.Entries.Where(e => e.FileName.EndsWith(@".osb")) - .Select(e => e.FileName).FirstOrDefault(); - using (var stream = new StreamReader(GetStream(BeatmapFilenames[0]))) - { - var decoder = BeatmapDecoder.GetDecoder(stream); - firstMap = decoder.Decode(stream); - } + + StoryboardFilename = archive.Entries.Where(e => e.FileName.EndsWith(@".osb")).Select(e => e.FileName).FirstOrDefault(); } public override Stream GetStream(string name) @@ -50,11 +44,6 @@ namespace osu.Game.Beatmaps.IO return entry.OpenReader(); } - public override BeatmapMetadata ReadMetadata() - { - return firstMap.BeatmapInfo.Metadata; - } - public override void Dispose() { archive.Dispose(); diff --git a/osu.Game/Beatmaps/Samples/SampleBank.cs b/osu.Game/Beatmaps/Samples/SampleBank.cs deleted file mode 100644 index 2154713cff..0000000000 --- a/osu.Game/Beatmaps/Samples/SampleBank.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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 -{ - public enum SampleBank - { - Default = 0, - Custom1 = 1, - Custom2 = 2 - } -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Samples/SampleInfo.cs b/osu.Game/Beatmaps/Samples/SampleInfo.cs deleted file mode 100644 index 5f9572c871..0000000000 --- a/osu.Game/Beatmaps/Samples/SampleInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// 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 -{ - public class SampleInfo - { - public SampleBank Bank; - public SampleSet Set; - } -} diff --git a/osu.Game/Beatmaps/Samples/SampleSet.cs b/osu.Game/Beatmaps/Samples/SampleSet.cs deleted file mode 100644 index 72f97d9e0e..0000000000 --- a/osu.Game/Beatmaps/Samples/SampleSet.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 -{ - public enum SampleSet - { - None = 0, - Normal = 1, - Soft = 2, - Drum = 3 - } -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Samples/SampleType.cs b/osu.Game/Beatmaps/Samples/SampleType.cs deleted file mode 100644 index 0a18a65201..0000000000 --- a/osu.Game/Beatmaps/Samples/SampleType.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Beatmaps.Samples -{ - [Flags] - public enum SampleType - { - None = 0, - Normal = 1, - Whistle = 2, - Finish = 4, - Clap = 8 - }; -} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Timing/ControlPoint.cs b/osu.Game/Beatmaps/Timing/ControlPoint.cs index 40320a88d7..ea152ccb39 100644 --- a/osu.Game/Beatmaps/Timing/ControlPoint.cs +++ b/osu.Game/Beatmaps/Timing/ControlPoint.cs @@ -5,18 +5,16 @@ namespace osu.Game.Beatmaps.Timing { public class ControlPoint { - public static ControlPoint Default = new ControlPoint - { - BeatLength = 500, - TimingChange = true, - }; - + public string SampleBank; + public int SampleVolume; public TimeSignatures TimeSignature; public double Time; - public double BeatLength; - public double VelocityAdjustment; - public bool TimingChange; + public double BeatLength = 500; + public double SpeedMultiplier = 1; + public bool TimingChange = true; public bool KiaiMode; public bool OmitFirstBarLine; + + public ControlPoint Clone() => (ControlPoint)MemberwiseClone(); } } diff --git a/osu.Game/Beatmaps/Timing/SampleChange.cs b/osu.Game/Beatmaps/Timing/SampleChange.cs deleted file mode 100644 index 2946fd749b..0000000000 --- a/osu.Game/Beatmaps/Timing/SampleChange.cs +++ /dev/null @@ -1,12 +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.Beatmaps.Samples; - -namespace osu.Game.Beatmaps.Timing -{ - public class SampleChange : ControlPoint - { - public SampleInfo Sample; - } -} diff --git a/osu.Game/Beatmaps/Timing/TimingChange.cs b/osu.Game/Beatmaps/Timing/TimingChange.cs deleted file mode 100644 index b759fcd01c..0000000000 --- a/osu.Game/Beatmaps/Timing/TimingChange.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Timing -{ - internal class TimingChange : ControlPoint - { - public TimingChange(double beatLength) - { - BeatLength = beatLength; - } - - public double BPM => 60000 / BeatLength; - } -} diff --git a/osu.Game/Beatmaps/Timing/TimingInfo.cs b/osu.Game/Beatmaps/Timing/TimingInfo.cs index 0e47ba983b..19cb0816ba 100644 --- a/osu.Game/Beatmaps/Timing/TimingInfo.cs +++ b/osu.Game/Beatmaps/Timing/TimingInfo.cs @@ -10,8 +10,8 @@ namespace osu.Game.Beatmaps.Timing { public readonly List ControlPoints = new List(); - public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength; - public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength; + public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength; + public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength; public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time); public double BPMAt(double time) @@ -20,23 +20,23 @@ namespace osu.Game.Beatmaps.Timing } /// - /// Finds the BPM multiplier at a time. + /// Finds the speed multiplier at a time. /// - /// The time to find the BPM multiplier at. - /// The BPM multiplier. - public double BPMMultiplierAt(double time) + /// The time to find the speed multiplier at. + /// The speed multiplier. + public double SpeedMultiplierAt(double time) { ControlPoint overridePoint; ControlPoint timingPoint = TimingPointAt(time, out overridePoint); - return overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1; + return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1; } /// - /// Finds the beat length at a time. + /// Finds the beat length at a time. This is expressed in milliseconds. /// /// The time to find the beat length at. - /// The beat length in milliseconds. + /// The beat length. public double BeatLengthAt(double time) { ControlPoint overridePoint; @@ -45,32 +45,6 @@ namespace osu.Game.Beatmaps.Timing return timingPoint.BeatLength; } - /// - /// Finds the beat velocity at a time. - /// - /// The time to find the velocity at. - /// The velocity. - public double BeatVelocityAt(double time) - { - ControlPoint overridePoint; - ControlPoint timingPoint = TimingPointAt(time, out overridePoint); - - return overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1; - } - - /// - /// Finds the beat length at a time. - /// - /// The time to find the beat length at. - /// The beat length in positional length units. - public double BeatDistanceAt(double time) - { - ControlPoint overridePoint; - ControlPoint timingPoint = TimingPointAt(time, out overridePoint); - - return (timingPoint?.BeatLength ?? 1) * (overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1); - } - /// /// Finds the timing point at a time. /// @@ -100,23 +74,7 @@ namespace osu.Game.Beatmaps.Timing else break; } - return timingPoint ?? ControlPoint.Default; - } - - /// - /// Finds the slider velocity at a time. - /// - /// The time to find the slider velocity at. - /// The slider velocity in milliseconds. - public double SliderVelocityAt(double time) - { - const double base_scoring_distance = 100; - - double beatDistance = BeatDistanceAt(time); - - if (beatDistance > 0) - return base_scoring_distance / beatDistance * 1000; - return base_scoring_distance; + return timingPoint ?? new ControlPoint(); } } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 74c8866596..5bea1d0986 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps protected abstract Beatmap GetBeatmap(); protected abstract Texture GetBackground(); protected abstract Track GetTrack(); - + private Beatmap beatmap; private readonly object beatmapLock = new object(); public Beatmap Beatmap @@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps } } } - + private readonly object backgroundLock = new object(); private Texture background; public Texture Background @@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps if (track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) other.track = track; } - + public virtual void Dispose() { track?.Dispose(); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6190678e1e..e2f33479c0 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -13,7 +13,7 @@ namespace osu.Game.Configuration protected override void InitialiseDefaults() { #pragma warning disable CS0612 // Type or member is obsolete - + Set(OsuConfig.Username, string.Empty); Set(OsuConfig.Token, string.Empty); @@ -31,10 +31,11 @@ namespace osu.Game.Configuration Set(OsuConfig.MouseDisableWheel, false); Set(OsuConfig.SnakingInSliders, true); - Set(OsuConfig.SnakingOutSliders, false); + Set(OsuConfig.SnakingOutSliders, true); Set(OsuConfig.MenuParallax, true); + Set(OsuConfig.ShowInterface, true); Set(OsuConfig.KeyOverlay, false); //todo: implement all settings below this line (remove the Disabled set when doing so). @@ -45,6 +46,7 @@ namespace osu.Game.Configuration Set(OsuConfig.AutomaticDownload, true).Disabled = true; Set(OsuConfig.AutomaticDownloadNoVideo, false).Disabled = true; Set(OsuConfig.BlockNonFriendPM, false).Disabled = true; + Set(OsuConfig.Bloom, false).Disabled = true; Set(OsuConfig.BloomSoftening, false).Disabled = true; Set(OsuConfig.BossKeyFirstActivation, true).Disabled = true; Set(OsuConfig.ChatAudibleHighlight, true).Disabled = true; @@ -89,7 +91,6 @@ namespace osu.Game.Configuration Set(OsuConfig.LastVersionPermissionsFailed, string.Empty).Disabled = true; Set(OsuConfig.LoadSubmittedThread, true).Disabled = true; Set(OsuConfig.LobbyPlayMode, -1).Disabled = true; - Set(OsuConfig.ShowInterface, true).Disabled = true; Set(OsuConfig.ShowInterfaceDuringRelax, false).Disabled = true; Set(OsuConfig.LobbyShowExistingOnly, false).Disabled = true; Set(OsuConfig.LobbyShowFriendsOnly, false).Disabled = true; @@ -187,10 +188,6 @@ namespace osu.Game.Configuration #pragma warning restore CS0612 // Type or member is obsolete } - //todo: make a UnicodeString class/struct rather than requiring this helper method. - public string GetUnicodeString(string nonunicode, string unicode) - => Get(OsuConfig.ShowUnicode) ? unicode ?? nonunicode : nonunicode ?? unicode; - public OsuConfigManager(Storage storage) : base(storage) { } diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index dfc916a136..41ddd8df39 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -175,7 +175,10 @@ namespace osu.Game.Database BeatmapMetadata metadata; using (var reader = ArchiveReader.GetReader(storage, path)) - metadata = reader.ReadMetadata(); + { + using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0]))) + metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; + } if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader { diff --git a/osu.Game/Database/BaseDifficulty.cs b/osu.Game/Database/BeatmapDifficulty.cs similarity index 78% rename from osu.Game/Database/BaseDifficulty.cs rename to osu.Game/Database/BeatmapDifficulty.cs index 3db8b29644..7c9f47e7b6 100644 --- a/osu.Game/Database/BaseDifficulty.cs +++ b/osu.Game/Database/BeatmapDifficulty.cs @@ -9,12 +9,12 @@ namespace osu.Game.Database { [PrimaryKey, AutoIncrement] public int ID { get; set; } - public float DrainRate { get; set; } - public float CircleSize { get; set; } - public float OverallDifficulty { get; set; } - public float ApproachRate { get; set; } - public float SliderMultiplier { get; set; } - public float SliderTickRate { get; set; } + public float DrainRate { get; set; } = 5; + public float CircleSize { get; set; } = 5; + public float OverallDifficulty { get; set; } = 5; + public float ApproachRate { get; set; } = 5; + public float SliderMultiplier { get; set; } = 1; + public float SliderTickRate { get; set; } = 1; /// /// Maps a difficulty value [0, 10] to a two-piece linear range of values. diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index cda9cba70c..3e84825919 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -1,7 +1,8 @@ // 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 Newtonsoft.Json; +using osu.Game.IO.Serialization; using osu.Game.Modes; using SQLite.Net.Attributes; using SQLiteNetExtensions.Attributes; @@ -10,11 +11,14 @@ using System.Linq; namespace osu.Game.Database { - public class BeatmapInfo : IEquatable + public class BeatmapInfo : IEquatable, IJsonSerializable { [PrimaryKey, AutoIncrement] public int ID { get; set; } + //TODO: should be in database + public int BeatmapVersion; + public int? OnlineBeatmapID { get; set; } public int? OnlineBeatmapSetID { get; set; } @@ -37,14 +41,17 @@ namespace osu.Game.Database [OneToOne(CascadeOperations = CascadeOperation.All)] public BeatmapDifficulty Difficulty { get; set; } + [Ignore] + public BeatmapMetrics Metrics { get; set; } + public string Path { get; set; } + [JsonProperty("file_md5")] public string Hash { get; set; } // General public int AudioLeadIn { get; set; } public bool Countdown { get; set; } - public SampleSet SampleSet { get; set; } public float StackLeniency { get; set; } public bool SpecialStyle { get; set; } public PlayMode Mode { get; set; } @@ -54,17 +61,13 @@ namespace osu.Game.Database // Editor // This bookmarks stuff is necessary because DB doesn't know how to store int[] public string StoredBookmarks { get; internal set; } + [Ignore] + [JsonIgnore] public int[] Bookmarks { - get - { - return StoredBookmarks.Split(',').Select(int.Parse).ToArray(); - } - set - { - StoredBookmarks = string.Join(",", value); - } + get { return StoredBookmarks.Split(',').Select(int.Parse).ToArray(); } + set { StoredBookmarks = string.Join(",", value); } } public double DistanceSpacing { get; set; } @@ -83,7 +86,7 @@ namespace osu.Game.Database } public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Path == other.BeatmapSet.Path && - (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; + BeatmapSet.Path == other.BeatmapSet.Path && + (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; } } diff --git a/osu.Game/Database/BeatmapMetrics.cs b/osu.Game/Database/BeatmapMetrics.cs new file mode 100644 index 0000000000..91320110d0 --- /dev/null +++ b/osu.Game/Database/BeatmapMetrics.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Database +{ + /// + /// Beatmap metrics based on acculumated online data from community plays. + /// + public class BeatmapMetrics + { + /// + /// Total vote counts of user ratings on a scale of 0..length. + /// + public IEnumerable Ratings { get; set; } + + /// + /// Points of failure on a relative time scale (usually 0..100). + /// + public IEnumerable Fails { get; set; } + + /// + /// Points of retry on a relative time scale (usually 0..100). + /// + public IEnumerable Retries { get; set; } + } +} diff --git a/osu.Game/Database/DatabaseWorkingBeatmap.cs b/osu.Game/Database/DatabaseWorkingBeatmap.cs index 1b37cf2fa0..9fb3bed1e7 100644 --- a/osu.Game/Database/DatabaseWorkingBeatmap.cs +++ b/osu.Game/Database/DatabaseWorkingBeatmap.cs @@ -34,12 +34,14 @@ namespace osu.Game.Database using (var stream = new StreamReader(reader.GetStream(BeatmapInfo.Path))) { decoder = BeatmapDecoder.GetDecoder(stream); - beatmap = decoder?.Decode(stream); + beatmap = decoder.Decode(stream); } - if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null) - using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) - decoder.Decode(stream, beatmap); + if (beatmap == null || !WithStoryboard || BeatmapSetInfo.StoryboardFile == null) + return beatmap; + + using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) + decoder.Decode(stream, beatmap); } return beatmap; diff --git a/osu.Game/Database/ScoreDatabase.cs b/osu.Game/Database/ScoreDatabase.cs index 5ce3ff273e..642bb4aff6 100644 --- a/osu.Game/Database/ScoreDatabase.cs +++ b/osu.Game/Database/ScoreDatabase.cs @@ -104,7 +104,7 @@ namespace osu.Game.Database score.Replay = score.CreateReplay(reader); } } - + return score; } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 8dff614f6c..830d0adc97 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -60,8 +60,8 @@ namespace osu.Game.Graphics.Backgrounds protected override void LoadComplete() { base.LoadComplete(); - for (int i = 0; i < aimTriangleCount; i++) - addTriangle(true); + + addTriangles(true); } private int aimTriangleCount => (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); @@ -83,8 +83,8 @@ namespace osu.Game.Graphics.Backgrounds t.Expire(); } - while (CreateNewTriangles && Children.Count() < aimTriangleCount) - addTriangle(false); + if (CreateNewTriangles) + addTriangles(false); } protected virtual Triangle CreateTriangle() @@ -113,12 +113,16 @@ namespace osu.Game.Graphics.Backgrounds protected virtual Color4 GetTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); - private void addTriangle(bool randomY) + private void addTriangles(bool randomY) { - var sprite = CreateTriangle(); - float triangleHeight = sprite.DrawHeight / DrawHeight; - sprite.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() * (1 + triangleHeight) - triangleHeight : 1); - Add(sprite); + int addCount = aimTriangleCount - Children.Count(); + for (int i = 0; i < addCount; i++) + { + var sprite = CreateTriangle(); + float triangleHeight = sprite.DrawHeight / DrawHeight; + sprite.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() * (1 + triangleHeight) - triangleHeight : 1); + Add(sprite); + } } } } diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index a143618807..8352656f8e 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -56,8 +56,8 @@ namespace osu.Game.Graphics.Containers { base.Update(); - if (parallaxEnabled) - { + if (parallaxEnabled) + { Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); content.Scale = new Vector2(1 + ParallaxAmount); diff --git a/osu.Game/Graphics/Cursor/CursorTrail.cs b/osu.Game/Graphics/Cursor/CursorTrail.cs index 4b5610e840..09d1b99d13 100644 --- a/osu.Game/Graphics/Cursor/CursorTrail.cs +++ b/osu.Game/Graphics/Cursor/CursorTrail.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.OpenGL.Buffers; using OpenTK.Graphics.ES30; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Colour; +using osu.Framework.Timing; namespace osu.Game.Graphics.Cursor { @@ -58,6 +59,9 @@ namespace osu.Game.Graphics.Cursor public CursorTrail() { + // as we are currently very dependent on having a running clock, let's make our own clock for the time being. + Clock = new FramedClock(); + AlwaysReceiveInput = true; RelativeSizeAxes = Axes.Both; @@ -231,4 +235,4 @@ namespace osu.Game.Graphics.Cursor } } } -} \ No newline at end of file +} diff --git a/osu.Game/Graphics/Cursor/GameplayCursor.cs b/osu.Game/Graphics/Cursor/GameplayCursor.cs index 3f94bbaddc..3f699219a4 100644 --- a/osu.Game/Graphics/Cursor/GameplayCursor.cs +++ b/osu.Game/Graphics/Cursor/GameplayCursor.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Configuration; -using System; namespace osu.Game.Graphics.Cursor { @@ -116,14 +115,9 @@ namespace osu.Game.Graphics.Cursor }; cursorScale = config.GetBindable(OsuConfig.GameplayCursorSize); - cursorScale.ValueChanged += scaleChanged; + cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)cursorScale); cursorScale.TriggerChange(); } - - private void scaleChanged(object sender, EventArgs e) - { - cursorContainer.Scale = new Vector2((float)cursorScale); - } } } } diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 361d0cc1ef..4d5e0848cb 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -119,14 +119,12 @@ namespace osu.Game.Graphics.Cursor protected override void PopIn() { ActiveCursor.FadeTo(1, 250, EasingTypes.OutQuint); - ActiveCursor.ScaleTo(1, 1000, EasingTypes.OutElastic); + ActiveCursor.ScaleTo(1, 400, EasingTypes.OutQuint); } protected override void PopOut() { - ActiveCursor.FadeTo(0, 1400, EasingTypes.OutQuint); - ActiveCursor.ScaleTo(1.1f, 100, EasingTypes.Out); - ActiveCursor.Delay(100); + ActiveCursor.FadeTo(0, 900, EasingTypes.OutQuint); ActiveCursor.ScaleTo(0, 500, EasingTypes.In); } @@ -175,15 +173,10 @@ namespace osu.Game.Graphics.Cursor }; cursorScale = config.GetBindable(OsuConfig.MenuCursorSize); - cursorScale.ValueChanged += scaleChanged; + cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)newScale); + cursorScale.ValueChanged += newScale => Tooltip.Y = cursorContainer.Height * (float)newScale; cursorScale.TriggerChange(); } - - private void scaleChanged(object sender, EventArgs e) - { - cursorContainer.Scale = new Vector2((float)cursorScale); - Tooltip.Y = cursorContainer.Height * (float)cursorScale; - } } } } diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index f959bc8760..e4647f22fd 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics /// The tween easing. public static void FadeAccent(this IHasAccentColour accentedDrawable, Color4 newColour, double duration = 0, EasingTypes easing = EasingTypes.None) { - accentedDrawable.TransformTo(accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent()); + accentedDrawable.TransformTo(() => accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent()); } } } diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs new file mode 100644 index 0000000000..76b75f1084 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -0,0 +1,137 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using System; + +namespace osu.Game.Graphics.UserInterface +{ + public class Bar : Container, IHasAccentColour + { + private readonly Box background; + private readonly Box bar; + + private const int resize_duration = 250; + + private const EasingTypes easing = EasingTypes.InOutCubic; + + private float length; + /// + /// Length of the bar, ranges from 0 to 1 + /// + public float Length + { + get + { + return length; + } + set + { + length = MathHelper.Clamp(value, 0, 1); + updateBarLength(); + } + } + + public Color4 BackgroundColour + { + get + { + return background.Colour; + } + set + { + background.Colour = value; + } + } + + public Color4 AccentColour + { + get + { + return bar.Colour; + } + set + { + bar.Colour = value; + } + } + + private BarDirection direction = BarDirection.LeftToRight; + public BarDirection Direction + { + get + { + return direction; + } + set + { + direction = value; + updateBarLength(); + } + } + + public Bar() + { + Children = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0,0,0,0) + }, + bar = new Box + { + RelativeSizeAxes = Axes.Both, + Width = 0, + }, + }; + } + + private void updateBarLength() + { + switch (direction) + { + case BarDirection.LeftToRight: + case BarDirection.RightToLeft: + bar.ResizeTo(new Vector2(length, 1), resize_duration, easing); + break; + + case BarDirection.TopToBottom: + case BarDirection.BottomToTop: + bar.ResizeTo(new Vector2(1, length), resize_duration, easing); + break; + } + + switch (direction) + { + case BarDirection.LeftToRight: + case BarDirection.TopToBottom: + bar.Anchor = Anchor.TopLeft; + bar.Origin = Anchor.TopLeft; + break; + + case BarDirection.RightToLeft: + case BarDirection.BottomToTop: + bar.Anchor = Anchor.BottomRight; + bar.Origin = Anchor.BottomRight; + break; + } + } + } + + [Flags] + public enum BarDirection + { + LeftToRight = 1 << 0, + RightToLeft = 1 << 1, + TopToBottom = 1 << 2, + BottomToTop = 1 << 3, + + Vertical = TopToBottom | BottomToTop, + Horizontal = LeftToRight | RightToLeft, + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs new file mode 100644 index 0000000000..d0965a1861 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Graphics.UserInterface +{ + public class BarGraph : FillFlowContainer + { + /// + /// Manually sets the max value, if null is instead used + /// + public float? MaxValue { get; set; } + + private BarDirection direction = BarDirection.BottomToTop; + public new BarDirection Direction + { + get + { + return direction; + } + set + { + direction = value; + base.Direction = (direction & BarDirection.Horizontal) > 0 ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) + { + bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / Children.Count()) : new Vector2(1.0f / Children.Count(), 1); + bar.Direction = direction; + } + } + } + + /// + /// A list of floats that defines the length of each + /// + public IEnumerable Values + { + set + { + List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) + if (bar.Bar != null) + { + bar.Bar.Length = bar.Value / (MaxValue ?? value.Max()); + bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1); + } + else + Add(new Bar + { + RelativeSizeAxes = Axes.Both, + Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1), + Length = bar.Value / (MaxValue ?? value.Max()), + Direction = Direction, + }); + //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards + Remove(Children.Where((bar, index) => index >= value.Count()).ToList()); + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index e150c7dc07..82ede8f079 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -3,8 +3,8 @@ using OpenTK; using OpenTK.Graphics; -using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -12,18 +12,18 @@ using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public class Nub : CircularContainer, IStateful + public class Nub : CircularContainer, IHasCurrentValue { public const float COLLAPSED_SIZE = 20; public const float EXPANDED_SIZE = 40; - private readonly Box fill; - private const float border_width = 3; private Color4 glowingColour, idleColour; public Nub() { + Box fill; + Size = new Vector2(COLLAPSED_SIZE, 12); BorderColour = Color4.White; @@ -40,6 +40,14 @@ namespace osu.Game.Graphics.UserInterface AlwaysPresent = true, }, }; + + Current.ValueChanged += newValue => + { + if (newValue) + fill.FadeIn(200, EasingTypes.OutQuint); + else + fill.FadeTo(0.01f, 200, EasingTypes.OutQuint); //todo: remove once we figure why containers aren't drawing at all times + }; } [BackgroundDependencyLoader] @@ -84,28 +92,6 @@ namespace osu.Game.Graphics.UserInterface } } - private CheckboxState state; - - public CheckboxState State - { - get - { - return state; - } - set - { - state = value; - - switch (state) - { - case CheckboxState.Checked: - fill.FadeIn(200, EasingTypes.OutQuint); - break; - case CheckboxState.Unchecked: - fill.FadeTo(0.01f, 200, EasingTypes.OutQuint); //todo: remove once we figure why containers aren't drawing at all times - break; - } - } - } + public Bindable Current { get; } = new Bindable(); } } diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index fc44d80ea6..d339388aa5 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -1,7 +1,6 @@ // 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.Audio; using osu.Framework.Audio.Sample; @@ -24,18 +23,9 @@ namespace osu.Game.Graphics.UserInterface { set { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; bindable = value; - if (bindable != null) - { - bool state = State == CheckboxState.Checked; - if (state != bindable.Value) - State = bindable.Value ? CheckboxState.Checked : CheckboxState.Unchecked; - bindable.ValueChanged += bindableValueChanged; - } - - if (bindable?.Disabled ?? true) + Current.BindTo(bindable); + if (value?.Disabled ?? true) Alpha = 0.3f; } } @@ -84,18 +74,16 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Right = 5 }, } }; - } - private void bindableValueChanged(object sender, EventArgs e) - { - State = bindable.Value ? CheckboxState.Checked : CheckboxState.Unchecked; - } + nub.Current.BindTo(Current); - protected override void Dispose(bool isDisposing) - { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; - base.Dispose(isDisposing); + Current.ValueChanged += newValue => + { + if (newValue) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + }; } protected override bool OnHover(InputState state) @@ -118,23 +106,5 @@ namespace osu.Game.Graphics.UserInterface sampleChecked = audio.Sample.Get(@"Checkbox/check-on"); sampleUnchecked = audio.Sample.Get(@"Checkbox/check-off"); } - - protected override void OnChecked() - { - sampleChecked?.Play(); - nub.State = CheckboxState.Checked; - - if (bindable != null) - bindable.Value = true; - } - - protected override void OnUnchecked() - { - sampleUnchecked?.Play(); - nub.State = CheckboxState.Unchecked; - - if (bindable != null) - bindable.Value = false; - } } } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 3466fb1a60..9bb0d15545 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface private class OsuDropdownMenuItem : DropdownMenuItem { - public OsuDropdownMenuItem(string text, T value) : base(text, value) + public OsuDropdownMenuItem(string text, T current) : base(text, current) { Foreground.Padding = new MarginPadding(2); diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 078c8564d7..180cb88707 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -50,7 +49,6 @@ namespace osu.Game.Graphics.UserInterface nub = new Nub { Origin = Anchor.TopCentre, - State = CheckboxState.Unchecked, Expanded = true, } }; @@ -64,15 +62,6 @@ namespace osu.Game.Graphics.UserInterface rightBox.Colour = colours.Pink; } - private void playSample() - { - if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) - return; - lastSampleTime = Clock.CurrentTime; - sample.Frequency.Value = 1 + NormalizedValue * 0.2f; - sample.Play(); - } - protected override bool OnHover(InputState state) { nub.Glowing = true; @@ -85,37 +74,39 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(state); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + protected override void OnUserChange() { - if (args.Key == Key.Left || args.Key == Key.Right) - playSample(); - return base.OnKeyDown(state, args); + base.OnUserChange(); + playSample(); + } + + private void playSample() + { + if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) + return; + lastSampleTime = Clock.CurrentTime; + sample.Frequency.Value = 1 + NormalizedValue * 0.2f; + + if (NormalizedValue == 0) + sample.Frequency.Value -= 0.4f; + else if (NormalizedValue == 1) + sample.Frequency.Value += 0.4f; + + sample.Play(); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - nub.State = CheckboxState.Checked; + nub.Current.Value = true; return base.OnMouseDown(state, args); } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - nub.State = CheckboxState.Unchecked; + nub.Current.Value = false; return base.OnMouseUp(state, args); } - protected override bool OnClick(InputState state) - { - playSample(); - return base.OnClick(state); - } - - protected override bool OnDrag(InputState state) - { - playSample(); - return base.OnDrag(state); - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 6d17d79ca1..242a9a8f6a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface { protected override Dropdown CreateDropdown() => new OsuTabDropdown(); - protected override TabItem CreateTabItem(T value) => new OsuTabItem { Value = value }; + protected override TabItem CreateTabItem(T value) => new OsuTabItem(value); protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || Dropdown.Contains(screenSpacePos); @@ -75,16 +75,6 @@ namespace osu.Game.Graphics.UserInterface } } - public new T Value - { - get { return base.Value; } - set - { - base.Value = value; - text.Text = (value as Enum)?.GetDescription(); - } - } - public override bool Active { get { return base.Active; } @@ -134,30 +124,31 @@ namespace osu.Game.Graphics.UserInterface AccentColour = colours.Blue; } - public OsuTabItem() + public OsuTabItem(T value) : base(value) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; Children = new Drawable[] { - text = new OsuSpriteText - { - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - TextSize = 14, - Font = @"Exo2.0-Bold", // Font should only turn bold when active? - }, - box = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Alpha = 0, - Colour = Color4.White, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - } + text = new OsuSpriteText + { + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = (value as Enum)?.GetDescription(), + TextSize = 14, + Font = @"Exo2.0-Bold", // Font should only turn bold when active? + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } }; } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckBox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs similarity index 84% rename from osu.Game/Graphics/UserInterface/OsuTabControlCheckBox.cs rename to osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 5914d0ba4c..f732916889 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -24,8 +23,6 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteText text; private readonly TextAwesome icon; - public event EventHandler Action; - private Color4? accentColour; public Color4 AccentColour { @@ -34,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface { accentColour = value; - if (State != CheckboxState.Checked) + if (Current) { text.Colour = AccentColour; icon.Colour = AccentColour; @@ -48,20 +45,6 @@ namespace osu.Game.Graphics.UserInterface set { text.Text = value; } } - protected override void OnChecked() - { - fadeIn(); - icon.Icon = FontAwesome.fa_check_circle_o; - Action?.Invoke(this, State); - } - - protected override void OnUnchecked() - { - fadeOut(); - icon.Icon = FontAwesome.fa_circle_o; - Action?.Invoke(this, State); - } - private const float transition_length = 500; private void fadeIn() @@ -84,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(InputState state) { - if (State == CheckboxState.Unchecked) + if (!Current) fadeOut(); base.OnHoverLost(state); @@ -134,6 +117,20 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.BottomLeft, } }; + + Current.ValueChanged += v => + { + if (v) + { + fadeIn(); + icon.Icon = FontAwesome.fa_check_circle_o; + } + else + { + fadeOut(); + icon.Icon = FontAwesome.fa_circle_o; + } + }; } } } diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index a4f6092d66..869ee37e11 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -98,21 +98,16 @@ namespace osu.Game.Graphics.UserInterface DisplayedCount = Current; - Current.ValueChanged += currentChanged; - } - - private void currentChanged(object sender, EventArgs e) - { - if (IsLoaded) - TransformCount(displayedCount, Current); + Current.ValueChanged += newValue => + { + if (IsLoaded) TransformCount(displayedCount, newValue); + }; } protected override void LoadComplete() { base.LoadComplete(); - Flush(false, TransformType); - DisplayedCountSpriteText.Text = FormatCount(Current); DisplayedCountSpriteText.Anchor = Anchor; DisplayedCountSpriteText.Origin = Origin; @@ -208,8 +203,8 @@ namespace osu.Game.Graphics.UserInterface ? GetProportionalDuration(currentValue, newValue) : RollingDuration; - transform.StartTime = Time.Current; - transform.EndTime = Time.Current + rollingTotalDuration; + transform.StartTime = TransformStartTime; + transform.EndTime = TransformStartTime + rollingTotalDuration; transform.StartValue = currentValue; transform.EndValue = newValue; transform.Easing = RollingEasing; diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 374385e351..4933c170e8 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -1,7 +1,6 @@ // 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.Framework.Graphics.Containers; using osu.Framework.Input; @@ -19,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface.Volume protected override bool HideOnEscape => false; - private void volumeChanged(object sender, EventArgs e) + private void volumeChanged(double newVolume) { Show(); schedulePopOut(); diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs new file mode 100644 index 0000000000..33d0801e47 --- /dev/null +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; + +namespace osu.Game.IO.Serialization +{ + public interface IJsonSerializable + { + } + + public static class JsonSerializableExtensions + { + public static string Serialize(this IJsonSerializable obj) + { + return JsonConvert.SerializeObject(obj); + } + + public static T Deserialize(string objString) + { + return JsonConvert.DeserializeObject(objString); + } + + public static T DeepClone(this IJsonSerializable obj) + { + return Deserialize(Serialize(obj)); + } + } +} diff --git a/osu.Game/Modes/Judgements/Judgement.cs b/osu.Game/Modes/Judgements/Judgement.cs index 677ec8bca9..1bf898d25c 100644 --- a/osu.Game/Modes/Judgements/Judgement.cs +++ b/osu.Game/Modes/Judgements/Judgement.cs @@ -17,10 +17,7 @@ namespace osu.Game.Modes.Judgements /// public double TimeOffset; - /// - /// The combo after this judgement was processed. - /// - public int ComboAtHit; + public virtual bool AffectsCombo => true; /// /// The string representation for the result achieved. diff --git a/osu.Game/Modes/Objects/BezierApproximator.cs b/osu.Game/Modes/Objects/BezierApproximator.cs index ee8e9b0e06..6688e6b2ce 100644 --- a/osu.Game/Modes/Objects/BezierApproximator.cs +++ b/osu.Game/Modes/Objects/BezierApproximator.cs @@ -109,7 +109,7 @@ namespace osu.Game.Modes.Objects // "toFlatten" contains all the curves which are not yet approximated well enough. // We use a stack to emulate recursion without the risk of running into a stack overflow. - // (More specifically, we iteratively and adaptively refine our curve with a + // (More specifically, we iteratively and adaptively refine our curve with a // Depth-first search // over the tree resulting from the subdivisions we make.) toFlatten.Push(controlPoints.ToArray()); diff --git a/osu.Game/Modes/Objects/CircularArcApproximator.cs b/osu.Game/Modes/Objects/CircularArcApproximator.cs index 310b923b0b..73db5fab29 100644 --- a/osu.Game/Modes/Objects/CircularArcApproximator.cs +++ b/osu.Game/Modes/Objects/CircularArcApproximator.cs @@ -66,7 +66,7 @@ namespace osu.Game.Modes.Objects double dir = 1; double thetaRange = thetaEnd - thetaStart; - // Decide in which direction to draw the circle, depending on which side of + // Decide in which direction to draw the circle, depending on which side of // AC B lies. Vector2 orthoAtoC = c - a; orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs index ed8269876e..e346a22813 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs @@ -7,11 +7,11 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Game.Beatmaps.Samples; using osu.Game.Modes.Judgements; using Container = osu.Framework.Graphics.Containers.Container; using osu.Game.Modes.Objects.Types; using OpenTK.Graphics; +using osu.Game.Audio; namespace osu.Game.Modes.Objects.Drawables { @@ -45,25 +45,29 @@ namespace osu.Game.Modes.Objects.Drawables UpdateState(state); if (State == ArmedState.Hit) - PlaySample(); + PlaySamples(); } } - protected SampleChannel Sample; + protected List Samples = new List(); - protected virtual void PlaySample() + protected void PlaySamples() { - Sample?.Play(); + Samples.ForEach(s => s?.Play()); + } + + [BackgroundDependencyLoader] + private void load() + { + //we may be setting a custom judgement in test cases or what not. + if (Judgement == null) + Judgement = CreateJudgement(); } protected override void LoadComplete() { base.LoadComplete(); - //we may be setting a custom judgement in test cases or what not. - if (Judgement == null) - Judgement = CreateJudgement(); - //force application of the state that was set before we loaded. UpdateState(State); } @@ -152,13 +156,16 @@ namespace osu.Game.Modes.Objects.Drawables [BackgroundDependencyLoader] private void load(AudioManager audio) { - SampleType type = HitObject.Sample?.Type ?? SampleType.None; - if (type == SampleType.None) - type = SampleType.Normal; + foreach (SampleInfo sample in HitObject.Samples) + { + SampleChannel channel = audio.Sample.Get($@"Gameplay/{sample.Bank}-{sample.Name}"); - SampleSet sampleSet = HitObject.Sample?.Set ?? SampleSet.Normal; + if (channel == null) + continue; - Sample = audio.Sample.Get($@"Gameplay/{sampleSet.ToString().ToLower()}-hit{type.ToString().ToLower()}"); + channel.Volume.Value = sample.Volume; + Samples.Add(channel); + } } private List> nestedHitObjects; diff --git a/osu.Game/Modes/Objects/HitObject.cs b/osu.Game/Modes/Objects/HitObject.cs index f2712d92ba..f362d6de63 100644 --- a/osu.Game/Modes/Objects/HitObject.cs +++ b/osu.Game/Modes/Objects/HitObject.cs @@ -1,9 +1,10 @@ // 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.Audio; using osu.Game.Beatmaps.Timing; using osu.Game.Database; +using System.Collections.Generic; namespace osu.Game.Modes.Objects { @@ -21,15 +22,31 @@ namespace osu.Game.Modes.Objects public double StartTime { get; set; } /// - /// The sample to be played when this HitObject is hit. + /// The samples to be played when this hit object is hit. /// - public HitSampleInfo Sample { get; set; } + public List Samples = new List(); /// /// Applies default values to this HitObject. /// /// The difficulty settings to use. /// The timing settings to use. - public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) { } + public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + { + ControlPoint overridePoint; + ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint); + + foreach (var sample in Samples) + { + if (sample.Volume == 0) + sample.Volume = (overridePoint ?? timingPoint)?.SampleVolume ?? 0; + + // If the bank is not assigned a name, assign it from the control point + if (!string.IsNullOrEmpty(sample.Bank)) + continue; + + sample.Bank = (overridePoint ?? timingPoint)?.SampleBank ?? @"normal"; + } + } } } diff --git a/osu.Game/Modes/Objects/LegacyHitObjectParser.cs b/osu.Game/Modes/Objects/LegacyHitObjectParser.cs index ccc5f18822..2316e5dc5d 100644 --- a/osu.Game/Modes/Objects/LegacyHitObjectParser.cs +++ b/osu.Game/Modes/Objects/LegacyHitObjectParser.cs @@ -2,12 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Beatmaps.Samples; using osu.Game.Modes.Objects.Types; using System; using System.Collections.Generic; using System.Globalization; using osu.Game.Modes.Objects.Legacy; +using osu.Game.Beatmaps.Formats; +using osu.Game.Audio; namespace osu.Game.Modes.Objects { @@ -20,6 +21,10 @@ namespace osu.Game.Modes.Objects bool combo = type.HasFlag(LegacyHitObjectType.NewCombo); type &= ~LegacyHitObjectType.NewCombo; + int sampleVolume = 0; + string normalSampleBank = null; + string addSampleBank = null; + HitObject result; if ((type & LegacyHitObjectType.Circle) > 0) @@ -29,6 +34,9 @@ namespace osu.Game.Modes.Objects Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])), NewCombo = combo }; + + if (split.Length > 5) + readCustomSampleBanks(split[5], ref normalSampleBank, ref addSampleBank, ref sampleVolume); } else if ((type & LegacyHitObjectType.Slider) > 0) { @@ -84,6 +92,9 @@ namespace osu.Game.Modes.Objects Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])), NewCombo = combo }; + + if (split.Length > 10) + readCustomSampleBanks(split[10], ref normalSampleBank, ref addSampleBank, ref sampleVolume); } else if ((type & LegacyHitObjectType.Spinner) > 0) { @@ -91,10 +102,17 @@ namespace osu.Game.Modes.Objects { EndTime = Convert.ToDouble(split[5], CultureInfo.InvariantCulture) }; + + if (split.Length > 6) + readCustomSampleBanks(split[6], ref normalSampleBank, ref addSampleBank, ref sampleVolume); } else if ((type & LegacyHitObjectType.Hold) > 0) { // Note: Hold is generated by BMS converts + + // Todo: Apparently end time is determined by samples?? + // Shouldn't need implementation until mania + result = new LegacyHold { Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])), @@ -105,15 +123,82 @@ namespace osu.Game.Modes.Objects throw new InvalidOperationException($@"Unknown hit object type {type}"); result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); - result.Sample = new HitSampleInfo - { - Type = (SampleType)int.Parse(split[4]), - Set = SampleSet.Soft, - }; - // TODO: "addition" field + var soundType = (LegacySoundType)int.Parse(split[4]); + + result.Samples.Add(new SampleInfo + { + Bank = normalSampleBank, + Name = SampleInfo.HIT_NORMAL, + Volume = sampleVolume + }); + + if ((soundType & LegacySoundType.Finish) > 0) + { + result.Samples.Add(new SampleInfo + { + Bank = addSampleBank, + Name = SampleInfo.HIT_FINISH, + Volume = sampleVolume + }); + } + + if ((soundType & LegacySoundType.Whistle) > 0) + { + result.Samples.Add(new SampleInfo + { + Bank = addSampleBank, + Name = SampleInfo.HIT_WHISTLE, + Volume = sampleVolume + }); + } + + if ((soundType & LegacySoundType.Clap) > 0) + { + result.Samples.Add(new SampleInfo + { + Bank = addSampleBank, + Name = SampleInfo.HIT_CLAP, + Volume = sampleVolume + }); + } return result; } + + private void readCustomSampleBanks(string str, ref string normalSampleBank, ref string addSampleBank, ref int sampleVolume) + { + if (string.IsNullOrEmpty(str)) + return; + + string[] split = str.Split(':'); + + var bank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]); + var addbank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]); + + // Let's not implement this for now, because this doesn't fit nicely into the bank structure + //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; + + string stringBank = bank.ToString().ToLower(); + if (stringBank == @"none") + stringBank = null; + string stringAddBank = addbank.ToString().ToLower(); + if (stringAddBank == @"none") + stringAddBank = null; + + normalSampleBank = stringBank; + addSampleBank = stringAddBank; + sampleVolume = split.Length > 3 ? int.Parse(split[3]) : 0; + } + + [Flags] + private enum LegacySoundType + { + None = 0, + Normal = 1, + Whistle = 2, + Finish = 4, + Clap = 8 + } } } diff --git a/osu.Game/Modes/Replays/FramedReplayInputHandler.cs b/osu.Game/Modes/Replays/FramedReplayInputHandler.cs index ae20ece515..0c1e140ce4 100644 --- a/osu.Game/Modes/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Modes/Replays/FramedReplayInputHandler.cs @@ -136,7 +136,7 @@ namespace osu.Game.Modes.Replays public ReplayMouseState(Vector2 position, IEnumerable list) { Position = position; - list.ForEach(b => PressedButtons.Add(b)); + list.ForEach(b => SetPressed(b, true)); } } @@ -148,4 +148,4 @@ namespace osu.Game.Modes.Replays } } } -} \ No newline at end of file +} diff --git a/osu.Game/Modes/Scoring/Score.cs b/osu.Game/Modes/Scoring/Score.cs index c998b11f77..b0c123f438 100644 --- a/osu.Game/Modes/Scoring/Score.cs +++ b/osu.Game/Modes/Scoring/Score.cs @@ -27,7 +27,24 @@ namespace osu.Game.Modes.Scoring public int Combo { get; set; } public Mod[] Mods { get; set; } - public User User { get; set; } + private User user; + + public User User + { + get + { + return user ?? new User + { + Username = LegacyUsername, + Id = LegacyUserID + }; + } + + set + { + user = value; + } + } [JsonProperty(@"replay_data")] public Replay Replay; @@ -38,10 +55,10 @@ namespace osu.Game.Modes.Scoring public long OnlineScoreID; [JsonProperty(@"username")] - public string Username; + public string LegacyUsername; [JsonProperty(@"user_id")] - public long UserID; + public long LegacyUserID; [JsonProperty(@"date")] public DateTime Date; diff --git a/osu.Game/Modes/Scoring/ScoreProcessor.cs b/osu.Game/Modes/Scoring/ScoreProcessor.cs index a64b4d4013..ba845b84dc 100644 --- a/osu.Game/Modes/Scoring/ScoreProcessor.cs +++ b/osu.Game/Modes/Scoring/ScoreProcessor.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Modes.Judgements; using osu.Game.Modes.Objects; using osu.Game.Modes.UI; +using osu.Game.Modes.Objects.Drawables; namespace osu.Game.Modes.Scoring { @@ -145,10 +146,21 @@ namespace osu.Game.Modes.Scoring if (!exists) { + if (judgement.AffectsCombo) + { + switch (judgement.Result) + { + case HitResult.Miss: + Combo.Value = 0; + break; + case HitResult.Hit: + Combo.Value++; + break; + } + } + Judgements.Add(judgement); OnNewJudgement(judgement); - - judgement.ComboAtHit = Combo.Value; } else OnJudgementChanged(judgement); diff --git a/osu.Game/Modes/UI/ComboCounter.cs b/osu.Game/Modes/UI/ComboCounter.cs index 3629634889..8c0327fa04 100644 --- a/osu.Game/Modes/UI/ComboCounter.cs +++ b/osu.Game/Modes/UI/ComboCounter.cs @@ -66,12 +66,7 @@ namespace osu.Game.Modes.UI TextSize = 80; - Current.ValueChanged += comboChanged; - } - - private void comboChanged(object sender, System.EventArgs e) - { - updateCount(Current.Value == 0); + Current.ValueChanged += newValue => updateCount(newValue == 0); } protected override void LoadComplete() diff --git a/osu.Game/Modes/UI/HealthDisplay.cs b/osu.Game/Modes/UI/HealthDisplay.cs index 3471f4bc3f..4c8d7e4ab8 100644 --- a/osu.Game/Modes/UI/HealthDisplay.cs +++ b/osu.Game/Modes/UI/HealthDisplay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Modes.UI protected HealthDisplay() { - Current.ValueChanged += (s, e) => SetHealth((float)Current); + Current.ValueChanged += newValue => SetHealth((float)newValue); } protected abstract void SetHealth(float value); diff --git a/osu.Game/Modes/UI/HitRenderer.cs b/osu.Game/Modes/UI/HitRenderer.cs index e36d2a101c..dd5eff5a95 100644 --- a/osu.Game/Modes/UI/HitRenderer.cs +++ b/osu.Game/Modes/UI/HitRenderer.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.Linq; using osu.Game.Modes.Replays; using osu.Game.Modes.Scoring; +using OpenTK; namespace osu.Game.Modes.UI { @@ -32,6 +33,11 @@ namespace osu.Game.Modes.UI /// public event Action OnAllJudged; + /// + /// Whether to apply adjustments to the child based on our own size. + /// + public bool AspectAdjust = true; + /// /// The input manager for this HitRenderer. /// @@ -42,6 +48,16 @@ namespace osu.Game.Modes.UI /// protected readonly KeyConversionInputManager KeyConversionInputManager; + /// + /// Whether we are currently providing the local user a gameplay cursor. + /// + public virtual bool ProvidingUserCursor => false; + + /// + /// Whether we have a replay loaded currently. + /// + public bool HasReplayLoaded => InputManager.ReplayInputHandler != null; + /// /// Whether all the HitObjects have been judged. /// @@ -157,6 +173,8 @@ namespace osu.Game.Modes.UI { public event Action OnJudgement; + public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor; + protected override Container Content => content; protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result != HitResult.None); @@ -207,6 +225,19 @@ namespace osu.Game.Modes.UI Playfield.PostProcess(); } + protected override void Update() + { + base.Update(); + + Playfield.Size = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One; + } + + /// + /// In some cases we want to apply changes to the relative size of our contained based on custom conditions. + /// + /// + protected virtual Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); //a sane default + /// /// Triggered when an object's Judgement is updated. /// diff --git a/osu.Game/Modes/UI/HudOverlay.cs b/osu.Game/Modes/UI/HudOverlay.cs index a6c54e7f3a..355b62bc57 100644 --- a/osu.Game/Modes/UI/HudOverlay.cs +++ b/osu.Game/Modes/UI/HudOverlay.cs @@ -8,13 +8,19 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play; -using System; using osu.Game.Modes.Scoring; +using osu.Framework.Input; +using OpenTK.Input; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; namespace osu.Game.Modes.UI { public abstract class HudOverlay : Container { + private const int duration = 100; + + private readonly Container content; public readonly KeyCounterCollection KeyCounter; public readonly ComboCounter ComboCounter; public readonly ScoreCounter ScoreCounter; @@ -22,6 +28,9 @@ namespace osu.Game.Modes.UI public readonly HealthDisplay HealthDisplay; private Bindable showKeyCounter; + private Bindable showHud; + + private static bool hasShownNotificationOnce; protected abstract KeyCounterCollection CreateKeyCounter(); protected abstract ComboCounter CreateComboCounter(); @@ -33,36 +42,57 @@ namespace osu.Game.Modes.UI { RelativeSizeAxes = Axes.Both; - Children = new Drawable[] + Add(content = new Container { - KeyCounter = CreateKeyCounter(), - ComboCounter = CreateComboCounter(), - ScoreCounter = CreateScoreCounter(), - AccuracyCounter = CreateAccuracyCounter(), - HealthDisplay = CreateHealthDisplay(), - }; + RelativeSizeAxes = Axes.Both, + + Children = new Drawable[] + { + KeyCounter = CreateKeyCounter(), + ComboCounter = CreateComboCounter(), + ScoreCounter = CreateScoreCounter(), + AccuracyCounter = CreateAccuracyCounter(), + HealthDisplay = CreateHealthDisplay(), + } + }); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config, NotificationManager notificationManager) { showKeyCounter = config.GetBindable(OsuConfig.KeyOverlay); - showKeyCounter.ValueChanged += visibilityChanged; + showKeyCounter.ValueChanged += keyCounterVisibility => + { + if (keyCounterVisibility) + KeyCounter.FadeIn(duration); + else + KeyCounter.FadeOut(duration); + }; showKeyCounter.TriggerChange(); - } - private void visibilityChanged(object sender, EventArgs e) - { - if (showKeyCounter) - KeyCounter.Show(); - else - KeyCounter.Hide(); + showHud = config.GetBindable(OsuConfig.ShowInterface); + showHud.ValueChanged += hudVisibility => + { + if (hudVisibility) + content.FadeIn(duration); + else + content.FadeOut(duration); + }; + showHud.TriggerChange(); + + if (!showHud && !hasShownNotificationOnce) + { + hasShownNotificationOnce = true; + + notificationManager?.Post(new SimpleNotification + { + Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." + }); + } } public void BindProcessor(ScoreProcessor processor) { - //bind processor bindables to combocounter, score display etc. - //TODO: these should be bindable binds, not events! ScoreCounter?.Current.BindTo(processor.TotalScore); AccuracyCounter?.Current.BindTo(processor.Accuracy); ComboCounter?.Current.BindTo(processor.Combo); @@ -73,5 +103,22 @@ namespace osu.Game.Modes.UI { hitRenderer.InputManager.Add(KeyCounter.GetReceptor()); } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) return false; + + if (state.Keyboard.ShiftPressed) + { + switch (args.Key) + { + case Key.Tab: + showHud.Value = !showHud.Value; + return true; + } + } + + return base.OnKeyDown(state, args); + } } } diff --git a/osu.Game/Modes/UI/ModIcon.cs b/osu.Game/Modes/UI/ModIcon.cs index 35459985c9..1e0aa89a41 100644 --- a/osu.Game/Modes/UI/ModIcon.cs +++ b/osu.Game/Modes/UI/ModIcon.cs @@ -26,7 +26,7 @@ namespace osu.Game.Modes.UI reapplySize(); } } - + public new Color4 Colour { get @@ -38,7 +38,7 @@ namespace osu.Game.Modes.UI background.Colour = value; } } - + public FontAwesome Icon { get diff --git a/osu.Game/Modes/UI/Playfield.cs b/osu.Game/Modes/UI/Playfield.cs index eff06ce80f..1e7cf6579c 100644 --- a/osu.Game/Modes/UI/Playfield.cs +++ b/osu.Game/Modes/UI/Playfield.cs @@ -1,6 +1,7 @@ // 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.Framework.Graphics.Containers; using osu.Game.Modes.Objects; @@ -22,6 +23,11 @@ namespace osu.Game.Modes.UI internal Container ScaledContent; + /// + /// Whether we are currently providing the local user a gameplay cursor. + /// + public virtual bool ProvidingUserCursor => false; + protected override Container Content => content; private readonly Container content; @@ -33,6 +39,9 @@ namespace osu.Game.Modes.UI { AlwaysReceiveInput = true; + // Default height since we force relative size axes + Size = Vector2.One; + AddInternal(ScaledContent = new ScaledContainer { CustomWidth = customWidth, @@ -58,6 +67,12 @@ namespace osu.Game.Modes.UI Add(HitObjects); } + public override Axes RelativeSizeAxes + { + get { return Axes.Both; } + set { throw new InvalidOperationException($@"{nameof(Playfield)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); } + } + /// /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. /// diff --git a/osu.Game/Modes/UI/StandardHudOverlay.cs b/osu.Game/Modes/UI/StandardHudOverlay.cs index f77191adf7..f07e421f00 100644 --- a/osu.Game/Modes/UI/StandardHudOverlay.cs +++ b/osu.Game/Modes/UI/StandardHudOverlay.cs @@ -40,6 +40,7 @@ namespace osu.Game.Modes.UI Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), + Y = - TwoLayerButton.SIZE_RETRACTED.Y, }; protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8ac86c5c67..ccea6ef458 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -150,7 +150,7 @@ namespace osu.Game } }); - (screenStack = new Loader()).LoadAsync(this, d => + LoadComponentAsync(screenStack = new Loader(), d => { screenStack.ModePushed += screenAdded; screenStack.Exited += screenRemoved; @@ -158,27 +158,27 @@ namespace osu.Game }); //overlay elements - (chat = new ChatOverlay { Depth = 0 }).LoadAsync(this, overlayContent.Add); - (options = new OptionsOverlay { Depth = -1 }).LoadAsync(this, overlayContent.Add); - (musicController = new MusicController + LoadComponentAsync(chat = new ChatOverlay { Depth = 0 }, overlayContent.Add); + LoadComponentAsync(options = new OptionsOverlay { Depth = -1 }, overlayContent.Add); + LoadComponentAsync(musicController = new MusicController { Depth = -2, Position = new Vector2(0, Toolbar.HEIGHT), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }).LoadAsync(this, overlayContent.Add); + }, overlayContent.Add); - (notificationManager = new NotificationManager + LoadComponentAsync(notificationManager = new NotificationManager { Depth = -2, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }).LoadAsync(this, overlayContent.Add); + }, overlayContent.Add); - (dialogOverlay = new DialogOverlay + LoadComponentAsync(dialogOverlay = new DialogOverlay { Depth = -4, - }).LoadAsync(this, overlayContent.Add); + }, overlayContent.Add); Logger.NewEntry += entry => { @@ -195,12 +195,12 @@ namespace osu.Game Dependencies.Cache(notificationManager); Dependencies.Cache(dialogOverlay); - (Toolbar = new Toolbar + LoadComponentAsync(Toolbar = new Toolbar { Depth = -3, OnHome = delegate { intro?.ChildScreen?.MakeCurrent(); }, OnPlayModeChange = m => PlayMode.Value = m, - }).LoadAsync(this, t => + }, t => { PlayMode.ValueChanged += delegate { Toolbar.SetGameMode(PlayMode.Value); }; PlayMode.TriggerChange(); @@ -307,6 +307,18 @@ namespace osu.Game return base.OnExiting(); } + /// + /// Use to programatically exit the game as if the user was triggering via alt-f4. + /// Will keep persisting until an exit occurs (exit may be blocked multiple times). + /// + public void GracefullyExit() + { + if (!OnExiting()) + Exit(); + else + Scheduler.AddDelayed(GracefullyExit, 2000); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -314,7 +326,7 @@ namespace osu.Game if (intro?.ChildScreen != null) intro.ChildScreen.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight }; - Cursor.State = currentScreen == null || currentScreen.HasLocalCursorDisplayed ? Visibility.Hidden : Visibility.Visible; + Cursor.State = currentScreen?.HasLocalCursorDisplayed == false ? Visibility.Visible : Visibility.Hidden; } private void screenAdded(Screen newScreen) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f454956de7..f95e8c3ac6 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -92,7 +92,10 @@ namespace osu.Game Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic")); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 0bb3d3dc71..fc12789b05 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays { var postText = sender.Text; - if (!string.IsNullOrEmpty(postText)) + if (!string.IsNullOrEmpty(postText) && api.LocalUser.Value != null) { //todo: actually send to server careChannels.FirstOrDefault()?.AddNewMessages(new[] diff --git a/osu.Game/Overlays/Dialog/PopupDialogOKButton.cs b/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs similarity index 100% rename from osu.Game/Overlays/Dialog/PopupDialogOKButton.cs rename to osu.Game/Overlays/Dialog/PopupDialogOkButton.cs diff --git a/osu.Game/Overlays/DragBar.cs b/osu.Game/Overlays/DragBar.cs index a9cf735171..53a01c9e9c 100644 --- a/osu.Game/Overlays/DragBar.cs +++ b/osu.Game/Overlays/DragBar.cs @@ -5,7 +5,9 @@ using System; 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; namespace osu.Game.Overlays { @@ -14,9 +16,10 @@ namespace osu.Game.Overlays private readonly Box fill; public Action SeekRequested; - private bool isDragging; - private bool enabled; + public bool IsSeeking { get; private set; } + + private bool enabled = true; public bool IsEnabled { get { return enabled; } @@ -46,9 +49,9 @@ namespace osu.Game.Overlays public void UpdatePosition(float position) { - if (isDragging || !IsEnabled) return; + if (IsSeeking || !IsEnabled) return; - fill.Width = position; + updatePosition(position); } private void seek(InputState state) @@ -56,7 +59,13 @@ namespace osu.Game.Overlays if (!IsEnabled) return; float seekLocation = state.Mouse.Position.X / DrawWidth; SeekRequested?.Invoke(seekLocation); - fill.Width = seekLocation; + updatePosition(seekLocation); + } + + private void updatePosition(float position) + { + position = MathHelper.Clamp(position, 0, 1); + fill.TransformTo(() => fill.Width, position, 200, EasingTypes.OutQuint, new TransformSeek()); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) @@ -71,12 +80,21 @@ namespace osu.Game.Overlays return true; } - protected override bool OnDragStart(InputState state) => isDragging = true; + protected override bool OnDragStart(InputState state) => IsSeeking = true; protected override bool OnDragEnd(InputState state) { - isDragging = false; + IsSeeking = false; return true; } + + private class TransformSeek : TransformFloat + { + public override void Apply(Drawable d) + { + base.Apply(d); + d.Width = CurrentValue; + } + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 0272438927..2b9f8e86a9 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -39,9 +39,9 @@ namespace osu.Game.Overlays.Mods public readonly Bindable PlayMode = new Bindable(); - private void modeChanged(object sender, EventArgs eventArgs) + private void modeChanged(PlayMode newMode) { - var ruleset = Ruleset.GetRuleset(PlayMode); + var ruleset = Ruleset.GetRuleset(newMode); foreach (ModSection section in modSectionsContainer.Children) section.Buttons = ruleset.GetModsFor(section.ModType).Select(m => new ModButton(m)).ToArray(); refreshSelectedMods(); @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Mods if (osu != null) PlayMode.BindTo(osu.PlayMode); PlayMode.ValueChanged += modeChanged; - modeChanged(null, null); + PlayMode.TriggerChange(); } protected override void PopOut() diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 2f8f0ab650..9d21a0341c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays { private Drawable currentBackground; private DragBar progress; - private TextAwesome playButton; + private Button playButton; private SpriteText title, artist; private List playList; @@ -46,6 +46,10 @@ namespace osu.Game.Overlays private Container dragContainer; + private const float progress_height = 10; + + private const float bottom_black_area_height = 55; + public MusicController() { Width = 400; @@ -78,8 +82,6 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(OsuGameBase game, OsuConfigManager config, BeatmapDatabase beatmaps, OsuColour colours) { - unicodeString = config.GetUnicodeString; - Children = new Drawable[] { dragContainer = new Container @@ -117,89 +119,64 @@ namespace osu.Game.Overlays Text = @"Nothing to play", Font = @"Exo2.0-BoldItalic" }, - new ClickableContainer + new Container { - AutoSizeAxes = Axes.Both, - Origin = Anchor.Centre, + Padding = new MarginPadding { Bottom = progress_height }, + Height = bottom_black_area_height, + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Position = new Vector2(0, -30), - Action = () => - { - if (current?.Track == null) return; - if (current.Track.IsRunning) - current.Track.Stop(); - else - current.Track.Start(); - }, Children = new Drawable[] { - playButton = new TextAwesome + new FillFlowContainer