diff --git a/.vscode/launch.json b/.vscode/launch.json index b981556649..0e07b0a067 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,6 +3,9 @@ "configurations": [ { "name": "Launch VisualTests", + "windows": { + "type": "clr" + }, "type": "mono", "request": "launch", "program": "${workspaceRoot}/osu.Desktop.VisualTests/bin/Debug/osu!.exe", @@ -11,10 +14,13 @@ "preLaunchTask": "build", "runtimeExecutable": null, "env": {}, - "externalConsole": false + "console": "internalConsole" }, { "name": "Launch Desktop", + "windows": { + "type": "clr" + }, "type": "mono", "request": "launch", "program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe", @@ -23,10 +29,15 @@ "preLaunchTask": "build", "runtimeExecutable": null, "env": {}, - "externalConsole": false + "console": "internalConsole" }, { "name": "Attach", + "windows": { + "type": "clr", + "request": "attach", + "processName": "osu!" + }, "type": "mono", "request": "attach", "address": "localhost", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0c0e79f7fb..5eaeaa9899 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,25 +2,50 @@ // 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": "msbuild", + "args": [ + "/property:GenerateFullPaths=true", + "/property:DebugType=portable" + ], + "windows": { + "args": [ + "/property:GenerateFullPaths=true", + "/property:DebugType=portable", + "/m" //parallel compiling support. doesn't work well with mono atm + ] + }, // Use the standard MS compiler pattern to detect errors, warnings and infos - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "isBuildCommand": true + }, + { + "taskName": "rebuild", + "isShellCommand": true, + "showOutput": "silent", + "command": "msbuild", + "args": [ + // Ask msbuild to generate full paths for file names. + "/property:GenerateFullPaths=true", + "/property:DebugType=portable", + "/target:Clean,Build" + ], + "windows": { + "args": [ + "/property:GenerateFullPaths=true", + "/property:DebugType=portable", + "/target:Clean,Build", + "/m" //parallel compiling support. doesn't work well with mono atm + ] + }, + // Use the standard MS compiler pattern to detect errors, warnings and infos + "problemMatcher": "$msCompile", + "isBuildCommand": true } ] } \ No newline at end of file diff --git a/README.md b/README.md index 885c7c7722..3306c9ab25 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) - - +# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [osu! on the web](https://osu.ppy.sh) | [dev chat](https://discord.gg/ppy) @@ -12,14 +10,14 @@ This is still heavily under development and is not intended for end-user use. Th # Requirements -- A desktop platform which can compile .NET 4.5. -- Visual Studio or MonoDevelop is recommended. +- A desktop platform which can compile .NET 4.5 (tested on macOS, linux and windows). We recommend using [Visual Studio Code](https://code.visualstudio.com/) (all platforms) or [Visual Studio Community Edition](https://www.visualstudio.com/) (windows only), both of which are free. +- Make sure you initialise and keep submodules up-to-date. # Contributing We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted. -Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://goo.gl/nFdoyI). If you're unsure of what you can help with, check out the [list](https://github.com/ppy/osu/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Abounty) of available issues with bounty. +Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://www.bountysource.com/teams/ppy). If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu-framework/issues). Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible. diff --git a/osu-framework b/osu-framework index 45e75163b2..fc93e11439 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 45e75163b272b7aa023afec7801ea079aba4ee69 +Subproject commit fc93e11439b8b391d9e01e208368d96ba85bfa26 diff --git a/osu-resources b/osu-resources index 0cba3cbc16..b90c4ed490 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 0cba3cbc167cfe94e07fe5b629c925e190be939e +Subproject commit b90c4ed490f76f2995662b3a8af3a32b8756a012 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/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index 7a3719a25b..1f9726b573 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + 6 AnyCPU @@ -68,9 +69,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 +120,7 @@ - - - + + diff --git a/osu.Desktop.VisualTests/Program.cs b/osu.Desktop.VisualTests/Program.cs index fe1cdfd7f0..03d1588b78 100644 --- a/osu.Desktop.VisualTests/Program.cs +++ b/osu.Desktop.VisualTests/Program.cs @@ -4,11 +4,6 @@ using System; using osu.Framework.Desktop; using osu.Framework.Platform; -using osu.Game.Modes; -using osu.Game.Modes.Catch; -using osu.Game.Modes.Mania; -using osu.Game.Modes.Osu; -using osu.Game.Modes.Taiko; namespace osu.Desktop.VisualTests { @@ -21,11 +16,6 @@ namespace osu.Desktop.VisualTests using (GameHost host = Host.GetSuitableHost(@"osu")) { - Ruleset.Register(new OsuRuleset()); - Ruleset.Register(new TaikoRuleset()); - Ruleset.Register(new ManiaRuleset()); - Ruleset.Register(new CatchRuleset()); - if (benchmark) host.Run(new AutomatedVisualTestGame()); else 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/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs index 3129cade63..cb15558ec3 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs @@ -9,21 +9,30 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Modes.Catch.UI; -using osu.Game.Modes.Mania.UI; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.UI; -using osu.Game.Modes.Taiko.UI; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Taiko.UI; using System.Collections.Generic; using osu.Desktop.VisualTests.Beatmaps; +using osu.Framework.Allocation; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseGamefield : TestCase { + private RulesetDatabase rulesets; + public override string Description => @"Showing hitobjects and what not."; + [BackgroundDependencyLoader] + private void load(RulesetDatabase rulesets) + { + this.rulesets = rulesets; + } + public override void Reset() { base.Reset(); @@ -36,7 +45,7 @@ namespace osu.Desktop.VisualTests.Tests objects.Add(new HitCircle { StartTime = time, - Position = new Vector2(RNG.Next(0, 512), RNG.Next(0, 384)), + Position = new Vector2(RNG.Next(0, (int)OsuPlayfield.BASE_SIZE.X), RNG.Next(0, (int)OsuPlayfield.BASE_SIZE.Y)), Scale = RNG.NextSingle(0.5f, 1.0f), }); @@ -49,6 +58,7 @@ namespace osu.Desktop.VisualTests.Tests BeatmapInfo = new BeatmapInfo { Difficulty = new BeatmapDifficulty(), + Ruleset = rulesets.Query().First(), Metadata = new BeatmapMetadata { Artist = @"Unknown", 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 99da7d1c73..dceb7a9cff 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -10,11 +10,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Judgements; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using System.Collections.Generic; namespace osu.Desktop.VisualTests.Tests @@ -62,15 +61,12 @@ namespace osu.Desktop.VisualTests.Tests add(new DrawableSlider(new Slider { StartTime = framedClock.CurrentTime + 600, - CurveObject = new CurvedHitObject + ControlPoints = new List { - ControlPoints = new List - { - new Vector2(-200, 0), - new Vector2(400, 0), - }, - Distance = 400 + new Vector2(-200, 0), + new Vector2(400, 0), }, + Distance = 400, Position = new Vector2(-200, 0), Velocity = 1, TickDistance = 100, @@ -99,6 +95,7 @@ namespace osu.Desktop.VisualTests.Tests AddToggleStep(@"auto", state => { auto = state; load(mode); }); + BasicSliderBar sliderBar; Add(new Container { Anchor = Anchor.TopRight, @@ -107,16 +104,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, - Value = 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 7e7782662b..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, - Value = bindable } } }); + sliderBar.Current.BindTo(bindable); + Add(kc); } private class TestSliderBar : SliderBar where T : struct diff --git a/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs b/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs index 44e52c237e..39010baf91 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseLeaderboard.cs @@ -4,9 +4,9 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Osu.Mods; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs b/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs index 7677682ac8..d1c137191f 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseModSelectOverlay.cs @@ -1,10 +1,11 @@ // 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.Graphics; using osu.Game.Overlays.Mods; using osu.Framework.Testing; -using osu.Game.Modes; +using osu.Game.Database; namespace osu.Desktop.VisualTests.Tests { @@ -13,6 +14,13 @@ namespace osu.Desktop.VisualTests.Tests public override string Description => @"Tests the mod select overlay"; private ModSelectOverlay modSelect; + private RulesetDatabase rulesets; + + [BackgroundDependencyLoader] + private void load(RulesetDatabase rulesets) + { + this.rulesets = rulesets; + } public override void Reset() { @@ -26,10 +34,9 @@ namespace osu.Desktop.VisualTests.Tests }); AddStep("Toggle", modSelect.ToggleVisibility); - AddStep("osu!", () => modSelect.PlayMode.Value = PlayMode.Osu); - AddStep("osu!taiko", () => modSelect.PlayMode.Value = PlayMode.Taiko); - AddStep("osu!catch", () => modSelect.PlayMode.Value = PlayMode.Catch); - AddStep("osu!mania", () => modSelect.PlayMode.Value = PlayMode.Mania); + + foreach (var ruleset in rulesets.AllRulesets) + AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset); } } } diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs index 1a43425dda..35eb6d0ff9 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs @@ -6,7 +6,6 @@ using osu.Desktop.VisualTests.Platform; using osu.Framework.Testing; using osu.Framework.MathUtils; using osu.Game.Database; -using osu.Game.Modes; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -20,13 +19,19 @@ namespace osu.Desktop.VisualTests.Tests public override string Description => @"with fake data"; + private RulesetDatabase rulesets; + public override void Reset() { base.Reset(); if (db == null) { storage = new TestStorage(@"TestCasePlaySongSelect"); - db = new BeatmapDatabase(storage); + + var backingDatabase = storage.GetDatabase(@"client"); + + rulesets = new RulesetDatabase(storage, backingDatabase); + db = new BeatmapDatabase(storage, backingDatabase, rulesets); var sets = new List(); @@ -72,7 +77,7 @@ namespace osu.Desktop.VisualTests.Tests new BeatmapInfo { OnlineBeatmapID = 1234 + i, - Mode = PlayMode.Osu, + Ruleset = rulesets.Query().First(), Path = "normal.osu", Version = "Normal", Difficulty = new BeatmapDifficulty @@ -83,7 +88,7 @@ namespace osu.Desktop.VisualTests.Tests new BeatmapInfo { OnlineBeatmapID = 1235 + i, - Mode = PlayMode.Osu, + Ruleset = rulesets.Query().First(), Path = "hard.osu", Version = "Hard", Difficulty = new BeatmapDifficulty @@ -94,7 +99,7 @@ namespace osu.Desktop.VisualTests.Tests new BeatmapInfo { OnlineBeatmapID = 1236 + i, - Mode = PlayMode.Osu, + Ruleset = rulesets.Query().First(), Path = "insane.osu", Version = "Insane", Difficulty = new BeatmapDifficulty diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs index f36889b02a..f28cdd6a7e 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs @@ -9,12 +9,12 @@ using osu.Game.Beatmaps; using OpenTK; using osu.Framework.Graphics.Sprites; using osu.Game.Database; -using osu.Game.Modes; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Osu.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Play; using OpenTK.Graphics; using osu.Desktop.VisualTests.Beatmaps; +using osu.Game.Rulesets.Osu.UI; namespace osu.Desktop.VisualTests.Tests { @@ -22,12 +22,14 @@ namespace osu.Desktop.VisualTests.Tests { protected Player Player; private BeatmapDatabase db; + private RulesetDatabase rulesets; public override string Description => @"Showing everything to play the game."; [BackgroundDependencyLoader] - private void load(BeatmapDatabase db) + private void load(BeatmapDatabase db, RulesetDatabase rulesets) { + this.rulesets = rulesets; this.db = db; } @@ -37,7 +39,7 @@ namespace osu.Desktop.VisualTests.Tests WorkingBeatmap beatmap = null; - var beatmapInfo = db.Query().FirstOrDefault(b => b.Mode == PlayMode.Osu); + var beatmapInfo = db.Query().FirstOrDefault(b => b.RulesetID == 0); if (beatmapInfo != null) beatmap = db.GetWorkingBeatmap(beatmapInfo); @@ -51,8 +53,8 @@ namespace osu.Desktop.VisualTests.Tests objects.Add(new HitCircle { StartTime = time, - Position = new Vector2(i % 4 == 0 || i % 4 == 2 ? 0 : 512, - i % 4 < 2 ? 0 : 384), + Position = new Vector2(i % 4 == 0 || i % 4 == 2 ? 0 : OsuPlayfield.BASE_SIZE.X, + i % 4 < 2 ? 0 : OsuPlayfield.BASE_SIZE.Y), NewCombo = i % 4 == 0 }); @@ -65,6 +67,7 @@ namespace osu.Desktop.VisualTests.Tests BeatmapInfo = new BeatmapInfo { Difficulty = new BeatmapDifficulty(), + Ruleset = rulesets.Query().First(), Metadata = new BeatmapMetadata { Artist = @"Unknown", @@ -83,10 +86,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 ffdca25bb3..e00a912278 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseReplay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseReplay.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Osu.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; namespace osu.Desktop.VisualTests.Tests diff --git a/osu.Desktop.VisualTests/Tests/TestCaseResults.cs b/osu.Desktop.VisualTests/Tests/TestCaseResults.cs new file mode 100644 index 0000000000..aa3a117667 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseResults.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Users; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseResults : TestCase + { + private BeatmapDatabase db; + + public override string Description => @"Results after playing."; + + [BackgroundDependencyLoader] + private void load(BeatmapDatabase db) + { + this.db = db; + } + + private WorkingBeatmap beatmap; + + public override void Reset() + { + base.Reset(); + + if (beatmap == null) + { + var beatmapInfo = db.Query().FirstOrDefault(b => b.RulesetID == 0); + if (beatmapInfo != null) + beatmap = db.GetWorkingBeatmap(beatmapInfo); + } + + base.Reset(); + + Add(new Results(new Score + { + TotalScore = 2845370, + Accuracy = 0.98, + MaxCombo = 123, + Rank = ScoreRank.A, + Date = DateTime.Now, + Statistics = new Dictionary() + { + { "300", 50 }, + { "100", 20 }, + { "50", 50 }, + { "x", 1 } + }, + User = new User + { + Username = "peppy", + } + }) + { + Beatmap = beatmap + }); + } + } +} diff --git a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs index f3cca16678..d8dac63980 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.UI; namespace osu.Desktop.VisualTests.Tests { diff --git a/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.cs b/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.cs new file mode 100644 index 0000000000..6d8aac1d09 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseSongProgress.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 System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Play; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseSongProgress : TestCase + { + public override string Description => @"With fake data"; + + private SongProgress progress; + private SongProgressGraph graph; + + public override void Reset() + { + base.Reset(); + + Add(progress = new SongProgress + { + RelativeSizeAxes = Axes.X, + AudioClock = new StopwatchClock(true), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + + Add(graph = new SongProgressGraph + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + + AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); + AddWaitStep(5); + AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); + AddWaitStep(2); + AddRepeatStep("New Values", displayNewValues, 5); + + displayNewValues(); + } + + private void displayNewValues() + { + List objects = new List(); + for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) + objects.Add(new HitObject { StartTime = i }); + + progress.Objects = objects; + graph.Objects = objects; + } + } +} diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs index 2d3969b822..b72abd1992 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs @@ -36,7 +36,7 @@ namespace osu.Desktop.VisualTests.Tests filter.PinItem(GroupMode.All); filter.PinItem(GroupMode.RecentlyPlayed); - filter.SelectedItem.ValueChanged += newFilter => + filter.Current.ValueChanged += newFilter => { text.Text = "Currently Selected: " + newFilter.ToString(); }; diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs index b3cb8c3457..d769071bd9 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoHitObjects.cs @@ -6,7 +6,7 @@ using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Desktop.VisualTests.Tests { diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index 88a037afee..259d0267db 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -1,30 +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.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.Drawables; -using osu.Game.Modes.Taiko.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.UI; +using System; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseTaikoPlayfield : TestCase { - public override string Description => "Taiko playfield"; + private const double default_duration = 300; + private const float scroll_time = 1000; - private TaikoPlayfield playfield; + public override string Description => "Taiko playfield"; protected override double TimePerAction => default_duration * 2; - private const double default_duration = 300; - - private const float scroll_time = 1000; + private readonly Random rng = new Random(1337); + private TaikoPlayfield playfield; + private Container playfieldContainer; public override void Reset() { @@ -41,15 +44,22 @@ namespace osu.Desktop.VisualTests.Tests 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)); var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; - Add(new Container + Add(playfieldContainer = new Container { - Clock = new FramedClock(rateAdjustClock), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Y = 200, + Height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT, + Clock = new FramedClock(rateAdjustClock), Children = new[] { playfield = new TaikoPlayfield() @@ -57,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() diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTooltip.cs b/osu.Desktop.VisualTests/Tests/TestCaseTooltip.cs new file mode 100644 index 0000000000..c536672314 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseTooltip.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Configuration; +using OpenTK; +using osu.Game.Graphics; + +namespace osu.Desktop.VisualTests.Tests +{ + internal class TestCaseTooltip : TestCase + { + public override string Description => "tests tooltips on various elements"; + + public override void Reset() + { + base.Reset(); + OsuSliderBar slider; + OsuSliderBar sliderDouble; + + const float width = 400; + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new TooltipTextContainer("text with a tooltip"), + new TooltipTextContainer("more text with another tooltip"), + new TooltipTextbox + { + Text = "a textbox with a tooltip", + Size = new Vector2(width,30), + }, + slider = new OsuSliderBar + { + Width = width, + }, + sliderDouble = new OsuSliderBar + { + Width = width, + }, + }, + }, + }; + + slider.Current.BindTo(new BindableInt(5) + { + MaxValue = 10, + MinValue = 0 + }); + + sliderDouble.Current.BindTo(new BindableDouble(0.5) + { + MaxValue = 1, + MinValue = 0 + }); + } + + private class TooltipTextContainer : Container, IHasTooltip + { + private readonly OsuSpriteText text; + + public string TooltipText => text.Text; + + public TooltipTextContainer(string tooltipText) + { + AutoSizeAxes = Axes.Both; + Children = new[] + { + text = new OsuSpriteText + { + Text = tooltipText, + } + }; + } + } + + private class TooltipTextbox : OsuTextBox, IHasTooltip + { + public string TooltipText => Text; + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index da068c5557..135e4596c7 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -61,6 +61,7 @@ false false false + 6 none @@ -83,22 +84,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 @@ -162,21 +161,21 @@ {d9a367c9-4c1a-489f-9b05-a0cea2b53b58} osu.Game.Resources - + {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Modes.Osu + osu.Game.Rulesets.Osu - + {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Modes.Catch + osu.Game.Rulesets.Catch - + {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Modes.Mania + osu.Game.Rulesets.Mania - + {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Modes.Taiko + osu.Game.Rulesets.Taiko {0d3fbf8a-7464-4cf7-8c90-3e7886df2d4d} @@ -187,8 +186,10 @@ + + @@ -197,16 +198,19 @@ + + + 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/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/Program.cs b/osu.Desktop/Program.cs index ddf58ac363..210f780078 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -7,11 +7,6 @@ using osu.Desktop.Beatmaps.IO; using osu.Framework.Desktop; using osu.Framework.Desktop.Platform; using osu.Game.IPC; -using osu.Game.Modes; -using osu.Game.Modes.Catch; -using osu.Game.Modes.Mania; -using osu.Game.Modes.Osu; -using osu.Game.Modes.Taiko; namespace osu.Desktop { @@ -41,11 +36,6 @@ namespace osu.Desktop } else { - Ruleset.Register(new OsuRuleset()); - Ruleset.Register(new TaikoRuleset()); - Ruleset.Register(new ManiaRuleset()); - Ruleset.Register(new CatchRuleset()); - host.Run(new OsuGameDesktop(args)); } return 0; diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index fbc342d695..4f66dfd3eb 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -63,6 +63,7 @@ false + 6 none @@ -124,8 +125,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 @@ -198,21 +198,21 @@ {d9a367c9-4c1a-489f-9b05-a0cea2b53b58} osu.Game.Resources - + {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Modes.Osu + osu.Game.Rulesets.Osu - + {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Modes.Catch + osu.Game.Rulesets.Catch - + {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Modes.Mania + osu.Game.Rulesets.Mania - + {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Modes.Taiko + osu.Game.Rulesets.Taiko {0d3fbf8a-7464-4cf7-8c90-3e7886df2d4d} 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/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Modes.Catch/Beatmaps/CatchBeatmapConverter.cs deleted file mode 100644 index 9791554f02..0000000000 --- a/osu.Game.Modes.Catch/Beatmaps/CatchBeatmapConverter.cs +++ /dev/null @@ -1,20 +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; -using osu.Game.Modes.Catch.Objects; -using System.Collections.Generic; - -namespace osu.Game.Modes.Catch.Beatmaps -{ - internal class CatchBeatmapConverter : IBeatmapConverter - { - public Beatmap Convert(Beatmap original) - { - return new Beatmap(original) - { - HitObjects = new List() // Todo: Convert HitObjects - }; - } - } -} diff --git a/osu.Game.Modes.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Modes.Catch/Beatmaps/CatchBeatmapProcessor.cs deleted file mode 100644 index ef585e2675..0000000000 --- a/osu.Game.Modes.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ /dev/null @@ -1,19 +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; -using osu.Game.Modes.Catch.Objects; - -namespace osu.Game.Modes.Catch.Beatmaps -{ - internal class CatchBeatmapProcessor : IBeatmapProcessor - { - public void SetDefaults(CatchBaseHit hitObject, Beatmap beatmap) - { - } - - public void PostProcess(Beatmap beatmap) - { - } - } -} diff --git a/osu.Game.Modes.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Modes.Mania/Beatmaps/ManiaBeatmapConverter.cs deleted file mode 100644 index 3ff210c1cc..0000000000 --- a/osu.Game.Modes.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ /dev/null @@ -1,20 +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; -using osu.Game.Modes.Mania.Objects; -using System.Collections.Generic; - -namespace osu.Game.Modes.Mania.Beatmaps -{ - internal class ManiaBeatmapConverter : IBeatmapConverter - { - public Beatmap Convert(Beatmap original) - { - return new Beatmap(original) - { - HitObjects = new List() // Todo: Implement - }; - } - } -} diff --git a/osu.Game.Modes.Mania/Beatmaps/ManiaBeatmapProcessor.cs b/osu.Game.Modes.Mania/Beatmaps/ManiaBeatmapProcessor.cs deleted file mode 100644 index 5e85a8f864..0000000000 --- a/osu.Game.Modes.Mania/Beatmaps/ManiaBeatmapProcessor.cs +++ /dev/null @@ -1,19 +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; -using osu.Game.Modes.Mania.Objects; - -namespace osu.Game.Modes.Mania.Beatmaps -{ - internal class ManiaBeatmapProcessor : IBeatmapProcessor - { - public void SetDefaults(ManiaBaseHit hitObject, Beatmap beatmap) - { - } - - public void PostProcess(Beatmap beatmap) - { - } - } -} diff --git a/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapProcessor.cs deleted file mode 100644 index 08c9d94141..0000000000 --- a/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ /dev/null @@ -1,32 +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; -using osu.Game.Modes.Osu.Objects; - -namespace osu.Game.Modes.Osu.Beatmaps -{ - internal class OsuBeatmapProcessor : IBeatmapProcessor - { - public void PostProcess(Beatmap beatmap) - { - if (beatmap.ComboColors.Count == 0) - return; - - int comboIndex = 0; - int colourIndex = 0; - - foreach (var obj in beatmap.HitObjects) - { - if (obj.NewCombo) - { - comboIndex = 0; - colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; - } - - obj.ComboIndex = comboIndex++; - obj.ComboColour = beatmap.ComboColors[colourIndex]; - } - } - } -} diff --git a/osu.Game.Modes.Osu/Scoring/OsuScore.cs b/osu.Game.Modes.Osu/Scoring/OsuScore.cs deleted file mode 100644 index a0a639a59e..0000000000 --- a/osu.Game.Modes.Osu/Scoring/OsuScore.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 - -using osu.Game.Modes.Scoring; - -namespace osu.Game.Modes.Osu.Scoring -{ - internal class OsuScore : Score - { - } -} diff --git a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs deleted file mode 100644 index 0bd587e8ea..0000000000 --- a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs +++ /dev/null @@ -1,61 +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.Osu.Judgements; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.UI; - -namespace osu.Game.Modes.Osu.Scoring -{ - internal class OsuScoreProcessor : ScoreProcessor - { - public OsuScoreProcessor() - { - } - - public OsuScoreProcessor(HitRenderer hitRenderer) - : base(hitRenderer) - { - } - - protected override void Reset() - { - base.Reset(); - - Health.Value = 1; - Accuracy.Value = 1; - } - - protected override void OnNewJudgement(OsuJudgement judgement) - { - if (judgement != null) - { - switch (judgement.Result) - { - case HitResult.Hit: - Combo.Value++; - Health.Value += 0.1f; - break; - case HitResult.Miss: - Combo.Value = 0; - Health.Value -= 0.2f; - break; - } - } - - int score = 0; - int maxScore = 0; - - foreach (var j in Judgements) - { - score += j.ScoreValue; - maxScore += j.MaxScoreValue; - } - - TotalScore.Value = score; - Accuracy.Value = (double)score / maxScore; - } - } -} diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapProcessor.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapProcessor.cs deleted file mode 100644 index 84bc470e55..0000000000 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapProcessor.cs +++ /dev/null @@ -1,19 +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; -using osu.Game.Modes.Taiko.Objects; - -namespace osu.Game.Modes.Taiko.Beatmaps -{ - internal class TaikoBeatmapProcessor : IBeatmapProcessor - { - public void SetDefaults(TaikoHitObject hitObject, Beatmap beatmap) - { - } - - public void PostProcess(Beatmap beatmap) - { - } - } -} diff --git a/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs deleted file mode 100644 index 93dfc3d651..0000000000 --- a/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs +++ /dev/null @@ -1,24 +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; -using osu.Game.Modes.Taiko.Beatmaps; -using osu.Game.Modes.Taiko.Objects; -using System.Collections.Generic; - -namespace osu.Game.Modes.Taiko -{ - public class TaikoDifficultyCalculator : DifficultyCalculator - { - public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap) - { - } - - protected override double CalculateInternal(Dictionary categoryDifficulty) - { - return 0; - } - - protected override IBeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); - } -} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs deleted file mode 100644 index 9e7eb571a1..0000000000 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ /dev/null @@ -1,212 +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.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.UI; -using OpenTK; -using OpenTK.Graphics; -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics.Primitives; -using System.Linq; -using osu.Game.Modes.Taiko.Objects.Drawables; - -namespace osu.Game.Modes.Taiko.UI -{ - public class TaikoPlayfield : Playfield - { - /// - /// The play field height. This is relative to the size of hit objects - /// such that the playfield is just a bit larger than strong hits. - /// - public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; - - /// - /// The offset from which the center of the hit target lies at. - /// - private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40; - - /// - /// The size of the left area of the playfield. This area contains the input drum. - /// - private const float left_area_size = 240; - - protected override Container Content => hitObjectContainer; - - private readonly Container hitExplosionContainer; - private readonly Container barLineContainer; - private readonly Container judgementContainer; - - private readonly Container hitObjectContainer; - private readonly Container topLevelHitContainer; - private readonly Container leftBackgroundContainer; - private readonly Container rightBackgroundContainer; - private readonly Box leftBackground; - private readonly Box rightBackground; - - public TaikoPlayfield() - { - RelativeSizeAxes = Axes.X; - Height = PLAYFIELD_HEIGHT; - - AddInternal(new Drawable[] - { - rightBackgroundContainer = new Container - { - RelativeSizeAxes = Axes.Both, - BorderThickness = 2, - Masking = true, - EdgeEffect = new EdgeEffect - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, - }, - Children = new Drawable[] - { - rightBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container - { - X = hit_target_offset, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - hitExplosionContainer = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), - 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.Both, - BlendingMode = BlendingMode.Additive - }, - }, - }, - } - }, - 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)), - }, - } - }, - topLevelHitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - leftBackgroundContainer.BorderColour = colours.Gray0; - leftBackground.Colour = colours.Gray1; - - rightBackgroundContainer.BorderColour = colours.Gray1; - rightBackground.Colour = colours.Gray0; - } - - public override void Add(DrawableHitObject h) - { - h.Depth = (float)h.HitObject.StartTime; - - base.Add(h); - - // Swells should be moved at the very top of the playfield when they reach the hit target - var swell = h as DrawableSwell; - if (swell != null) - swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); - } - - public void AddBarLine(DrawableBarLine barLine) - { - barLineContainer.Add(barLine); - } - - public override void OnJudgement(DrawableHitObject judgedObject) - { - bool wasHit = judgedObject.Judgement.Result == HitResult.Hit; - bool secondHit = judgedObject.Judgement.SecondHit; - - judgementContainer.Add(new DrawableTaikoJudgement(judgedObject.Judgement) - { - Anchor = wasHit ? Anchor.TopLeft : Anchor.CentreLeft, - Origin = wasHit ? Anchor.BottomCentre : Anchor.Centre, - RelativePositionAxes = Axes.X, - X = wasHit ? judgedObject.Position.X : 0, - }); - - if (!wasHit) - return; - - if (!secondHit) - { - if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell)) - { - // If we're far enough away from the left stage, we should bring outselves in front of it - topLevelHitContainer.Add(judgedObject.CreateProxy()); - } - - hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement)); - } - else - hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); - } - } -} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs new file mode 100644 index 0000000000..f9859cd244 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using System.Collections.Generic; +using System; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Catch.Beatmaps +{ + internal class CatchBeatmapConverter : BeatmapConverter + { + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; + + protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) + { + yield return null; + } + } +} diff --git a/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs similarity index 65% rename from osu.Game.Modes.Catch/CatchDifficultyCalculator.cs rename to osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs index 53c6f5c2ce..a865299cff 100644 --- a/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs @@ -2,11 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Catch.Beatmaps; -using osu.Game.Modes.Catch.Objects; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; -namespace osu.Game.Modes.Catch +namespace osu.Game.Rulesets.Catch { public class CatchDifficultyCalculator : DifficultyCalculator { @@ -19,6 +20,6 @@ namespace osu.Game.Modes.Catch return 0; } - protected override IBeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); + protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); } } \ No newline at end of file diff --git a/osu.Game.Modes.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs similarity index 88% rename from osu.Game.Modes.Catch/CatchRuleset.cs rename to osu.Game.Rulesets.Catch/CatchRuleset.cs index 09d8bdb9e5..a6faf13d51 100644 --- a/osu.Game.Modes.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -4,16 +4,16 @@ using OpenTK.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Modes.Catch.Mods; -using osu.Game.Modes.Catch.UI; -using osu.Game.Modes.Mods; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using System.Collections.Generic; -using osu.Game.Modes.Catch.Scoring; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Scoring; -namespace osu.Game.Modes.Catch +namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { @@ -76,8 +76,6 @@ namespace osu.Game.Modes.Catch } } - protected override PlayMode PlayMode => PlayMode.Catch; - public override string Description => "osu!catch"; public override FontAwesome Icon => FontAwesome.fa_osu_fruits_o; @@ -92,5 +90,7 @@ namespace osu.Game.Modes.Catch public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); + + public override int LegacyID => 2; } } diff --git a/osu.Game.Modes.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs similarity index 78% rename from osu.Game.Modes.Catch/Judgements/CatchJudgement.cs rename to osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index eaacedd7e0..f0125b4c3a 100644 --- a/osu.Game.Modes.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; -namespace osu.Game.Modes.Catch.Judgements +namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { diff --git a/osu.Game.Modes.Catch/Mods/CatchMod.cs b/osu.Game.Rulesets.Catch/Mods/CatchMod.cs similarity index 91% rename from osu.Game.Modes.Catch/Mods/CatchMod.cs rename to osu.Game.Rulesets.Catch/Mods/CatchMod.cs index 97e4e58a5d..64a0c51b72 100644 --- a/osu.Game.Modes.Catch/Mods/CatchMod.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchMod.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; -namespace osu.Game.Modes.Catch.Mods +namespace osu.Game.Rulesets.Catch.Mods { public class CatchModNoFail : ModNoFail { diff --git a/osu.Game.Modes.Catch/Objects/CatchBaseHit.cs b/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs similarity index 75% rename from osu.Game.Modes.Catch/Objects/CatchBaseHit.cs rename to osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs index ee66894d31..de0547580f 100644 --- a/osu.Game.Modes.Catch/Objects/CatchBaseHit.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; -namespace osu.Game.Modes.Catch.Objects +namespace osu.Game.Rulesets.Catch.Objects { public abstract class CatchBaseHit : HitObject { diff --git a/osu.Game.Modes.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs similarity index 93% rename from osu.Game.Modes.Catch/Objects/Drawable/DrawableFruit.cs rename to osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 885048b938..ae6585bdb2 100644 --- a/osu.Game.Modes.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Transforms; using OpenTK; -namespace osu.Game.Modes.Catch.Objects.Drawable +namespace osu.Game.Rulesets.Catch.Objects.Drawable { internal class DrawableFruit : Sprite { diff --git a/osu.Game.Modes.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs similarity index 80% rename from osu.Game.Modes.Catch/Objects/Droplet.cs rename to osu.Game.Rulesets.Catch/Objects/Droplet.cs index dd58fec7e8..b1206e0d75 100644 --- a/osu.Game.Modes.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Catch.Objects +namespace osu.Game.Rulesets.Catch.Objects { public class Droplet : CatchBaseHit { diff --git a/osu.Game.Modes.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs similarity index 80% rename from osu.Game.Modes.Catch/Objects/Fruit.cs rename to osu.Game.Rulesets.Catch/Objects/Fruit.cs index 15363a7031..fc55f83969 100644 --- a/osu.Game.Modes.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Catch.Objects +namespace osu.Game.Rulesets.Catch.Objects { public class Fruit : CatchBaseHit { diff --git a/osu.Game.Modes.Catch/OpenTK.dll.config b/osu.Game.Rulesets.Catch/OpenTK.dll.config similarity index 100% rename from osu.Game.Modes.Catch/OpenTK.dll.config rename to osu.Game.Rulesets.Catch/OpenTK.dll.config diff --git a/osu.Game.Modes.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs similarity index 90% rename from osu.Game.Modes.Catch/Properties/AssemblyInfo.cs rename to osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs index 1d25411e73..42fbc7e082 100644 --- a/osu.Game.Modes.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs @@ -7,11 +7,11 @@ using System.Runtime.InteropServices; // 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")] +[assembly: AssemblyTitle("osu.Game.Rulesets.Catch")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("osu.Game.Modes.Catch")] +[assembly: AssemblyProduct("osu.Game.Rulesets.Catch")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs similarity index 72% rename from osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs rename to osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 1b9bedf7fb..3f29547e46 100644 --- a/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.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.Game.Modes.Catch.Judgements; -using osu.Game.Modes.Catch.Objects; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; -namespace osu.Game.Modes.Catch.Scoring +namespace osu.Game.Rulesets.Catch.Scoring { internal class CatchScoreProcessor : ScoreProcessor { diff --git a/osu.Game.Modes.Catch/UI/CatchHitRenderer.cs b/osu.Game.Rulesets.Catch/UI/CatchHitRenderer.cs similarity index 56% rename from osu.Game.Modes.Catch/UI/CatchHitRenderer.cs rename to osu.Game.Rulesets.Catch/UI/CatchHitRenderer.cs index 90bd61a39f..f34585be55 100644 --- a/osu.Game.Modes.Catch/UI/CatchHitRenderer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchHitRenderer.cs @@ -2,15 +2,16 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Catch.Beatmaps; -using osu.Game.Modes.Catch.Judgements; -using osu.Game.Modes.Catch.Objects; -using osu.Game.Modes.Catch.Scoring; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.Judgements; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; -namespace osu.Game.Modes.Catch.UI +namespace osu.Game.Rulesets.Catch.UI { public class CatchHitRenderer : HitRenderer { @@ -21,9 +22,7 @@ namespace osu.Game.Modes.Catch.UI public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); - protected override IBeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); - - protected override IBeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor(); + protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); protected override Playfield CreatePlayfield() => new CatchPlayfield(); diff --git a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs similarity index 68% rename from osu.Game.Modes.Catch/UI/CatchPlayfield.cs rename to osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index cf1a665470..4b1e6e93cd 100644 --- a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,19 +3,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Modes.Catch.Objects; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.UI; using OpenTK; -using osu.Game.Modes.Catch.Judgements; +using osu.Game.Rulesets.Catch.Judgements; -namespace osu.Game.Modes.Catch.UI +namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfield : Playfield { 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.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj similarity index 88% rename from osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj rename to osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 593d8db4f6..281d2b5a79 100644 --- a/osu.Game.Modes.Catch/osu.Game.Modes.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -7,8 +7,8 @@ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} Library Properties - osu.Game.Modes.Catch - osu.Game.Modes.Catch + osu.Game.Rulesets.Catch + osu.Game.Rulesets.Catch v4.5 512 @@ -21,6 +21,7 @@ prompt 4 false + 6 pdbonly @@ -33,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 @@ -48,7 +48,6 @@ - @@ -74,9 +73,9 @@ {C76BF5B3-985E-4D39-95FE-97C9C879B83A} osu.Framework - + {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Modes.Osu + osu.Game.Rulesets.Osu {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} @@ -84,7 +83,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs new file mode 100644 index 0000000000..847af965cc --- /dev/null +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using System.Collections.Generic; +using System; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Beatmaps +{ + internal class ManiaBeatmapConverter : BeatmapConverter + { + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; + + protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) + { + yield return null; + } + } +} diff --git a/osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs similarity index 78% rename from osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs rename to osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 3ef5b0f29b..8dafbd01a5 100644 --- a/osu.Game.Modes.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; -namespace osu.Game.Modes.Mania.Judgements +namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { diff --git a/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs similarity index 65% rename from osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs rename to osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs index 02a5a3acdc..e9bcc60d2c 100644 --- a/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs @@ -2,11 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Mania.Beatmaps; -using osu.Game.Modes.Mania.Objects; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; using System.Collections.Generic; -namespace osu.Game.Modes.Mania +namespace osu.Game.Rulesets.Mania { public class ManiaDifficultyCalculator : DifficultyCalculator { @@ -20,6 +21,6 @@ namespace osu.Game.Modes.Mania return 0; } - protected override IBeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); } } \ No newline at end of file diff --git a/osu.Game.Modes.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs similarity index 90% rename from osu.Game.Modes.Mania/ManiaRuleset.cs rename to osu.Game.Rulesets.Mania/ManiaRuleset.cs index bd995d87d6..26614075b1 100644 --- a/osu.Game.Modes.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -3,16 +3,16 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Modes.Mania.Mods; -using osu.Game.Modes.Mania.UI; -using osu.Game.Modes.Mods; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using System.Collections.Generic; -using osu.Game.Modes.Mania.Scoring; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Scoring; -namespace osu.Game.Modes.Mania +namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { @@ -96,8 +96,6 @@ namespace osu.Game.Modes.Mania } } - protected override PlayMode PlayMode => PlayMode.Mania; - public override string Description => "osu!mania"; public override FontAwesome Icon => FontAwesome.fa_osu_mania_o; @@ -107,5 +105,7 @@ namespace osu.Game.Modes.Mania public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); + + public override int LegacyID => 3; } } diff --git a/osu.Game.Modes.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs similarity index 94% rename from osu.Game.Modes.Mania/Mods/ManiaMod.cs rename to osu.Game.Rulesets.Mania/Mods/ManiaMod.cs index b330680550..68458caeac 100644 --- a/osu.Game.Modes.Mania/Mods/ManiaMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs @@ -2,10 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Graphics; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; using System; -namespace osu.Game.Modes.Mania.Mods +namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModNoFail : ModNoFail { diff --git a/osu.Game.Modes.Mania/Objects/Drawable/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawable/DrawableNote.cs similarity index 93% rename from osu.Game.Modes.Mania/Objects/Drawable/DrawableNote.cs rename to osu.Game.Rulesets.Mania/Objects/Drawable/DrawableNote.cs index 76999cef21..07a27b1643 100644 --- a/osu.Game.Modes.Mania/Objects/Drawable/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawable/DrawableNote.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics; using OpenTK; -namespace osu.Game.Modes.Mania.Objects.Drawable +namespace osu.Game.Rulesets.Mania.Objects.Drawable { public class DrawableNote : Sprite { diff --git a/osu.Game.Modes.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs similarity index 79% rename from osu.Game.Modes.Mania/Objects/HoldNote.cs rename to osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 3d95e11118..e8ce1da77f 100644 --- a/osu.Game.Modes.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Mania.Objects +namespace osu.Game.Rulesets.Mania.Objects { public class HoldNote : Note { diff --git a/osu.Game.Modes.Mania/Objects/ManiaBaseHit.cs b/osu.Game.Rulesets.Mania/Objects/ManiaBaseHit.cs similarity index 74% rename from osu.Game.Modes.Mania/Objects/ManiaBaseHit.cs rename to osu.Game.Rulesets.Mania/Objects/ManiaBaseHit.cs index 8b3afc82d9..4c15b69eb7 100644 --- a/osu.Game.Modes.Mania/Objects/ManiaBaseHit.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaBaseHit.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; -namespace osu.Game.Modes.Mania.Objects +namespace osu.Game.Rulesets.Mania.Objects { public abstract class ManiaBaseHit : HitObject { diff --git a/osu.Game.Modes.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs similarity index 80% rename from osu.Game.Modes.Mania/Objects/Note.cs rename to osu.Game.Rulesets.Mania/Objects/Note.cs index c36ed8cf7e..5a6d6003db 100644 --- a/osu.Game.Modes.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Mania.Objects +namespace osu.Game.Rulesets.Mania.Objects { public class Note : ManiaBaseHit { diff --git a/osu.Game.Modes.Mania/OpenTK.dll.config b/osu.Game.Rulesets.Mania/OpenTK.dll.config similarity index 100% rename from osu.Game.Modes.Mania/OpenTK.dll.config rename to osu.Game.Rulesets.Mania/OpenTK.dll.config diff --git a/osu.Game.Modes.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs similarity index 90% rename from osu.Game.Modes.Mania/Properties/AssemblyInfo.cs rename to osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs index 11c8290f1b..790002acd7 100644 --- a/osu.Game.Modes.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs @@ -7,11 +7,11 @@ using System.Runtime.InteropServices; // 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")] +[assembly: AssemblyTitle("osu.Game.Rulesets.Mania")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("osu.Game.Modes.Mania")] +[assembly: AssemblyProduct("osu.Game.Rulesets.Mania")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs similarity index 72% rename from osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs rename to osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 0f87030e25..ba0304a44a 100644 --- a/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.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.Game.Modes.Mania.Judgements; -using osu.Game.Modes.Mania.Objects; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; -namespace osu.Game.Modes.Mania.Scoring +namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { diff --git a/osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs similarity index 59% rename from osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs rename to osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index 0415bc961a..7fb8f95b4c 100644 --- a/osu.Game.Modes.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -2,15 +2,16 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Mania.Beatmaps; -using osu.Game.Modes.Mania.Judgements; -using osu.Game.Modes.Mania.Objects; -using osu.Game.Modes.Mania.Scoring; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; -namespace osu.Game.Modes.Mania.UI +namespace osu.Game.Rulesets.Mania.UI { public class ManiaHitRenderer : HitRenderer { @@ -24,9 +25,7 @@ namespace osu.Game.Modes.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - protected override IBeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); - - protected override IBeatmapProcessor CreateBeatmapProcessor() => new ManiaBeatmapProcessor(); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override Playfield CreatePlayfield() => new ManiaPlayfield(columns); diff --git a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs similarity index 78% rename from osu.Game.Modes.Mania/UI/ManiaPlayfield.cs rename to osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 670d18f71f..5eea3d70c0 100644 --- a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -3,20 +3,19 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Modes.Mania.Objects; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI; using OpenTK; using OpenTK.Graphics; -using osu.Game.Modes.Mania.Judgements; +using osu.Game.Rulesets.Mania.Judgements; -namespace osu.Game.Modes.Mania.UI +namespace osu.Game.Rulesets.Mania.UI { public class ManiaPlayfield : Playfield { 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.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj similarity index 86% rename from osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj rename to osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index cc925d417a..facffa757c 100644 --- a/osu.Game.Modes.Mania/osu.Game.Modes.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -7,8 +7,8 @@ {48F4582B-7687-4621-9CBE-5C24197CB536} Library Properties - osu.Game.Modes.Mania - osu.Game.Modes.Mania + osu.Game.Rulesets.Mania + osu.Game.Rulesets.Mania v4.5 512 @@ -21,6 +21,7 @@ prompt 4 false + 6 pdbonly @@ -33,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 @@ -48,7 +48,6 @@ - @@ -67,13 +66,13 @@ {C76BF5B3-985E-4D39-95FE-97C9C879B83A} osu.Framework - + {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Modes.Osu + osu.Game.Rulesets.Osu - + {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Modes.Taiko + osu.Game.Rulesets.Taiko {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} @@ -89,7 +88,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs new file mode 100644 index 0000000000..7f6f524a7a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.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.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Types; +using System; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Beatmaps +{ + internal class OsuBeatmapConverter : BeatmapConverter + { + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) }; + + protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) + { + var curveData = original as IHasCurve; + var endTimeData = original as IHasEndTime; + var positionData = original as IHasPosition; + var comboData = original as IHasCombo; + + if (curveData != null) + { + yield return new Slider + { + StartTime = original.StartTime, + Samples = original.Samples, + ControlPoints = curveData.ControlPoints, + CurveType = curveData.CurveType, + Distance = curveData.Distance, + RepeatSamples = curveData.RepeatSamples, + RepeatCount = curveData.RepeatCount, + Position = positionData?.Position ?? Vector2.Zero, + NewCombo = comboData?.NewCombo ?? false + }; + } + else if (endTimeData != null) + { + yield return new Spinner + { + StartTime = original.StartTime, + Samples = original.Samples, + EndTime = endTimeData.EndTime, + + Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2, + }; + } + else + { + yield return new HitCircle + { + StartTime = original.StartTime, + Samples = original.Samples, + Position = positionData?.Position ?? Vector2.Zero, + NewCombo = comboData?.NewCombo ?? false + }; + } + } + } +} diff --git a/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs similarity index 64% rename from osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs rename to osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index bae12a98e3..fce0188cda 100644 --- a/osu.Game.Modes.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -3,96 +3,58 @@ using OpenTK; using osu.Game.Beatmaps; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.Objects.Drawables; -using System.Collections.Generic; -using osu.Game.Modes.Objects.Types; -using System.Linq; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; -namespace osu.Game.Modes.Osu.Beatmaps +namespace osu.Game.Rulesets.Osu.Beatmaps { - internal class OsuBeatmapConverter : IBeatmapConverter + internal class OsuBeatmapProcessor : BeatmapProcessor { - public Beatmap Convert(Beatmap original) + public override void PostProcess(Beatmap beatmap) { - return new Beatmap(original) + applyStacking(beatmap); + + if (beatmap.ComboColors.Count == 0) + return; + + int comboIndex = 0; + int colourIndex = 0; + + foreach (var obj in beatmap.HitObjects) { - HitObjects = convertHitObjects(original.HitObjects, original.BeatmapInfo?.StackLeniency ?? 0.7f) - }; - } - - private List convertHitObjects(List hitObjects, float stackLeniency) - { - List converted = hitObjects.Select(convertHitObject).ToList(); - - updateStacking(converted, stackLeniency); - - return converted; - } - - private OsuHitObject convertHitObject(HitObject original) - { - IHasCurve curveData = original as IHasCurve; - IHasEndTime endTimeData = original as IHasEndTime; - IHasPosition positionData = original as IHasPosition; - IHasCombo comboData = original as IHasCombo; - - if (curveData != null) - { - return new Slider + if (obj.NewCombo) { - StartTime = original.StartTime, - Samples = original.Samples, - CurveObject = curveData, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false - }; - } + comboIndex = 0; + colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count; + } - if (endTimeData != null) - { - return new Spinner - { - StartTime = original.StartTime, - Samples = original.Samples, - Position = new Vector2(512, 384) / 2, - EndTime = endTimeData.EndTime - }; + obj.ComboIndex = comboIndex++; + obj.ComboColour = beatmap.ComboColors[colourIndex]; } - - return new HitCircle - { - StartTime = original.StartTime, - Samples = original.Samples, - Position = positionData?.Position ?? Vector2.Zero, - NewCombo = comboData?.NewCombo ?? false - }; } - private void updateStacking(List hitObjects, float stackLeniency, int startIndex = 0, int endIndex = -1) + private void applyStacking(Beatmap beatmap) { - if (endIndex == -1) - endIndex = hitObjects.Count - 1; - const int stack_distance = 3; - float stackThreshold = DrawableOsuHitObject.TIME_PREEMPT * stackLeniency; + float stackThreshold = DrawableOsuHitObject.TIME_PREEMPT * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; - // Reset stacking inside the update range - for (int i = startIndex; i <= endIndex; i++) - hitObjects[i].StackHeight = 0; + // Reset stacking + for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++) + beatmap.HitObjects[i].StackHeight = 0; // Extend the end index to include objects they are stacked on - int extendedEndIndex = endIndex; - for (int i = endIndex; i >= startIndex; i--) + int extendedEndIndex = beatmap.HitObjects.Count - 1; + for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--) { int stackBaseIndex = i; - for (int n = stackBaseIndex + 1; n < hitObjects.Count; n++) + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) { - OsuHitObject stackBaseObject = hitObjects[stackBaseIndex]; + OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; if (stackBaseObject is Spinner) break; - OsuHitObject objectN = hitObjects[n]; + OsuHitObject objectN = beatmap.HitObjects[n]; if (objectN is Spinner) continue; @@ -115,14 +77,14 @@ namespace osu.Game.Modes.Osu.Beatmaps if (stackBaseIndex > extendedEndIndex) { extendedEndIndex = stackBaseIndex; - if (extendedEndIndex == hitObjects.Count - 1) + if (extendedEndIndex == beatmap.HitObjects.Count - 1) break; } } //Reverse pass for stack calculation. - int extendedStartIndex = startIndex; - for (int i = extendedEndIndex; i > startIndex; i--) + int extendedStartIndex = 0; + for (int i = extendedEndIndex; i > 0; i--) { int n = i; /* We should check every note which has not yet got a stack. @@ -136,7 +98,7 @@ namespace osu.Game.Modes.Osu.Beatmaps * 2 and 1 will be ignored in the i loop because they already have a stack value. */ - OsuHitObject objectI = hitObjects[i]; + OsuHitObject objectI = beatmap.HitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; /* If this object is a hitcircle, then we enter this "special" case. @@ -147,7 +109,7 @@ namespace osu.Game.Modes.Osu.Beatmaps { while (--n >= 0) { - OsuHitObject objectN = hitObjects[n]; + OsuHitObject objectN = beatmap.HitObjects[n]; if (objectN is Spinner) continue; double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime; @@ -174,7 +136,7 @@ namespace osu.Game.Modes.Osu.Beatmaps for (int j = n + 1; j <= i; j++) { //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). - OsuHitObject objectJ = hitObjects[j]; + OsuHitObject objectJ = beatmap.HitObjects[j]; if (Vector2.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) objectJ.StackHeight -= offset; } @@ -199,9 +161,9 @@ namespace osu.Game.Modes.Osu.Beatmaps /* We have hit the first slider in a possible stack. * From this point on, we ALWAYS stack positive regardless. */ - while (--n >= startIndex) + while (--n >= 0) { - OsuHitObject objectN = hitObjects[n]; + OsuHitObject objectN = beatmap.HitObjects[n]; if (objectN is Spinner) continue; if (objectI.StartTime - objectN.StartTime > stackThreshold) diff --git a/osu.Game.Modes.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs similarity index 89% rename from osu.Game.Modes.Osu/Judgements/OsuJudgement.cs rename to osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index e65d3dde3a..d61e179002 100644 --- a/osu.Game.Modes.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -2,11 +2,11 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Modes.Judgements; -using osu.Game.Modes.Osu.Objects.Drawables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Framework.Extensions; -namespace osu.Game.Modes.Osu.Judgements +namespace osu.Game.Rulesets.Osu.Judgements { public class OsuJudgement : Judgement { diff --git a/osu.Game.Modes.Osu/Mods/OsuMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs similarity index 93% rename from osu.Game.Modes.Osu/Mods/OsuMod.cs rename to osu.Game.Rulesets.Osu/Mods/OsuMod.cs index db2ee26b7a..bdb5f386d0 100644 --- a/osu.Game.Modes.Osu/Mods/OsuMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs @@ -3,13 +3,13 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Osu.Objects; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; using System; using System.Linq; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Scoring; -namespace osu.Game.Modes.Osu.Mods +namespace osu.Game.Rulesets.Osu.Mods { public class OsuModNoFail : ModNoFail { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs similarity index 83% rename from osu.Game.Modes.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs index a680c847ac..192ab0536e 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs @@ -2,10 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics.Containers; -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; using System.Collections.Generic; -namespace osu.Game.Modes.Osu.Objects.Drawables.Connections +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// /// Connects hit objects visually, for example with follow points. diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs similarity index 59% rename from osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 7815e3ba41..9f8ff17853 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -8,20 +8,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -namespace osu.Game.Modes.Osu.Objects.Drawables.Connections +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPoint : Container { - public double StartTime; - public double EndTime; - public Vector2 EndPosition; - private const float width = 8; public FollowPoint() { Origin = Anchor.Centre; - Alpha = 0; Masking = true; AutoSizeAxes = Axes.Both; @@ -45,22 +40,5 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Connections }, }; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Delay(StartTime); - FadeIn(DrawableOsuHitObject.TIME_FADEIN); - ScaleTo(1.5f); - ScaleTo(1, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out); - MoveTo(EndPosition, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out); - - Delay(EndTime - StartTime); - FadeOut(DrawableOsuHitObject.TIME_FADEIN); - - Delay(DrawableOsuHitObject.TIME_FADEIN); - Expire(true); - } } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs similarity index 76% rename from osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index f45e4226dd..925767b851 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -4,9 +4,10 @@ using System; using System.Collections.Generic; using OpenTK; -using osu.Game.Modes.Objects.Types; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Modes.Osu.Objects.Drawables.Connections +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : ConnectionRenderer { @@ -80,14 +81,28 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Connections double fadeOutTime = startTime + fraction * duration; double fadeInTime = fadeOutTime - PreEmpt; - Add(new FollowPoint + FollowPoint fp; + + Add(fp = new FollowPoint { - StartTime = fadeInTime, - EndTime = fadeOutTime, Position = pointStartPosition, - EndPosition = pointEndPosition, Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f), }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(DrawableOsuHitObject.TIME_FADEIN); + fp.ScaleTo(1, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out); + + fp.MoveTo(pointEndPosition, DrawableOsuHitObject.TIME_FADEIN, EasingTypes.Out); + + fp.Delay(fadeOutTime - fadeInTime); + fp.FadeOut(DrawableOsuHitObject.TIME_FADEIN); + } + + fp.Expire(true); } } prevHitObject = currHitObject; diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs similarity index 91% rename from osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 68c5ec0a45..09bfffeefe 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -3,12 +3,12 @@ using System; using osu.Framework.Graphics; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using OpenTK; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Modes.Osu.Objects.Drawables +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { @@ -104,10 +104,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT); } - protected override void UpdateState(ArmedState state) + protected override void UpdateCurrentState(ArmedState state) { - base.UpdateState(state); - ApproachCircle.FadeOut(); double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs similarity index 68% rename from osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 816faa0d98..57a9804330 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -2,10 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; -namespace osu.Game.Modes.Osu.Objects.Drawables +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuHitObject : DrawableHitObject { @@ -21,17 +21,23 @@ namespace osu.Game.Modes.Osu.Objects.Drawables protected override OsuJudgement CreateJudgement() => new OsuJudgement { MaxScore = OsuScoreResult.Hit300 }; - protected override void UpdateState(ArmedState state) + protected sealed override void UpdateState(ArmedState state) { Flush(); UpdateInitialState(); - Delay(HitObject.StartTime - Time.Current - TIME_PREEMPT + Judgement.TimeOffset, true); + using (BeginAbsoluteSequence(HitObject.StartTime - TIME_PREEMPT, true)) + { + UpdatePreemptState(); - UpdatePreemptState(); + using (BeginDelayedSequence(TIME_PREEMPT + Judgement.TimeOffset, true)) + UpdateCurrentState(state); + } + } - Delay(TIME_PREEMPT, true); + protected virtual void UpdateCurrentState(ArmedState state) + { } protected virtual void UpdatePreemptState() diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs similarity index 76% rename from osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 647c8faef8..eaa0bb7d27 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -2,12 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; using OpenTK; -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; -namespace osu.Game.Modes.Osu.Objects.Drawables +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuJudgement : DrawableJudgement { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs similarity index 93% rename from osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index be326751ba..b80f1d7178 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -3,13 +3,13 @@ using OpenTK; using osu.Framework.Graphics; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Containers; -namespace osu.Game.Modes.Osu.Objects.Drawables +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { @@ -158,10 +158,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables ball.Alpha = 0; } - protected override void UpdateState(ArmedState state) + protected override void UpdateCurrentState(ArmedState state) { - base.UpdateState(state); - ball.FadeIn(); Delay(slider.Duration, true); @@ -181,4 +179,4 @@ namespace osu.Game.Modes.Osu.Objects.Drawables { void UpdateProgress(double progress, int repeat); } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs similarity index 88% rename from osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 188306c857..6b4d40e080 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -4,12 +4,12 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTick : DrawableOsuHitObject { @@ -72,10 +72,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Delay(-animIn); } - protected override void UpdateState(ArmedState state) + protected override void UpdateCurrentState(ArmedState state) { - base.UpdateState(state); - switch (state) { case ArmedState.Idle: @@ -93,4 +91,4 @@ namespace osu.Game.Modes.Osu.Objects.Drawables } } } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs similarity index 91% rename from osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d0136f717c..90a6d432c4 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -5,12 +5,13 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using OpenTK; using OpenTK.Graphics; +using osu.Game.Rulesets.Osu.UI; -namespace osu.Game.Modes.Osu.Objects.Drawables +namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinner : DrawableOsuHitObject { @@ -29,7 +30,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Position = s.Position; //take up full playfield. - Size = new Vector2(512); + Size = new Vector2(OsuPlayfield.BASE_SIZE.X); spinner = s; @@ -131,10 +132,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables disc.FadeIn(200); } - protected override void UpdateState(ArmedState state) + protected override void UpdateCurrentState(ArmedState state) { - base.UpdateState(state); - Delay(spinner.Duration, true); FadeOut(160); diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs similarity index 90% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/ApproachCircle.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index fd4ef64350..323f5fb297 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ApproachCircle : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs similarity index 93% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 704a6b7490..9a90c07517 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using OpenTK; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class CirclePiece : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs similarity index 90% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 97228f610f..e5cf10b88a 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ExplodePiece : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs similarity index 89% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/FlashPiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index cb60977dab..68ffb756d4 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using OpenTK; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class FlashPiece : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs similarity index 91% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/GlowPiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs index 6cffa370cf..8a7b353da1 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class GlowPiece : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs similarity index 93% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 0ebd274246..07b21657a5 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using OpenTK.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class NumberPiece : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs similarity index 90% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/RingPiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index 3e172cdc09..a04d3e7a0a 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class RingPiece : Container { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs similarity index 95% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index becbebf0c7..4cffc1def3 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using OpenTK.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SliderBall : CircularContainer, ISliderProgress { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs similarity index 96% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBody.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index e7837471ee..b23fdde4e8 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -15,7 +15,7 @@ using OpenTK; using OpenTK.Graphics.ES30; using OpenTK.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SliderBody : Container, ISliderProgress { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBouncer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs similarity index 93% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBouncer.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs index 196b9fb521..65679dd7d3 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBouncer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SliderBouncer : Container, ISliderProgress { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs similarity index 80% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index 50dab933b0..72024bbe99 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SpinnerBackground : SpinnerDisc { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs similarity index 95% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index a4fce3deb5..71adba74c7 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -15,7 +15,7 @@ using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SpinnerDisc : CircularContainer { diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs similarity index 89% rename from osu.Game.Modes.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 26d44f3865..ea3ddb5051 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -3,7 +3,7 @@ using osu.Game.Graphics.Backgrounds; -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class TrianglesPiece : Triangles { diff --git a/osu.Game.Modes.Osu/Objects/HitCircle.cs b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs similarity index 81% rename from osu.Game.Modes.Osu/Objects/HitCircle.cs rename to osu.Game.Rulesets.Osu/Objects/HitCircle.cs index aa45ac7fb9..be969f1e18 100644 --- a/osu.Game.Modes.Osu/Objects/HitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Osu.Objects +namespace osu.Game.Rulesets.Osu.Objects { public class HitCircle : OsuHitObject { diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs similarity index 88% rename from osu.Game.Modes.Osu/Objects/OsuHitObject.cs rename to osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index fa422834db..723a37ed7b 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,15 +1,15 @@ // 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; +using osu.Game.Rulesets.Objects; using OpenTK; -using osu.Game.Modes.Osu.Objects.Drawables; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Database; -namespace osu.Game.Modes.Osu.Objects +namespace osu.Game.Rulesets.Osu.Objects { public abstract class OsuHitObject : HitObject, IHasCombo, IHasPosition { @@ -21,6 +21,8 @@ namespace osu.Game.Modes.Osu.Objects private const double hit_window_300 = 30; public Vector2 Position { get; set; } + public float X => Position.X; + public float Y => Position.Y; public Vector2 StackedPosition => Position + StackOffset; diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObjectDifficulty.cs similarity index 97% rename from osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs rename to osu.Game.Rulesets.Osu/Objects/OsuHitObjectDifficulty.cs index 322f6b077a..1786771dca 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObjectDifficulty.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObjectDifficulty.cs @@ -6,7 +6,7 @@ using System; using System.Diagnostics; using System.Linq; -namespace osu.Game.Modes.Osu.Objects +namespace osu.Game.Rulesets.Osu.Objects { internal class OsuHitObjectDifficulty { diff --git a/osu.Game.Modes.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs similarity index 69% rename from osu.Game.Modes.Osu/Objects/Slider.cs rename to osu.Game.Rulesets.Osu/Objects/Slider.cs index a01c517cb2..6c0147a3de 100644 --- a/osu.Game.Modes.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -3,15 +3,15 @@ using OpenTK; using osu.Game.Beatmaps.Timing; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Database; using System.Linq; using osu.Game.Audio; -namespace osu.Game.Modes.Osu.Objects +namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { @@ -20,24 +20,33 @@ namespace osu.Game.Modes.Osu.Objects /// private const float base_scoring_distance = 100; - public IHasCurve CurveObject { get; set; } - - public SliderCurve Curve => CurveObject.Curve; + public readonly SliderCurve Curve = new SliderCurve(); public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; public double Duration => EndTime - StartTime; public override Vector2 EndPosition => PositionAt(1); - public Vector2 PositionAt(double progress) => CurveObject.PositionAt(progress); - public double ProgressAt(double progress) => CurveObject.ProgressAt(progress); - public int RepeatAt(double progress) => CurveObject.RepeatAt(progress); + public List ControlPoints + { + get { return Curve.ControlPoints; } + set { Curve.ControlPoints = value; } + } - public List ControlPoints => CurveObject.ControlPoints; - public CurveType CurveType => CurveObject.CurveType; - public double Distance => CurveObject.Distance; + public CurveType CurveType + { + get { return Curve.CurveType; } + set { Curve.CurveType = value; } + } - public int RepeatCount => CurveObject.RepeatCount; + public double Distance + { + get { return Curve.Distance; } + set { Curve.Distance = value; } + } + + public List RepeatSamples { get; set; } = new List(); + public int RepeatCount { get; set; } = 1; private int stackHeight; public override int StackHeight @@ -63,6 +72,18 @@ namespace osu.Game.Modes.Osu.Objects TickDistance = scoringDistance / difficulty.SliderTickRate; } + public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); + + public double ProgressAt(double progress) + { + double p = progress * RepeatCount % 1; + if (RepeatAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + public int RepeatAt(double progress) => (int)(progress * RepeatCount); + public IEnumerable Ticks { get @@ -96,12 +117,12 @@ namespace osu.Game.Modes.Osu.Objects StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, - Samples = Samples.Select(s => new SampleInfo + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", Volume = s.Volume - }).ToList() + })) }; } } diff --git a/osu.Game.Modes.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs similarity index 83% rename from osu.Game.Modes.Osu/Objects/SliderTick.cs rename to osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 67f393b126..7112a39f97 100644 --- a/osu.Game.Modes.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Osu.Objects +namespace osu.Game.Rulesets.Osu.Objects { public class SliderTick : OsuHitObject { diff --git a/osu.Game.Modes.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs similarity index 79% rename from osu.Game.Modes.Osu/Objects/Spinner.cs rename to osu.Game.Rulesets.Osu/Objects/Spinner.cs index dd9a6c386a..0a2c05833a 100644 --- a/osu.Game.Modes.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Modes.Osu.Objects +namespace osu.Game.Rulesets.Osu.Objects { public class Spinner : OsuHitObject, IHasEndTime { diff --git a/osu.Game.Modes.Osu/OpenTK.dll.config b/osu.Game.Rulesets.Osu/OpenTK.dll.config similarity index 100% rename from osu.Game.Modes.Osu/OpenTK.dll.config rename to osu.Game.Rulesets.Osu/OpenTK.dll.config diff --git a/osu.Game.Modes.Osu/OsuAutoReplay.cs b/osu.Game.Rulesets.Osu/OsuAutoReplay.cs similarity index 94% rename from osu.Game.Modes.Osu/OsuAutoReplay.cs rename to osu.Game.Rulesets.Osu/OsuAutoReplay.cs index ae85bd72d8..da30cf4efb 100644 --- a/osu.Game.Modes.Osu/OsuAutoReplay.cs +++ b/osu.Game.Rulesets.Osu/OsuAutoReplay.cs @@ -4,16 +4,17 @@ using OpenTK; using osu.Framework.MathUtils; using osu.Game.Beatmaps; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using System; using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Graphics; -using osu.Game.Modes.Objects.Types; -using osu.Game.Modes.Replays; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; +using osu.Game.Users; -namespace osu.Game.Modes.Osu +namespace osu.Game.Rulesets.Osu { public class OsuAutoReplay : Replay { @@ -27,6 +28,11 @@ namespace osu.Game.Modes.Osu { this.beatmap = beatmap; + User = new User + { + Username = @"Autoplay", + }; + createAutoReplay(); } @@ -132,8 +138,7 @@ namespace osu.Game.Modes.Osu if (h is Spinner) { - targetPosition.X = Frames[Frames.Count - 1].MouseX; - targetPosition.Y = Frames[Frames.Count - 1].MouseY; + targetPosition = Frames[Frames.Count - 1].Position; Vector2 difference = spinner_centre - targetPosition; @@ -187,7 +192,7 @@ namespace osu.Game.Modes.Osu addFrameToReplay(lastFrame); } - Vector2 lastPosition = new Vector2(lastFrame.MouseX, lastFrame.MouseY); + Vector2 lastPosition = lastFrame.Position; double timeDifference = applyModsToTime(h.StartTime - lastFrame.Time); @@ -213,7 +218,7 @@ namespace osu.Game.Modes.Osu ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1; - double hEndTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY; ReplayFrame newFrame = new ReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button); ReplayFrame endFrame = new ReplayFrame(hEndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, ReplayButtonState.None); diff --git a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficultyCalculator.cs similarity index 94% rename from osu.Game.Modes.Osu/OsuDifficultyCalculator.cs rename to osu.Game.Rulesets.Osu/OsuDifficultyCalculator.cs index 7696638082..5669993e67 100644 --- a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficultyCalculator.cs @@ -2,13 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Objects.Types; -using osu.Game.Modes.Osu.Beatmaps; -using osu.Game.Modes.Osu.Objects; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using System; using System.Collections.Generic; -namespace osu.Game.Modes.Osu +namespace osu.Game.Rulesets.Osu { public class OsuDifficultyCalculator : DifficultyCalculator { @@ -27,7 +27,7 @@ namespace osu.Game.Modes.Osu protected override void PreprocessHitObjects() { foreach (var h in Objects) - (h as IHasCurve)?.Curve?.Calculate(); + (h as Slider)?.Curve?.Calculate(); } protected override double CalculateInternal(Dictionary categoryDifficulty) @@ -180,7 +180,7 @@ namespace osu.Game.Modes.Osu return difficulty; } - protected override IBeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); + protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); // Those values are used as array indices. Be careful when changing them! public enum DifficultyType @@ -189,4 +189,4 @@ namespace osu.Game.Modes.Osu Aim, }; } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs b/osu.Game.Rulesets.Osu/OsuKeyConversionInputManager.cs similarity index 58% rename from osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs rename to osu.Game.Rulesets.Osu/OsuKeyConversionInputManager.cs index 986240b37f..d60aab90fb 100644 --- a/osu.Game.Modes.Osu/OsuKeyConversionInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuKeyConversionInputManager.cs @@ -2,28 +2,18 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Input; -using osu.Game.Configuration; using osu.Game.Screens.Play; using OpenTK.Input; using KeyboardState = osu.Framework.Input.KeyboardState; using MouseState = osu.Framework.Input.MouseState; -namespace osu.Game.Modes.Osu +namespace osu.Game.Rulesets.Osu { public class OsuKeyConversionInputManager : KeyConversionInputManager { private bool leftViaKeyboard; private bool rightViaKeyboard; - private Bindable mouseDisabled; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - mouseDisabled = config.GetBindable(OsuConfig.MouseDisableButtons); - } protected override void TransformState(InputState state) { @@ -40,16 +30,10 @@ namespace osu.Game.Modes.Osu if (mouse != null) { - if (mouseDisabled.Value) - { - mouse.PressedButtons.Remove(MouseButton.Left); - mouse.PressedButtons.Remove(MouseButton.Right); - } - 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/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs similarity index 89% rename from osu.Game.Modes.Osu/OsuRuleset.cs rename to osu.Game.Rulesets.Osu/OsuRuleset.cs index 12df7d3f3c..39e911651a 100644 --- a/osu.Game.Modes.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -4,18 +4,18 @@ using OpenTK.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Osu.Mods; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.UI; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using System.Collections.Generic; using System.Linq; -using osu.Game.Modes.Osu.Scoring; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; -namespace osu.Game.Modes.Osu +namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { @@ -99,8 +99,6 @@ namespace osu.Game.Modes.Osu public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap); - protected override PlayMode PlayMode => PlayMode.Osu; - public override string Description => "osu!"; public override IEnumerable CreateGameplayKeys() => new KeyCounter[] @@ -112,5 +110,7 @@ namespace osu.Game.Modes.Osu }; public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + + public override int LegacyID => 0; } } diff --git a/osu.Game.Modes.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs similarity index 100% rename from osu.Game.Modes.Osu/Properties/AssemblyInfo.cs rename to osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs new file mode 100644 index 0000000000..079ee928af --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Extensions; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + internal class OsuScoreProcessor : ScoreProcessor + { + public OsuScoreProcessor() + { + } + + public OsuScoreProcessor(HitRenderer hitRenderer) + : base(hitRenderer) + { + } + + protected override void Reset() + { + base.Reset(); + + Health.Value = 1; + Accuracy.Value = 1; + + scoreResultCounts.Clear(); + comboResultCounts.Clear(); + } + + private readonly Dictionary scoreResultCounts = new Dictionary(); + private readonly Dictionary comboResultCounts = new Dictionary(); + + public override void PopulateScore(Score score) + { + base.PopulateScore(score); + + score.Statistics[@"300"] = scoreResultCounts.GetOrDefault(OsuScoreResult.Hit300); + score.Statistics[@"100"] = scoreResultCounts.GetOrDefault(OsuScoreResult.Hit100); + score.Statistics[@"50"] = scoreResultCounts.GetOrDefault(OsuScoreResult.Hit50); + score.Statistics[@"x"] = scoreResultCounts.GetOrDefault(OsuScoreResult.Miss); + } + + protected override void OnNewJudgement(OsuJudgement judgement) + { + if (judgement != null) + { + if (judgement.Result != HitResult.None) + { + scoreResultCounts[judgement.Score] = scoreResultCounts.GetOrDefault(judgement.Score) + 1; + comboResultCounts[judgement.Combo] = comboResultCounts.GetOrDefault(judgement.Combo) + 1; + } + + switch (judgement.Result) + { + case HitResult.Hit: + Health.Value += 0.1f; + break; + case HitResult.Miss: + Health.Value -= 0.2f; + break; + } + } + + int score = 0; + int maxScore = 0; + + foreach (var j in Judgements) + { + score += j.ScoreValue; + maxScore += j.MaxScoreValue; + } + + TotalScore.Value = score; + Accuracy.Value = (double)score / maxScore; + } + } +} diff --git a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs b/osu.Game.Rulesets.Osu/UI/OsuHitRenderer.cs similarity index 62% rename from osu.Game.Modes.Osu/UI/OsuHitRenderer.cs rename to osu.Game.Rulesets.Osu/UI/OsuHitRenderer.cs index ca9ff6fc61..687518e6d5 100644 --- a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuHitRenderer.cs @@ -1,18 +1,20 @@ // 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; -using osu.Game.Modes.Osu.Judgements; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.Objects.Drawables; -using osu.Game.Modes.Osu.Scoring; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; -namespace osu.Game.Modes.Osu.UI +namespace osu.Game.Rulesets.Osu.UI { public class OsuHitRenderer : HitRenderer { @@ -23,9 +25,9 @@ namespace osu.Game.Modes.Osu.UI public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); - protected override IBeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); + protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter(); - protected override IBeatmapProcessor CreateBeatmapProcessor() => new OsuBeatmapProcessor(); + protected override BeatmapProcessor CreateBeatmapProcessor() => new OsuBeatmapProcessor(); protected override Playfield CreatePlayfield() => new OsuPlayfield(); @@ -46,5 +48,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.Rulesets.Osu/UI/OsuPlayfield.cs similarity index 84% rename from osu.Game.Modes.Osu/UI/OsuPlayfield.cs rename to osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d89bbfd131..53eedea073 100644 --- a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -4,16 +4,16 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Objects; -using osu.Game.Modes.Osu.Objects.Drawables; -using osu.Game.Modes.Osu.Objects.Drawables.Connections; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; +using osu.Game.Rulesets.UI; using System.Linq; using osu.Game.Graphics.Cursor; -using osu.Game.Modes.Osu.Judgements; +using osu.Game.Rulesets.Osu.Judgements; -namespace osu.Game.Modes.Osu.UI +namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { @@ -23,6 +23,8 @@ namespace osu.Game.Modes.Osu.UI public override bool ProvidingUserCursor => true; + public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); + public override Vector2 Size { get @@ -34,12 +36,10 @@ namespace osu.Game.Modes.Osu.UI } } - public OsuPlayfield() : base(512) + public OsuPlayfield() : base(BASE_SIZE.X) { Anchor = Anchor.Centre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(0.75f); Add(new Drawable[] { @@ -96,4 +96,4 @@ namespace osu.Game.Modes.Osu.UI judgementLayer.Add(explosion); } } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj similarity index 93% rename from osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj rename to osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 55322e855e..fcad0061e4 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -7,8 +7,8 @@ {C92A607B-1FDD-4954-9F92-03FF547D9080} Library Properties - osu.Game.Modes.Osu - osu.Game.Modes.Osu + osu.Game.Rulesets.Osu + osu.Game.Rulesets.Osu v4.5 512 @@ -22,6 +22,7 @@ prompt 4 false + 6 pdbonly @@ -34,8 +35,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 @@ -72,7 +72,6 @@ - @@ -104,7 +103,7 @@ - - + \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs similarity index 67% rename from osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs rename to osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index aee06ad796..0784c94059 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -2,19 +2,20 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Objects.Types; -using osu.Game.Modes.Taiko.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Objects; using System; using System.Collections.Generic; using System.Linq; using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Audio; +using osu.Game.Rulesets.Beatmaps; -namespace osu.Game.Modes.Taiko.Beatmaps +namespace osu.Game.Rulesets.Taiko.Beatmaps { - internal class TaikoBeatmapConverter : IBeatmapConverter + internal class TaikoBeatmapConverter : BeatmapConverter { /// /// osu! is generally slower than taiko, so a factor is added to increase @@ -38,31 +39,37 @@ namespace osu.Game.Modes.Taiko.Beatmaps /// private const float taiko_base_distance = 100; - public Beatmap Convert(Beatmap original) + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) }; + + protected override Beatmap ConvertBeatmap(Beatmap original) { + // Rewrite the beatmap info to add the slider velocity multiplier BeatmapInfo info = original.BeatmapInfo.DeepClone(); info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; - return new Beatmap(original) + Beatmap converted = base.ConvertBeatmap(original); + + // Post processing step to transform hit objects with the same start time into strong hits + converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => { - BeatmapInfo = info, - HitObjects = original.HitObjects.SelectMany(h => convertHitObject(h, original)).ToList() - }; + TaikoHitObject first = x.First(); + if (x.Skip(1).Any()) + first.IsStrong = true; + return first; + }).ToList(); + + return converted; } - private IEnumerable convertHitObject(HitObject obj, Beatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) { - // Check if this HitObject is already a TaikoHitObject, and return it if so - var originalTaiko = obj as TaikoHitObject; - if (originalTaiko != null) - yield return originalTaiko; - var distanceData = obj as IHasDistance; var repeatsData = obj as IHasRepeats; var endTimeData = obj as IHasEndTime; + var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information - List samples = obj.Samples; + SampleInfoList samples = obj.Samples; bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); @@ -92,20 +99,39 @@ namespace osu.Game.Modes.Taiko.Beatmaps 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; + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats); if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - for (double j = obj.StartTime; j <= distanceData.EndTime + tickSpacing; j += tickSpacing) + List allSamples = curveData != null ? curveData.RepeatSamples : new List(new[] { samples }); + + int i = 0; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; 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 + SampleInfoList currentSamples = allSamples[i]; + bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); + + if (isRim) { - StartTime = j, - Samples = obj.Samples, - IsStrong = strong, - }; + yield return new RimHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong + }; + } + else + { + yield return new CentreHit + { + StartTime = j, + Samples = currentSamples, + IsStrong = strong, + }; + } + + i = (i + 1) % allSamples.Count; } } else diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs similarity index 92% rename from osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs rename to osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 6ae476b265..78a5b29d36 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Taiko.Judgements +namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollTickJudgement : TaikoJudgement { diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoHitResult.cs similarity index 84% rename from osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs rename to osu.Game.Rulesets.Taiko/Judgements/TaikoHitResult.cs index cbc3919c4f..5fd850d6b0 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoHitResult.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoHitResult.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Modes.Taiko.Judgements +namespace osu.Game.Rulesets.Taiko.Judgements { public enum TaikoHitResult { diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs similarity index 93% rename from osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs rename to osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 7676ef8c29..7bca59bf11 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -1,11 +1,11 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; using osu.Framework.Extensions; -using osu.Game.Modes.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Modes.Taiko.Judgements +namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoJudgement : Judgement { diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs similarity index 84% rename from osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs rename to osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs index ee978d0026..4996cac39e 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; -namespace osu.Game.Modes.Taiko.Judgements +namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongHitJudgement : TaikoJudgement, IPartialJudgement { diff --git a/osu.Game.Modes.Taiko/Mods/TaikoMod.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs similarity index 87% rename from osu.Game.Modes.Taiko/Mods/TaikoMod.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs index 422f0ec250..0b8492ef8c 100644 --- a/osu.Game.Modes.Taiko/Mods/TaikoMod.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs @@ -2,13 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.Taiko.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Users; -namespace osu.Game.Modes.Taiko.Mods +namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModNoFail : ModNoFail { diff --git a/osu.Game.Modes.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs similarity index 80% rename from osu.Game.Modes.Taiko/Objects/BarLine.cs rename to osu.Game.Rulesets.Taiko/Objects/BarLine.cs index ae3c03de5e..0e6ff9f758 100644 --- a/osu.Game.Modes.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class BarLine : TaikoHitObject { diff --git a/osu.Game.Modes.Taiko/Objects/CentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs similarity index 79% rename from osu.Game.Modes.Taiko/Objects/CentreHit.cs rename to osu.Game.Rulesets.Taiko/Objects/CentreHit.cs index 258112f045..f82058fe01 100644 --- a/osu.Game.Modes.Taiko/Objects/CentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class CentreHit : Hit { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs similarity index 94% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLine.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index 59f8aca867..4c83e08bab 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using OpenTK; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { /// /// A line that scrolls alongside hit objects in the playfield and visualises control points. diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs similarity index 94% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLineMajor.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 73565e6948..e64682a1e4 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using OpenTK; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableBarLineMajor : DrawableBarLine { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs similarity index 83% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index ff5ac859b4..8bb78669ca 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -3,10 +3,10 @@ using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableCentreHit : DrawableHit { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs similarity index 83% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs index bc24e2aa65..434fb9377f 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs @@ -3,10 +3,10 @@ using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableCentreHitStrong : DrawableHitStrong { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs similarity index 92% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRoll.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 0a0098dd34..4562501ed1 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -5,13 +5,13 @@ 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 osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; using OpenTK; using OpenTK.Graphics; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableDrumRoll : DrawableTaikoHitObject { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs similarity index 80% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 296affedaf..56a747467e 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -3,12 +3,12 @@ using System; using osu.Framework.Graphics; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; using OpenTK.Input; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableDrumRollTick : DrawableTaikoHitObject { @@ -27,11 +27,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables protected override void CheckJudgement(bool userTriggered) { if (!userTriggered) - { - if (Judgement.TimeOffset > HitObject.HitWindow) - Judgement.Result = HitResult.Miss; return; - } if (Math.Abs(Judgement.TimeOffset) < HitObject.HitWindow) { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 167fbebd7b..a4a46e3b48 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -4,12 +4,12 @@ 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 osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public abstract class DrawableHit : DrawableTaikoHitObject { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs similarity index 90% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs index 4ab029acb3..1c6b12ea43 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -4,12 +4,12 @@ using System; using System.Linq; using osu.Framework.Input; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; using OpenTK.Input; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public abstract class DrawableHitStrong : DrawableHit { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs similarity index 83% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 5a311d51ef..20e8d36105 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -3,10 +3,10 @@ using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableRimHit : DrawableHit { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs similarity index 83% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHitStrong.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs index 5789dfb140..4b1bb62bab 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableRimHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs @@ -3,10 +3,10 @@ using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableRimHitStrong : DrawableHitStrong { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index e1a590a025..37efd8aba4 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -9,14 +9,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK; using OpenTK.Graphics; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableSwell : DrawableTaikoHitObject { @@ -65,7 +65,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables 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 [] @@ -82,7 +82,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables 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, @@ -148,9 +148,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables 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(); + using (expandingRing.BeginDelayedSequence(50)) + expandingRing.FadeTo(completion / 8, 2000, EasingTypes.OutQuint); symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, EasingTypes.OutQuint); diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index f15f2bd152..24aa366944 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Taiko.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Objects.Drawables +namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public abstract class DrawableTaikoHitObject : DrawableHitObject where TaikoHitType : TaikoHitObject diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs similarity index 90% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs index 0cf4e97b41..ddf1492ecc 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using OpenTK; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.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.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs similarity index 89% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index f921511e22..9f91488fe3 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Backgrounds; using OpenTK.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { /// /// A circle piece which is used uniformly through osu!taiko to visualise hitobjects. @@ -19,15 +19,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces /// public class CirclePiece : TaikoPiece { - public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; + 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 amount to scale up the base circle to show it as a "strong" piece. - /// - private const float strong_scale = 1.5f; - /// /// The colour of the inner circle and outer glows. /// @@ -129,10 +124,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces if (isStrong) { - Size *= strong_scale; + Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; //default for symbols etc. - Content.Scale *= strong_scale; + Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs similarity index 88% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs index 5431507614..bed54d358e 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs @@ -3,14 +3,13 @@ using System; using osu.Framework.Graphics.Primitives; -using osu.Game.Modes.Taiko.UI; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.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 . + /// As we are being used to define the absolute size of hits, we need to be given a relative reference of our containing playfield container. /// public Func PlayfieldLengthReference; @@ -38,4 +37,4 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces Width = (PlayfieldLengthReference?.Invoke() ?? 0) * Length + DrawHeight; } } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs index 6e19497978..4146edbdf7 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { /// /// The symbol used for rim hit pieces. diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs similarity index 88% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index e491793902..0f703837a9 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.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.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs similarity index 85% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index a0c8865c59..83b2e59e44 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -6,7 +6,7 @@ using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { public class TaikoPiece : Container, IHasAccentColour { @@ -39,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces public TaikoPiece() { //just a default - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); } } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs similarity index 84% rename from osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs rename to osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 697102eb22..1a0d0156e8 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; -namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces +namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { public class TickPiece : TaikoPiece { @@ -15,12 +15,12 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces /// Any tick that is not the first for a drumroll is not filled, but is instead displayed /// as a hollow circle. This is what controls the border width of that circle. /// - private const float tick_border_width = TaikoHitObject.CIRCLE_RADIUS / 2 / 4; + private const float tick_border_width = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 16; /// /// The size of a tick. /// - private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2; + private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 6; private bool filled; public bool Filled diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/DrumRoll.cs rename to osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 4f26ffd3a1..f79c01b643 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Audio; -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class DrumRoll : TaikoHitObject, IHasEndTime { @@ -82,12 +82,12 @@ namespace osu.Game.Modes.Taiko.Objects TickSpacing = tickSpacing, StartTime = t, IsStrong = IsStrong, - Samples = Samples.Select(s => new SampleInfo + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo { Bank = s.Bank, Name = @"slidertick", Volume = s.Volume - }).ToList() + })) }); first = false; diff --git a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs similarity index 91% rename from osu.Game.Modes.Taiko/Objects/DrumRollTick.cs rename to osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 32e8851b66..01f9caf215 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class DrumRollTick : TaikoHitObject { diff --git a/osu.Game.Modes.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs similarity index 93% rename from osu.Game.Modes.Taiko/Objects/Hit.cs rename to osu.Game.Rulesets.Taiko/Objects/Hit.cs index ad8d07d901..136e89124c 100644 --- a/osu.Game.Modes.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -4,7 +4,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Database; -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class Hit : TaikoHitObject { diff --git a/osu.Game.Modes.Taiko/Objects/RimHit.cs b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs similarity index 79% rename from osu.Game.Modes.Taiko/Objects/RimHit.cs rename to osu.Game.Rulesets.Taiko/Objects/RimHit.cs index aae93ec10d..8e09842294 100644 --- a/osu.Game.Modes.Taiko/Objects/RimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class RimHit : Hit { diff --git a/osu.Game.Modes.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs similarity index 82% rename from osu.Game.Modes.Taiko/Objects/Swell.cs rename to osu.Game.Rulesets.Taiko/Objects/Swell.cs index 97101ea797..f74a543ca9 100644 --- a/osu.Game.Modes.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.Taiko.Objects { public class Swell : TaikoHitObject, IHasEndTime { diff --git a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs similarity index 65% rename from osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs rename to osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 54ab8c5300..6a6353fde2 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -3,16 +3,32 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Database; -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.UI; -namespace osu.Game.Modes.Taiko.Objects +namespace osu.Game.Rulesets.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; + + /// + /// Scale multiplier for a strong circle. + /// + 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. diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs new file mode 100644 index 0000000000..c8bb73abbb --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs @@ -0,0 +1,127 @@ +// 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.Rulesets.Taiko.Objects +{ + internal class TaikoHitObjectDifficulty + { + /// + /// Factor by how much individual / overall strain decays per second. + /// + /// + /// These values are results of tweaking a lot and taking into account general feedback. + /// + internal const double DECAY_BASE = 0.30; + + private const double type_change_bonus = 0.75; + private const double rhythm_change_bonus = 1.0; + private const double rhythm_change_base_threshold = 0.2; + private const double rhythm_change_base = 2.0; + + internal TaikoHitObject BaseHitObject; + + /// + /// Measures note density in a way + /// + internal double Strain = 1; + + private double timeElapsed; + private int sameTypeSince = 1; + + private bool isRim => BaseHitObject is RimHit; + + public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) + { + BaseHitObject = baseHitObject; + } + + internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) + { + // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. + // See Taiko feedback thread. + timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; + double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); + + double addition = 1; + + // Only if we are no slider or spinner we get an extra addition + if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit + && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past + { + addition += typeChangeAddition(previousHitObject); + addition += rhythmChangeAddition(previousHitObject); + } + + double additionFactor = 1.0; + // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50 + if (timeElapsed < 50.0) + additionFactor = 0.4 + 0.6 * timeElapsed / 50.0; + + Strain = previousHitObject.Strain * decay + addition * additionFactor; + } + + private TypeSwitch lastTypeSwitchEven = TypeSwitch.None; + private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject) + { + // If we don't have the same hit type, trigger a type change! + if (previousHitObject.isRim != isRim) + { + lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd; + + // We only want a bonus if the parity of the type switch changes! + switch (previousHitObject.lastTypeSwitchEven) + { + case TypeSwitch.Even: + if (lastTypeSwitchEven == TypeSwitch.Odd) + return type_change_bonus; + break; + case TypeSwitch.Odd: + if (lastTypeSwitchEven == TypeSwitch.Even) + return type_change_bonus; + break; + } + } + // No type change? Increment counter and keep track of last type switch + else + { + lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven; + sameTypeSince = previousHitObject.sameTypeSince + 1; + } + + return 0; + } + + private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject) + { + // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time. + if (timeElapsed == 0 || previousHitObject.timeElapsed == 0) + return 0; + + double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed); + + if (timeElapsedRatio >= 8) + return 0; + + double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; + + if (isWithinChangeThreshold(difference)) + return rhythm_change_bonus; + + return 0; + } + + private bool isWithinChangeThreshold(double value) + { + return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold; + } + + private enum TypeSwitch + { + None, + Even, + Odd + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/OpenTK.dll.config b/osu.Game.Rulesets.Taiko/OpenTK.dll.config similarity index 100% rename from osu.Game.Modes.Taiko/OpenTK.dll.config rename to osu.Game.Rulesets.Taiko/OpenTK.dll.config diff --git a/osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs similarity index 90% rename from osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs rename to osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs index 94ec895707..89c07517ca 100644 --- a/osu.Game.Modes.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs @@ -7,11 +7,11 @@ using System.Runtime.InteropServices; // 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")] +[assembly: AssemblyTitle("osu.Game.Rulesets.Taiko")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("osu.Game.Modes.Taiko")] +[assembly: AssemblyProduct("osu.Game.Rulesets.Taiko")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs similarity index 76% rename from osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs rename to osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs index 89d974baf9..d78e8af589 100644 --- a/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs @@ -3,14 +3,16 @@ using System; using osu.Game.Beatmaps; -using osu.Game.Modes.Objects.Types; -using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.Replays; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Replays; -namespace osu.Game.Modes.Taiko.Replays +namespace osu.Game.Rulesets.Taiko.Replays { public class TaikoAutoReplay : Replay { + private const double swell_hit_speed = 50; + private readonly Beatmap beatmap; public TaikoAutoReplay(Beatmap beatmap) @@ -24,8 +26,8 @@ namespace osu.Game.Modes.Taiko.Replays { bool hitButton = true; - Frames.Add(new ReplayFrame(-100000, 320, 240, ReplayButtonState.None)); - Frames.Add(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 320, 240, ReplayButtonState.None)); + Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); + Frames.Add(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None)); for (int i = 0; i < beatmap.HitObjects.Count; i++) { @@ -45,12 +47,13 @@ namespace osu.Game.Modes.Taiko.Replays int d = 0; int count = 0; int req = swell.RequiredHits; - double hitRate = swell.Duration / req; + double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); for (double j = h.StartTime; j < endTime; j += hitRate) { switch (d) { default: + case 0: button = ReplayButtonState.Left1; break; case 1: @@ -64,9 +67,9 @@ namespace osu.Game.Modes.Taiko.Replays break; } - Frames.Add(new ReplayFrame(j, 0, 0, button)); + Frames.Add(new ReplayFrame(j, null, null, button)); d = (d + 1) % 4; - if (++count > req) + if (++count == req) break; } } @@ -74,7 +77,7 @@ namespace osu.Game.Modes.Taiko.Replays { foreach (var tick in drumRoll.Ticks) { - Frames.Add(new ReplayFrame(tick.StartTime, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); + Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); hitButton = !hitButton; } } @@ -95,22 +98,22 @@ namespace osu.Game.Modes.Taiko.Replays button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2; } - Frames.Add(new ReplayFrame(h.StartTime, 0, 0, button)); + Frames.Add(new ReplayFrame(h.StartTime, null, null, button)); } else throw new Exception("Unknown hit object type."); - Frames.Add(new ReplayFrame(endTime + 1, 0, 0, ReplayButtonState.None)); + Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None)); if (i < beatmap.HitObjects.Count - 1) { double waitTime = beatmap.HitObjects[i + 1].StartTime - 1000; if (waitTime > endTime) - Frames.Add(new ReplayFrame(waitTime, 0, 0, ReplayButtonState.None)); + Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None)); } hitButton = !hitButton; } } } -} \ No newline at end of file +} diff --git a/osu.Game.Modes.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs similarity index 90% rename from osu.Game.Modes.Taiko/Replays/TaikoFramedReplayInputHandler.cs rename to osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 44fca4abe7..f6425dd66f 100644 --- a/osu.Game.Modes.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.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.Game.Modes.Replays; +using osu.Game.Rulesets.Replays; using System.Collections.Generic; using osu.Framework.Input; using OpenTK.Input; -namespace osu.Game.Modes.Taiko.Replays +namespace osu.Game.Rulesets.Taiko.Replays { internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler { diff --git a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs similarity index 95% rename from osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs rename to osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 987c3181a4..f5e2094cbf 100644 --- a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -4,14 +4,14 @@ using System; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; using OpenTK; -namespace osu.Game.Modes.Taiko.Scoring +namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs new file mode 100644 index 0000000000..33e9510f1c --- /dev/null +++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs @@ -0,0 +1,140 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using System.Collections.Generic; +using System.Globalization; +using System; + +namespace osu.Game.Rulesets.Taiko +{ + internal class TaikoDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.04125; + + /// + /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. + /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. + /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. + /// + private const double strain_step = 400; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + private const double decay_weight = 0.9; + + /// + /// HitObjects are stored as a member variable. + /// + private readonly List difficultyHitObjects = new List(); + + public TaikoDifficultyCalculator(Beatmap beatmap) + : base(beatmap) + { + } + + protected override double CalculateInternal(Dictionary categoryDifficulty) + { + // Fill our custom DifficultyHitObject class, that carries additional information + difficultyHitObjects.Clear(); + + foreach (var hitObject in Objects) + difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject)); + + // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. + difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); + + if (!calculateStrainValues()) return 0; + + double starRating = calculateDifficulty() * star_scaling_factor; + + if (categoryDifficulty != null) + { + categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture)); + categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture)); + } + + return starRating; + } + + private bool calculateStrainValues() + { + // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. + using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) + { + if (!hitObjectsEnumerator.MoveNext()) return false; + + TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current; + + // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. + while (hitObjectsEnumerator.MoveNext()) + { + var next = hitObjectsEnumerator.Current; + next?.CalculateStrains(current, TimeRate); + current = next; + } + + return true; + } + } + + private double calculateDifficulty() + { + double actualStrainStep = strain_step * TimeRate; + + // Find the highest strain value within each strain step + List highestStrains = new List(); + double intervalEndTime = actualStrainStep; + double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval + + TaikoHitObjectDifficulty previousHitObject = null; + foreach (var hitObject in difficultyHitObjects) + { + // While we are beyond the current interval push the currently available maximum to our strain list + while (hitObject.BaseHitObject.StartTime > intervalEndTime) + { + highestStrains.Add(maximumStrain); + + // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay + // until the beginning of the next interval. + if (previousHitObject == null) + { + maximumStrain = 0; + } + else + { + double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); + maximumStrain = previousHitObject.Strain * decay; + } + + // Go to the next time interval + intervalEndTime += actualStrainStep; + } + + // Obtain maximum strain + maximumStrain = Math.Max(hitObject.Strain, maximumStrain); + + previousHitObject = hitObject; + } + + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } + + return difficulty; + } + + protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs similarity index 88% rename from osu.Game.Modes.Taiko/TaikoRuleset.cs rename to osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 1b3c3fc0eb..3fb2cf6c28 100644 --- a/osu.Game.Modes.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -4,16 +4,16 @@ using OpenTK.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Taiko.Mods; -using osu.Game.Modes.Taiko.UI; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using System.Collections.Generic; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.Taiko.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Scoring; -namespace osu.Game.Modes.Taiko +namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { @@ -76,8 +76,6 @@ namespace osu.Game.Modes.Taiko } } - protected override PlayMode PlayMode => PlayMode.Taiko; - public override string Description => "osu!taiko"; public override FontAwesome Icon => FontAwesome.fa_osu_taiko_o; @@ -93,5 +91,7 @@ namespace osu.Game.Modes.Taiko public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); + + public override int LegacyID => 1; } } diff --git a/osu.Game.Modes.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs similarity index 87% rename from osu.Game.Modes.Taiko/UI/DrawableTaikoJudgement.cs rename to osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 78c9657b40..08fd8dbecc 100644 --- a/osu.Game.Modes.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.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.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; -namespace osu.Game.Modes.Taiko.UI +namespace osu.Game.Rulesets.Taiko.UI { /// /// Text that is shown as judgement when a hit object is hit or missed. diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs similarity index 79% rename from osu.Game.Modes.Taiko/UI/HitExplosion.cs rename to osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index eb43c1a5d0..2ebdeaa5b0 100644 --- a/osu.Game.Modes.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -8,21 +8,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; -namespace osu.Game.Modes.Taiko.UI +namespace osu.Game.Rulesets.Taiko.UI { /// /// A circle explodes from the hit target to indicate a hitobject has been hit. /// internal class HitExplosion : CircularContainer { - /// - /// The size multiplier of a hit explosion if a hit object has been hit with the second key. - /// - private const float secondhit_size_multiplier = 1.5f; - /// /// The judgement this hit explosion visualises. /// @@ -34,7 +29,7 @@ namespace osu.Game.Modes.Taiko.UI { Judgement = judgement; - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -85,7 +80,7 @@ namespace osu.Game.Modes.Taiko.UI /// public void VisualiseSecondHit() { - ResizeTo(Size * secondhit_size_multiplier, 50); + ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50); } } } diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs similarity index 78% rename from osu.Game.Modes.Taiko/UI/HitTarget.cs rename to osu.Game.Rulesets.Taiko/UI/HitTarget.cs index a17480628d..fde2623246 100644 --- a/osu.Game.Modes.Taiko/UI/HitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs @@ -6,25 +6,15 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Modes.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects; -namespace osu.Game.Modes.Taiko.UI +namespace osu.Game.Rulesets.Taiko.UI { /// /// A component that is displayed at the hit position in the taiko playfield. /// internal class HitTarget : Container { - /// - /// Diameter of normal hit object circles. - /// - private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2; - - /// - /// Diameter of strong hit object circles. - /// - private const float strong_hit_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,7 +37,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_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 @@ -55,7 +45,7 @@ namespace osu.Game.Modes.Taiko.UI Name = "Strong Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(strong_hit_diameter), + Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -75,7 +65,7 @@ namespace osu.Game.Modes.Taiko.UI 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 - strong_hit_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.Rulesets.Taiko/UI/InputDrum.cs similarity index 95% rename from osu.Game.Modes.Taiko/UI/InputDrum.cs rename to osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 0c1e1105cb..999d76ab0b 100644 --- a/osu.Game.Modes.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Game.Graphics; -namespace osu.Game.Modes.Taiko.UI +namespace osu.Game.Rulesets.Taiko.UI { /// /// A component of the playfield that captures input and displays input as a drum. @@ -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; diff --git a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs similarity index 54% rename from osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs rename to osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs index 29fa693d58..db15193ce5 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs @@ -2,23 +2,23 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.MathUtils; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; -using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Objects.Types; -using osu.Game.Modes.Replays; -using osu.Game.Modes.Scoring; -using osu.Game.Modes.Taiko.Beatmaps; -using osu.Game.Modes.Taiko.Judgements; -using osu.Game.Modes.Taiko.Objects; -using osu.Game.Modes.Taiko.Objects.Drawables; -using osu.Game.Modes.Taiko.Scoring; -using osu.Game.Modes.UI; -using osu.Game.Modes.Taiko.Replays; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Taiko.Replays; +using OpenTK; +using osu.Game.Rulesets.Beatmaps; -namespace osu.Game.Modes.Taiko.UI +namespace osu.Game.Rulesets.Taiko.UI { public class TaikoHitRenderer : HitRenderer { @@ -49,62 +49,53 @@ namespace osu.Game.Modes.Taiko.UI 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; + double time = timingPoints[currentIndex].Time; while (time <= lastHitTime) { - ControlPoint current = timingPoints[currentIndex]; - - if (time > current.Time || current.OmitFirstBarLine) + int nextIndex = currentIndex + 1; + if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) { - 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++; + currentIndex = nextIndex; + time = timingPoints[currentIndex].Time; + currentBeat = 0; } - double bl = current.BeatLength; + var currentPoint = timingPoints[currentIndex]; + var barLine = new BarLine + { + StartTime = time, + }; + + barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty); + + bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; + taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); + + double bl = currentPoint.BeatLength; if (bl < 800) - bl *= (int)current.TimeSignature; + bl *= (int)currentPoint.TimeSignature; time += bl; - - if (currentIndex + 1 >= timingPoints.Count || time < timingPoints[currentIndex + 1].Time) - continue; - - currentBeat = 0; - currentIndex++; - time = timingPoints[currentIndex].Time; + currentBeat++; } } + 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 BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override Playfield CreatePlayfield() => new TaikoPlayfield { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs new file mode 100644 index 0000000000..8e6f1c8556 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -0,0 +1,273 @@ +// 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.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Primitives; +using System.Linq; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using System; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class TaikoPlayfield : Playfield + { + /// + /// The default play field height. + /// + 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.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40; + + /// + /// The size of the left area of the playfield. This area contains the input drum. + /// + private const float left_area_size = 240; + + protected override Container Content => hitObjectContainer; + + private readonly Container hitExplosionContainer; + private readonly Container barLineContainer; + private readonly Container judgementContainer; + + private readonly Container hitObjectContainer; + private readonly Container topLevelHitContainer; + private readonly Container leftBackgroundContainer; + private readonly Container rightBackgroundContainer; + private readonly Box leftBackground; + private readonly Box rightBackground; + + public TaikoPlayfield() + { + AddInternal(new Drawable[] + { + rightBackgroundContainer = new Container + { + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + BorderThickness = 2, + Masking = true, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + rightBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f + }, + } + }, + new ScaleFixContainer + { + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_HEIGHT, + Children = new[] + { + new Container + { + Name = "Transparent playfield elements", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + 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 + }, + }, + }, + } + }, + 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, + }, + 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)), + }, + } + }, + } + }, + topLevelHitContainer = new Container + { + Name = "Top level hit objects", + RelativeSizeAxes = Axes.Both, + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + leftBackgroundContainer.BorderColour = colours.Gray0; + leftBackground.Colour = colours.Gray1; + + rightBackgroundContainer.BorderColour = colours.Gray1; + rightBackground.Colour = colours.Gray0; + } + + public override void Add(DrawableHitObject h) + { + h.Depth = (float)h.HitObject.StartTime; + + base.Add(h); + + // Swells should be moved at the very top of the playfield when they reach the hit target + var swell = h as DrawableSwell; + if (swell != null) + swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); + } + + public void AddBarLine(DrawableBarLine barLine) + { + barLineContainer.Add(barLine); + } + + public override void OnJudgement(DrawableHitObject judgedObject) + { + bool wasHit = judgedObject.Judgement.Result == HitResult.Hit; + bool secondHit = judgedObject.Judgement.SecondHit; + + judgementContainer.Add(new DrawableTaikoJudgement(judgedObject.Judgement) + { + Anchor = wasHit ? Anchor.TopLeft : Anchor.CentreLeft, + Origin = wasHit ? Anchor.BottomCentre : Anchor.Centre, + 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.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj similarity index 93% rename from osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj rename to osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index d0981c2500..f890e32f90 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -7,8 +7,8 @@ {F167E17A-7DE6-4AF5-B920-A5112296C695} Library Properties - osu.Game.Modes.Taiko - osu.Game.Modes.Taiko + osu.Game.Rulesets.Taiko + osu.Game.Rulesets.Taiko v4.5 512 @@ -21,6 +21,7 @@ prompt 4 false + 6 pdbonly @@ -33,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 @@ -48,7 +48,6 @@ - @@ -82,6 +81,7 @@ + @@ -112,7 +112,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 8183bc952e..4814af984e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs @@ -6,12 +6,10 @@ using NUnit.Framework; using OpenTK; using OpenTK.Graphics; using osu.Game.Beatmaps.Formats; -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; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Tests.Beatmaps.Formats { @@ -22,7 +20,6 @@ namespace osu.Game.Tests.Beatmaps.Formats public void SetUp() { OsuLegacyDecoder.Register(); - Ruleset.Register(new OsuRuleset()); } [Test] @@ -58,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(false, beatmapInfo.Countdown); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.AreEqual(PlayMode.Osu, beatmapInfo.Mode); + Assert.IsTrue(beatmapInfo.RulesetID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); } @@ -133,16 +130,22 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) { var beatmap = decoder.Decode(new StreamReader(stream)); - var slider = beatmap.HitObjects[0] as LegacySlider; - Assert.IsNotNull(slider); - Assert.AreEqual(new Vector2(192, 168), slider.Position); - Assert.AreEqual(956, slider.StartTime); - 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.IsTrue(hit.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + + var curveData = beatmap.HitObjects[0] as IHasCurve; + var positionData = beatmap.HitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); + Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + + positionData = beatmap.HitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); + Assert.IsTrue(beatmap.HitObjects[1].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 5d15b43761..b35f5901be 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -12,11 +12,6 @@ using osu.Framework.Desktop.Platform; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.IPC; -using osu.Game.Modes; -using osu.Game.Modes.Catch; -using osu.Game.Modes.Mania; -using osu.Game.Modes.Osu; -using osu.Game.Modes.Taiko; namespace osu.Game.Tests.Beatmaps.IO { @@ -25,15 +20,6 @@ namespace osu.Game.Tests.Beatmaps.IO { private const string osz_path = @"../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; - [OneTimeSetUp] - public void SetUp() - { - Ruleset.Register(new OsuRuleset()); - Ruleset.Register(new TaikoRuleset()); - Ruleset.Register(new ManiaRuleset()); - Ruleset.Register(new CatchRuleset()); - } - [Test] public void TestImportWhenClosed() { @@ -119,6 +105,7 @@ namespace osu.Game.Tests.Beatmaps.IO Thread.Sleep(1); //reset beatmap database (sqlite and storage backing) + osu.Dependencies.Get().Reset(); osu.Dependencies.Get().Reset(); return osu; @@ -166,8 +153,16 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(set.Beatmaps.Count > 0); - var beatmap = osu.Dependencies.Get().GetWorkingBeatmap(set.Beatmaps.First(b => b.Mode == PlayMode.Osu))?.Beatmap; + var beatmap = osu.Dependencies.Get().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + beatmap = osu.Dependencies.Get().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + + beatmap = osu.Dependencies.Get().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap; + Assert.IsTrue(beatmap?.HitObjects.Count > 0); + + beatmap = osu.Dependencies.Get().GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); } } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index b9c4cf780a..03d09e24e0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -4,8 +4,6 @@ using System.IO; using NUnit.Framework; 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; @@ -19,7 +17,6 @@ namespace osu.Game.Tests.Beatmaps.IO public void SetUp() { OszArchiveReader.Register(); - Ruleset.Register(new OsuRuleset()); } [Test] diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index d01aa77e02..b8fcb80aaf 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -19,6 +19,7 @@ 4 false false + 6 true @@ -29,13 +30,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 @@ -64,21 +63,21 @@ {c76bf5b3-985e-4d39-95fe-97c9c879b83a} osu.Framework - + {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Modes.Osu + osu.Game.Rulesets.Osu - + {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Modes.Catch + osu.Game.Rulesets.Catch - + {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Modes.Mania + osu.Game.Rulesets.Mania - + {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Modes.Taiko + osu.Game.Rulesets.Taiko {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} 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/SampleInfoList.cs b/osu.Game/Audio/SampleInfoList.cs new file mode 100644 index 0000000000..594341bbb1 --- /dev/null +++ b/osu.Game/Audio/SampleInfoList.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Audio +{ + public class SampleInfoList : List + { + public SampleInfoList() + { + } + + public SampleInfoList(IEnumerable elements) + { + AddRange(elements); + } + } +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 5709bdc8c5..3964fd25a7 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -4,8 +4,7 @@ using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Database; -using osu.Game.Modes; -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; using System.Collections.Generic; namespace osu.Game.Beatmaps @@ -50,12 +49,6 @@ namespace osu.Game.Beatmaps /// public class Beatmap : Beatmap { - /// - /// Calculates the star difficulty for this Beatmap. - /// - /// The star difficulty. - public double CalculateStarDifficulty() => Ruleset.GetRuleset(BeatmapInfo.Mode).CreateDifficultyCalculator(this).Calculate(); - /// /// Constructs a new beatmap. /// @@ -63,6 +56,7 @@ namespace osu.Game.Beatmaps public Beatmap(Beatmap original = null) : base(original) { + HitObjects = original?.HitObjects; } } } diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index a9da5c589c..8e9266b644 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.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.Modes.Objects; +using osu.Game.Rulesets.Beatmaps; +using osu.Game.Rulesets.Objects; using System.Collections.Generic; namespace osu.Game.Beatmaps @@ -34,6 +35,10 @@ namespace osu.Game.Beatmaps protected DifficultyCalculator(Beatmap beatmap) { Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects; + + foreach (var h in Objects) + h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty); + PreprocessHitObjects(); } @@ -41,6 +46,6 @@ namespace osu.Game.Beatmaps { } - protected abstract IBeatmapConverter CreateBeatmapConverter(); + protected abstract BeatmapConverter CreateBeatmapConverter(); } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index 534578337f..db14a48af1 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -1,19 +1,18 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; -using osu.Game.Configuration; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; namespace osu.Game.Beatmaps.Drawables { @@ -23,8 +22,6 @@ namespace osu.Game.Beatmaps.Drawables private readonly SpriteText title; private readonly SpriteText artist; - private Bindable preferUnicode; - private readonly WorkingBeatmap beatmap; private readonly FillFlowContainer difficultyIcons; @@ -82,15 +79,10 @@ namespace osu.Game.Beatmaps.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(LocalisationEngine localisation) { - preferUnicode = config.GetBindable(OsuConfig.ShowUnicode); - 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(); + title.Current = localisation.GetUnicodePreference(beatmap.BeatmapSetInfo.Metadata.TitleUnicode, beatmap.BeatmapSetInfo.Metadata.Title); + artist.Current = localisation.GetUnicodePreference(beatmap.BeatmapSetInfo.Metadata.ArtistUnicode, beatmap.BeatmapSetInfo.Metadata.Artist); } private class PanelBackground : BufferedContainer @@ -112,7 +104,7 @@ namespace osu.Game.Beatmaps.Drawables Depth = -1, Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.Both, - // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle + // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), Alpha = 0.5f, Children = new[] diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs new file mode 100644 index 0000000000..7c0aa49d2a --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -0,0 +1,69 @@ +// 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.Graphics.Containers; +using osu.Game.Database; +using osu.Game.Graphics; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + internal class DifficultyColouredContainer : Container, IHasAccentColour + { + public Color4 AccentColour { get; set; } + + private readonly BeatmapInfo beatmap; + private OsuColour palette; + + public DifficultyColouredContainer(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load(OsuColour palette) + { + this.palette = palette; + AccentColour = getColour(beatmap); + } + + private enum DifficultyRating + { + Easy, + Normal, + Hard, + Insane, + Expert + } + + private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) + { + var rating = beatmap.StarDifficulty; + + if (rating < 1.5) return DifficultyRating.Easy; + if (rating < 2.25) return DifficultyRating.Normal; + if (rating < 3.75) return DifficultyRating.Hard; + if (rating < 5.25) return DifficultyRating.Insane; + return DifficultyRating.Expert; + } + + private Color4 getColour(BeatmapInfo beatmap) + { + switch (getDifficultyRating(beatmap)) + { + case DifficultyRating.Easy: + return palette.Green; + default: + case DifficultyRating.Normal: + return palette.Yellow; + case DifficultyRating.Hard: + return palette.Pink; + case DifficultyRating.Insane: + return palette.Purple; + case DifficultyRating.Expert: + return palette.Gray0; + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 47ae4d7985..a8b63c2502 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -3,32 +3,27 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Modes; using OpenTK; using OpenTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - internal class DifficultyIcon : Container + + internal class DifficultyIcon : DifficultyColouredContainer { private readonly BeatmapInfo beatmap; - private OsuColour palette; - public DifficultyIcon(BeatmapInfo beatmap) + public DifficultyIcon(BeatmapInfo beatmap) : base(beatmap) { this.beatmap = beatmap; - const float size = 20; - Size = new Vector2(size); + Size = new Vector2(20); } [BackgroundDependencyLoader] - private void load(OsuColour palette) + private void load() { - this.palette = palette; - Children = new[] { new TextAwesome @@ -36,7 +31,7 @@ namespace osu.Game.Beatmaps.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, TextSize = Size.X, - Colour = getColour(beatmap), + Colour = AccentColour, Icon = FontAwesome.fa_circle }, new TextAwesome @@ -45,47 +40,9 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, TextSize = Size.X, Colour = Color4.White, - Icon = Ruleset.GetRuleset(beatmap.Mode).Icon + Icon = beatmap.Ruleset.CreateInstance().Icon } }; } - - private enum DifficultyRating - { - Easy, - Normal, - Hard, - Insane, - Expert - } - - private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) - { - var rating = beatmap.StarDifficulty; - - if (rating < 1.5) return DifficultyRating.Easy; - if (rating < 2.25) return DifficultyRating.Normal; - if (rating < 3.75) return DifficultyRating.Hard; - if (rating < 5.25) return DifficultyRating.Insane; - return DifficultyRating.Expert; - } - - private Color4 getColour(BeatmapInfo beatmap) - { - switch (getDifficultyRating(beatmap)) - { - case DifficultyRating.Easy: - return palette.Green; - default: - case DifficultyRating.Normal: - return palette.Yellow; - case DifficultyRating.Hard: - return palette.Pink; - case DifficultyRating.Insane: - return palette.Purple; - case DifficultyRating.Expert: - return palette.Gray0; - } - } } -} \ No newline at end of file +} diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 452bd595c7..cc9d367a59 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.IO; -using osu.Game.Modes.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Database; namespace osu.Game.Beatmaps.Formats diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 35d81311d2..95213417ed 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -7,9 +7,8 @@ using System.IO; using OpenTK.Graphics; using osu.Game.Beatmaps.Events; using osu.Game.Beatmaps.Timing; -using osu.Game.Modes; -using osu.Game.Modes.Objects; using osu.Game.Beatmaps.Legacy; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats { @@ -30,6 +29,8 @@ namespace osu.Game.Beatmaps.Formats // TODO: Not sure how far back to go, or differences between versions } + private ConvertHitObjectParser parser; + private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; @@ -84,7 +85,23 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.StackLeniency = float.Parse(val, NumberFormatInfo.InvariantInfo); break; case @"Mode": - beatmap.BeatmapInfo.Mode = (PlayMode)int.Parse(val); + beatmap.BeatmapInfo.RulesetID = int.Parse(val); + + switch (beatmap.BeatmapInfo.RulesetID) + { + case 0: + parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); + break; + case 1: + parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(); + break; + case 2: + parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(); + break; + case 3: + parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); + break; + } break; case @"LetterboxInBreaks": beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(val) == 1; @@ -304,8 +321,6 @@ namespace osu.Game.Beatmaps.Formats { beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion; - HitObjectParser parser = new LegacyHitObjectParser(); - Section section = Section.None; bool hasCustomColours = false; diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs deleted file mode 100644 index 72b248cfba..0000000000 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ /dev/null @@ -1,21 +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; - -namespace osu.Game.Beatmaps -{ - /// - /// Converts a Beatmap for another mode. - /// - /// The type of HitObject stored in the Beatmap. - public interface IBeatmapConverter where T : HitObject - { - /// - /// Converts a Beatmap to another mode. - /// - /// The original Beatmap. - /// The converted Beatmap. - Beatmap Convert(Beatmap original); - } -} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5bea1d0986..616128dab5 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -5,10 +5,10 @@ using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Graphics.Textures; using osu.Game.Database; -using osu.Game.Modes; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Beatmaps { @@ -18,15 +18,7 @@ namespace osu.Game.Beatmaps public readonly BeatmapSetInfo BeatmapSetInfo; - /// - /// A play mode that is preferred for this beatmap. PlayMode will become this mode where conversion is feasible, - /// or otherwise to the beatmap's default. - /// - public PlayMode? PreferredPlayMode; - - public PlayMode PlayMode => Beatmap?.BeatmapInfo?.Mode > PlayMode.Osu ? Beatmap.BeatmapInfo.Mode : PreferredPlayMode ?? PlayMode.Osu; - - public readonly Bindable> Mods = new Bindable>(); + public readonly Bindable> Mods = new Bindable>(new Mod[] { }); public readonly bool WithStoryboard; @@ -35,6 +27,18 @@ namespace osu.Game.Beatmaps BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapSetInfo; WithStoryboard = withStoryboard; + + Mods.ValueChanged += mods => applyRateAdjustments(); + } + + private void applyRateAdjustments() + { + var t = track; + if (t == null) return; + + t.ResetSpeedAdjustments(); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToClock(t); } protected abstract Beatmap GetBeatmap(); @@ -75,7 +79,11 @@ namespace osu.Game.Beatmaps { lock (trackLock) { - return track ?? (track = GetTrack()); + if (track != null) return track; + + track = GetTrack(); + applyRateAdjustments(); + return track; } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d7c61b69a..30cd31c113 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -4,7 +4,7 @@ using System; using osu.Framework.Configuration; using osu.Framework.Platform; -using osu.Game.Modes; +using osu.Game.Screens.Select; namespace osu.Game.Configuration { @@ -17,7 +17,7 @@ namespace osu.Game.Configuration Set(OsuConfig.Username, string.Empty); Set(OsuConfig.Token, string.Empty); - Set(OsuConfig.PlayMode, PlayMode.Osu); + Set(OsuConfig.Ruleset, 0, 0, int.MaxValue); Set(OsuConfig.AudioDevice, string.Empty); Set(OsuConfig.SavePassword, false); @@ -25,7 +25,7 @@ namespace osu.Game.Configuration Set(OsuConfig.MenuCursorSize, 1.0, 0.5f, 2); Set(OsuConfig.GameplayCursorSize, 1.0, 0.5f, 2); - Set(OsuConfig.DimLevel, 30, 0, 100); + Set(OsuConfig.DimLevel, 0.3, 0, 1); Set(OsuConfig.MouseDisableButtons, false); Set(OsuConfig.MouseDisableWheel, false); @@ -35,9 +35,16 @@ namespace osu.Game.Configuration Set(OsuConfig.MenuParallax, true); + Set(OsuConfig.MenuVoice, true); + Set(OsuConfig.MenuMusic, true); + + Set(OsuConfig.BeatmapDetailTab, BeatmapDetailTab.Details); + Set(OsuConfig.ShowInterface, true); Set(OsuConfig.KeyOverlay, false); + //todo: implement all settings below this line (remove the Disabled set when doing so). + Set(OsuConfig.AudioOffset, 0, -500.0, 500.0); Set(OsuConfig.MouseSpeed, 1.0).Disabled = true; Set(OsuConfig.BeatmapDirectory, @"Songs").Disabled = true; // TODO: use thi.Disabled = trues @@ -46,6 +53,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; @@ -84,7 +92,6 @@ namespace osu.Game.Configuration Set(OsuConfig.IgnoreBeatmapSamples, false).Disabled = true; Set(OsuConfig.IgnoreBeatmapSkins, false).Disabled = true; Set(OsuConfig.IgnoreList, string.Empty).Disabled = true; - Set(OsuConfig.Language, @"unknown").Disabled = true; Set(OsuConfig.AllowNowPlayingHighlights, false).Disabled = true; Set(OsuConfig.LastVersion, string.Empty).Disabled = true; Set(OsuConfig.LastVersionPermissionsFailed, string.Empty).Disabled = true; @@ -103,7 +110,6 @@ namespace osu.Game.Configuration Set(OsuConfig.ManiaSpeedBPMScale, true).Disabled = true; Set(OsuConfig.MenuTip, 0).Disabled = true; Set(OsuConfig.MouseSpeed, 1, 0.4, 6).Disabled = true; - Set(OsuConfig.Offset, 0, -300, 300).Disabled = true; Set(OsuConfig.ScoreMeterScale, 1, 0.5, 2).Disabled = true; //Set(OsuConfig.ScoreMeterScale, 1, 0.5, OsuGame.Tournament ? 10 : 2).Disabled = true; Set(OsuConfig.DistanceSpacing, 0.8, 0.1, 6).Disabled = true; @@ -144,8 +150,6 @@ namespace osu.Game.Configuration Set(OsuConfig.YahooIntegration, false).Disabled = true; Set(OsuConfig.ForceFrameFlush, false).Disabled = true; Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true; - Set(OsuConfig.MenuMusic, true).Disabled = true; - Set(OsuConfig.MenuVoice, true).Disabled = true; Set(OsuConfig.RawInput, false).Disabled = true; Set(OsuConfig.AbsoluteToOsuWindow, Get(OsuConfig.RawInput)).Disabled = true; Set(OsuConfig.ShowMenuTips, true).Disabled = true; @@ -158,16 +162,6 @@ namespace osu.Game.Configuration Set(OsuConfig.UpdateFailCount, 0).Disabled = true; //Set(OsuConfig.TreeSortMode, TreeGroupMode.Show_All).Disabled = true; //Set(OsuConfig.TreeSortMode2, TreeSortMode.Title).Disabled = true; - bool unicodeDefault = false; - switch (Get(OsuConfig.Language)) - { - case @"zh": - case @"ja": - case @"ko": - unicodeDefault = true; - break; - } - Set(OsuConfig.ShowUnicode, unicodeDefault); Set(OsuConfig.PermanentSongInfo, false).Disabled = true; Set(OsuConfig.Ticker, false).Disabled = true; Set(OsuConfig.CompatibilityContext, false).Disabled = true; @@ -175,7 +169,6 @@ namespace osu.Game.Configuration Set(OsuConfig.ConfineMouse, Get(OsuConfig.ConfineMouseToFullscreen) ? ConfineMouseMode.Fullscreen : ConfineMouseMode.Never).Disabled = true; - GetOriginalBindable(OsuConfig.SavePassword).ValueChanged += delegate { if (Get(OsuConfig.SavePassword)) Set(OsuConfig.SaveUsername, true); @@ -195,7 +188,7 @@ namespace osu.Game.Configuration public enum OsuConfig { // New osu: - PlayMode, + Ruleset, Token, // Imported from old osu: BeatmapDirectory, @@ -270,7 +263,7 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, MouseSpeed, - Offset, + AudioOffset, ScoreMeterScale, DistanceSpacing, EditorBeatDivisor, @@ -315,6 +308,7 @@ namespace osu.Game.Configuration MenuMusic, MenuVoice, MenuParallax, + BeatmapDetailTab, RawInput, AbsoluteToOsuWindow, ConfineMouse, @@ -334,11 +328,9 @@ namespace osu.Game.Configuration SaveUsername, TreeSortMode, TreeSortMode2, - ShowUnicode, PermanentSongInfo, Ticker, CompatibilityContext, CanForceOptimusCompatibility, - } } diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index 41ddd8df39..0e814dea82 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; @@ -18,37 +17,21 @@ using SQLiteNetExtensions.Extensions; namespace osu.Game.Database { - public class BeatmapDatabase + public class BeatmapDatabase : Database { - private SQLiteConnection connection { get; } - private readonly Storage storage; + private readonly RulesetDatabase rulesets; + public event Action BeatmapSetAdded; public event Action BeatmapSetRemoved; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private BeatmapIPCChannel ipc; - public BeatmapDatabase(Storage storage, IIpcHost importHost = null) + public BeatmapDatabase(Storage storage, SQLiteConnection connection, RulesetDatabase rulesets, IIpcHost importHost = null) : base(storage, connection) { - this.storage = storage; - + this.rulesets = rulesets; if (importHost != null) ipc = new BeatmapIPCChannel(importHost, this); - - if (connection == null) - { - try - { - connection = prepareConnection(); - deletePending(); - } - catch (Exception e) - { - Logger.Error(e, @"Failed to initialise the beatmap database! Trying again with a clean database..."); - storage.DeleteDatabase(@"beatmaps"); - connection = prepareConnection(); - } - } } private void deletePending() @@ -57,20 +40,20 @@ namespace osu.Game.Database { try { - storage.Delete(b.Path); + Storage.Delete(b.Path); GetChildren(b, true); foreach (var i in b.Beatmaps) { - if (i.Metadata != null) connection.Delete(i.Metadata); - if (i.Difficulty != null) connection.Delete(i.Difficulty); + if (i.Metadata != null) Connection.Delete(i.Metadata); + if (i.Difficulty != null) Connection.Delete(i.Difficulty); - connection.Delete(i); + Connection.Delete(i); } - if (b.Metadata != null) connection.Delete(b.Metadata); - connection.Delete(b); + if (b.Metadata != null) Connection.Delete(b.Metadata); + Connection.Delete(b); } catch (Exception e) { @@ -80,42 +63,41 @@ namespace osu.Game.Database //this is required because sqlite migrations don't work, initially inserting nulls into this field. //see https://github.com/praeclarum/sqlite-net/issues/326 - connection.Query("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL"); + Connection.Query("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL"); } - private SQLiteConnection prepareConnection() + protected override void Prepare(bool reset = false) { - var conn = storage.GetDatabase(@"beatmaps"); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); - try + if (reset) { - conn.CreateTable(); - conn.CreateTable(); - conn.CreateTable(); - conn.CreateTable(); - } - catch - { - conn.Close(); - throw; + Storage.DeleteDatabase(@"beatmaps"); + + foreach (var setInfo in Query()) + { + if (Storage.Exists(setInfo.Path)) + Storage.Delete(setInfo.Path); + } + + Connection.DeleteAll(); + Connection.DeleteAll(); + Connection.DeleteAll(); + Connection.DeleteAll(); } - return conn; + deletePending(); } - public void Reset() - { - foreach (var setInfo in Query()) - { - if (storage.Exists(setInfo.Path)) - storage.Delete(setInfo.Path); - } - - connection.DeleteAll(); - connection.DeleteAll(); - connection.DeleteAll(); - connection.DeleteAll(); - } + protected override Type[] ValidTypes => new[] { + typeof(BeatmapSetInfo), + typeof(BeatmapInfo), + typeof(BeatmapMetadata), + typeof(BeatmapDifficulty), + }; /// /// Import multiple from . @@ -174,7 +156,7 @@ namespace osu.Game.Database BeatmapMetadata metadata; - using (var reader = ArchiveReader.GetReader(storage, path)) + using (var reader = ArchiveReader.GetReader(Storage, path)) { using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0]))) metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; @@ -182,18 +164,18 @@ namespace osu.Game.Database if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader { - using (var input = storage.GetStream(path)) + using (var input = Storage.GetStream(path)) { hash = input.GetMd5Hash(); input.Seek(0, SeekOrigin.Begin); path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); - if (!storage.Exists(path)) - using (var output = storage.GetStream(path, FileAccess.Write)) + if (!Storage.Exists(path)) + using (var output = Storage.GetStream(path, FileAccess.Write)) input.CopyTo(output); } } - var existing = connection.Table().FirstOrDefault(b => b.Hash == hash); + var existing = Connection.Table().FirstOrDefault(b => b.Hash == hash); if (existing != null) { @@ -216,7 +198,7 @@ namespace osu.Game.Database Metadata = metadata }; - using (var archive = ArchiveReader.GetReader(storage, path)) + using (var archive = ArchiveReader.GetReader(Storage, path)) { string[] mapNames = archive.BeatmapFilenames; foreach (var name in mapNames) @@ -236,7 +218,9 @@ namespace osu.Game.Database // TODO: Diff beatmap metadata with set metadata and leave it here if necessary beatmap.BeatmapInfo.Metadata = null; - beatmap.BeatmapInfo.StarDifficulty = beatmap.CalculateStarDifficulty(); + // TODO: this should be done in a better place once we actually need to dynamically update it. + beatmap.BeatmapInfo.Ruleset = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID); + beatmap.BeatmapInfo.StarDifficulty = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); } @@ -248,17 +232,17 @@ namespace osu.Game.Database public void Import(IEnumerable beatmapSets) { - lock (connection) + lock (Connection) { - connection.BeginTransaction(); + Connection.BeginTransaction(); foreach (var s in beatmapSets) { - connection.InsertWithChildren(s, true); + Connection.InsertOrReplaceWithChildren(s, true); BeatmapSetAdded?.Invoke(s); } - connection.Commit(); + Connection.Commit(); } } @@ -275,7 +259,7 @@ namespace osu.Game.Database if (string.IsNullOrEmpty(beatmapSet.Path)) return null; - return ArchiveReader.GetReader(storage, beatmapSet.Path); + return ArchiveReader.GetReader(Storage, beatmapSet.Path); } public BeatmapSetInfo GetBeatmapSet(int id) @@ -287,12 +271,14 @@ namespace osu.Game.Database { var beatmapSetInfo = Query().FirstOrDefault(s => s.ID == beatmapInfo.BeatmapSetInfoID); - //we need metadata - GetChildren(beatmapSetInfo); - if (beatmapSetInfo == null) throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); + //we need metadata + GetChildren(beatmapSetInfo); + //we also need a ruleset + GetChildren(beatmapInfo); + if (beatmapInfo.Metadata == null) beatmapInfo.Metadata = beatmapSetInfo.Metadata; @@ -303,47 +289,6 @@ namespace osu.Game.Database return working; } - public TableQuery Query() where T : class - { - return connection.Table(); - } - - public T GetWithChildren(object id) where T : class - { - return connection.GetWithChildren(id); - } - - public List GetAllWithChildren(Expression> filter = null, bool recursive = true) - where T : class - { - return connection.GetAllWithChildren(filter, recursive); - } - - public T GetChildren(T item, bool recursive = false) - { - if (item == null) return default(T); - - connection.GetChildren(item, recursive); - return item; - } - - private readonly Type[] validTypes = { - typeof(BeatmapSetInfo), - typeof(BeatmapInfo), - typeof(BeatmapMetadata), - typeof(BeatmapDifficulty), - }; - - public void Update(T record, bool cascade = true) where T : class - { - if (validTypes.All(t => t != typeof(T))) - throw new ArgumentException("Must be a type managed by BeatmapDatabase", nameof(T)); - if (cascade) - connection.UpdateWithChildren(record); - else - connection.Update(record); - } - - public bool Exists(BeatmapSetInfo beatmapSet) => storage.Exists(beatmapSet.Path); + public bool Exists(BeatmapSetInfo beatmapSet) => Storage.Exists(beatmapSet.Path); } } diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index bc6e077633..5097622deb 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using osu.Game.IO.Serialization; -using osu.Game.Modes; using SQLite.Net.Attributes; using SQLiteNetExtensions.Attributes; using System; @@ -41,8 +40,12 @@ 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 @@ -50,7 +53,13 @@ namespace osu.Game.Database public bool Countdown { get; set; } public float StackLeniency { get; set; } public bool SpecialStyle { get; set; } - public PlayMode Mode { get; set; } + + [ForeignKey(typeof(RulesetInfo))] + public int RulesetID { get; set; } + + [OneToOne(CascadeOperations = CascadeOperation.All)] + public RulesetInfo Ruleset { get; set; } + public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } diff --git a/osu.Game/Database/BeatmapMetrics.cs b/osu.Game/Database/BeatmapMetrics.cs new file mode 100644 index 0000000000..25de0f0a8d --- /dev/null +++ b/osu.Game/Database/BeatmapMetrics.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using Newtonsoft.Json; + +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). + /// + [JsonProperty(@"fail")] + public IEnumerable Fails { get; set; } + + /// + /// Points of retry on a relative time scale (usually 0..100). + /// + [JsonProperty(@"exit")] + public IEnumerable Retries { get; set; } + } +} diff --git a/osu.Game/Database/BeatmapSetInfo.cs b/osu.Game/Database/BeatmapSetInfo.cs index 0ef0ba4c63..0875d3c01f 100644 --- a/osu.Game/Database/BeatmapSetInfo.cs +++ b/osu.Game/Database/BeatmapSetInfo.cs @@ -26,6 +26,7 @@ namespace osu.Game.Database public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); + [Indexed] public bool DeletePending { get; set; } public string Hash { get; set; } diff --git a/osu.Game/Database/Database.cs b/osu.Game/Database/Database.cs new file mode 100644 index 0000000000..23851b3b2e --- /dev/null +++ b/osu.Game/Database/Database.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using osu.Framework.Logging; +using osu.Framework.Platform; +using SQLite.Net; +using SQLiteNetExtensions.Extensions; + +namespace osu.Game.Database +{ + public abstract class Database + { + protected SQLiteConnection Connection { get; } + protected Storage Storage { get; } + + protected Database(Storage storage, SQLiteConnection connection) + { + Storage = storage; + Connection = connection; + + try + { + Prepare(); + } + catch (Exception e) + { + Logger.Error(e, $@"Failed to initialise the {GetType()}! Trying again with a clean database..."); + Prepare(true); + } + } + + /// + /// Prepare this database for use. + /// + protected abstract void Prepare(bool reset = false); + + /// + /// Reset this database to a default state. Undo all changes to database and storage backings. + /// + public void Reset() => Prepare(true); + + public TableQuery Query() where T : class + { + return Connection.Table(); + } + + public T GetWithChildren(object id) where T : class + { + return Connection.GetWithChildren(id); + } + + public List GetAllWithChildren(Expression> filter = null, bool recursive = true) + where T : class + { + return Connection.GetAllWithChildren(filter, recursive); + } + + public T GetChildren(T item, bool recursive = false) + { + if (item == null) return default(T); + + Connection.GetChildren(item, recursive); + return item; + } + + protected abstract Type[] ValidTypes { get; } + + public void Update(T record, bool cascade = true) where T : class + { + if (ValidTypes.All(t => t != typeof(T))) + throw new ArgumentException("Must be a type managed by BeatmapDatabase", nameof(T)); + if (cascade) + Connection.UpdateWithChildren(record); + else + Connection.Update(record); + } + } +} \ No newline at end of file diff --git a/osu.Game/Database/RulesetDatabase.cs b/osu.Game/Database/RulesetDatabase.cs new file mode 100644 index 0000000000..b78ca5ffc6 --- /dev/null +++ b/osu.Game/Database/RulesetDatabase.cs @@ -0,0 +1,103 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using osu.Framework.Platform; +using osu.Game.Rulesets; +using SQLite.Net; + +namespace osu.Game.Database +{ + /// + /// Todo: All of this needs to be moved to a RulesetDatabase. + /// + public class RulesetDatabase : Database + { + public IEnumerable AllRulesets => Query().Where(r => r.Available); + + public RulesetDatabase(Storage storage, SQLiteConnection connection) + : base(storage, connection) + { + } + + protected override void Prepare(bool reset = false) + { + Connection.CreateTable(); + + if (reset) + { + Connection.DeleteAll(); + } + + List instances = new List(); + + foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, @"osu.Game.Rulesets.*.dll")) + { + try + { + var assembly = Assembly.LoadFile(file); + var rulesets = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Ruleset))); + + if (rulesets.Count() != 1) + continue; + + foreach (Type rulesetType in rulesets) + instances.Add((Ruleset)Activator.CreateInstance(rulesetType)); + } + catch (Exception) { } + } + + Connection.BeginTransaction(); + + //add all legacy modes in correct order + foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) + { + Connection.InsertOrReplace(createRulesetInfo(r)); + } + + //add any other modes + foreach (var r in instances.Where(r => r.LegacyID < 0)) + { + var us = createRulesetInfo(r); + + var existing = Query().FirstOrDefault(ri => ri.InstantiationInfo == us.InstantiationInfo); + + if (existing == null) + Connection.Insert(us); + } + + //perform a consistency check + foreach (var r in Query()) + { + try + { + r.CreateInstance(); + r.Available = true; + } + catch + { + r.Available = false; + } + + Connection.Update(r); + } + + Connection.Commit(); + } + + private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo + { + Name = ruleset.Description, + InstantiationInfo = ruleset.GetType().AssemblyQualifiedName, + ID = ruleset.LegacyID + }; + + protected override Type[] ValidTypes => new[] { typeof(RulesetInfo) }; + + public RulesetInfo GetRuleset(int id) => Query().First(r => r.ID == id); + } +} diff --git a/osu.Game/Database/RulesetInfo.cs b/osu.Game/Database/RulesetInfo.cs new file mode 100644 index 0000000000..322cb10c33 --- /dev/null +++ b/osu.Game/Database/RulesetInfo.cs @@ -0,0 +1,26 @@ +// 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.Rulesets; +using SQLite.Net.Attributes; + +namespace osu.Game.Database +{ + public class RulesetInfo + { + [PrimaryKey, AutoIncrement] + public int? ID { get; set; } + + [Indexed(Unique = true)] + public string Name { get; set; } + + [Indexed(Unique = true)] + public string InstantiationInfo { get; set; } + + [Indexed] + public bool Available { get; set; } + + public Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo)); + } +} \ No newline at end of file diff --git a/osu.Game/Database/ScoreDatabase.cs b/osu.Game/Database/ScoreDatabase.cs index 642bb4aff6..8ea836aceb 100644 --- a/osu.Game/Database/ScoreDatabase.cs +++ b/osu.Game/Database/ScoreDatabase.cs @@ -2,31 +2,36 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Platform; using osu.Game.IO.Legacy; using osu.Game.IPC; -using osu.Game.Modes; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; using SharpCompress.Compressors.LZMA; +using SQLite.Net; namespace osu.Game.Database { - public class ScoreDatabase + public class ScoreDatabase : Database { private readonly Storage storage; + private readonly BeatmapDatabase beatmaps; + private readonly RulesetDatabase rulesets; private const string replay_folder = @"replays"; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ScoreIPCChannel ipc; - public ScoreDatabase(Storage storage, IIpcHost importHost = null, BeatmapDatabase beatmaps = null) + public ScoreDatabase(Storage storage, SQLiteConnection connection, IIpcHost importHost = null, BeatmapDatabase beatmaps = null, RulesetDatabase rulesets = null) : base(storage, connection) { this.storage = storage; this.beatmaps = beatmaps; + this.rulesets = rulesets; if (importHost != null) ipc = new ScoreIPCChannel(importHost, this); @@ -39,8 +44,10 @@ namespace osu.Game.Database using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) using (SerializationReader sr = new SerializationReader(s)) { - var ruleset = Ruleset.GetRuleset((PlayMode)sr.ReadByte()); - score = ruleset.CreateScoreProcessor().CreateScore(); + score = new Score + { + Ruleset = rulesets.GetRuleset(sr.ReadByte()) + }; /* score.Pass = true;*/ var version = sr.ReadInt32(); @@ -101,11 +108,47 @@ namespace osu.Game.Database using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) using (var reader = new StreamReader(lzma)) - score.Replay = score.CreateReplay(reader); + score.Replay = createLegacyReplay(reader); } } return score; } + + /// + /// Creates a legacy replay which is read from a stream. + /// + /// The stream reader. + /// The legacy replay. + private Replay createLegacyReplay(StreamReader reader) + { + var frames = new List(); + + float lastTime = 0; + + foreach (var l in reader.ReadToEnd().Split(',')) + { + var split = l.Split('|'); + + if (split.Length < 4 || float.Parse(split[0]) < 0) continue; + + lastTime += float.Parse(split[0]); + + frames.Add(new ReplayFrame( + lastTime, + float.Parse(split[1]), + 384 - float.Parse(split[2]), + (ReplayButtonState)int.Parse(split[3]) + )); + } + + return new Replay { Frames = frames }; + } + + protected override void Prepare(bool reset = false) + { + } + + protected override Type[] ValidTypes => new[] { typeof(Score) }; } } 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/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 0fb7f59212..8d5f95aad5 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -80,14 +80,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); } @@ -100,7 +98,7 @@ namespace osu.Game.Graphics.Cursor public Cursor() { - Size = new Vector2(42); + AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/Cursor/TooltipContainer.cs b/osu.Game/Graphics/Cursor/TooltipContainer.cs new file mode 100644 index 0000000000..5f0743746a --- /dev/null +++ b/osu.Game/Graphics/Cursor/TooltipContainer.cs @@ -0,0 +1,158 @@ +// 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.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; +using System.Linq; + +namespace osu.Game.Graphics.Cursor +{ + public class TooltipContainer : Container + { + private readonly CursorContainer cursor; + private readonly Tooltip tooltip; + + private ScheduledDelegate findTooltipTask; + private UserInputManager inputManager; + + private const int default_appear_delay = 220; + + private IHasTooltip currentlyDisplayed; + + public TooltipContainer(CursorContainer cursor) + { + this.cursor = cursor; + AlwaysPresent = true; + RelativeSizeAxes = Axes.Both; + Add(tooltip = new Tooltip { Alpha = 0 }); + } + + [BackgroundDependencyLoader] + private void load(UserInputManager input) + { + inputManager = input; + } + + protected override void Update() + { + if (tooltip.IsPresent) + { + if (currentlyDisplayed != null) + tooltip.TooltipText = currentlyDisplayed.TooltipText; + + //update the position of the displayed tooltip. + tooltip.Position = ToLocalSpace(cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre) + new Vector2(10); + } + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + updateTooltipState(state); + return base.OnMouseUp(state, args); + } + + protected override bool OnMouseMove(InputState state) + { + updateTooltipState(state); + return base.OnMouseMove(state); + } + + private void updateTooltipState(InputState state) + { + if (currentlyDisplayed?.Hovering != true) + { + if (currentlyDisplayed != null && !state.Mouse.HasMainButtonPressed) + { + tooltip.Delay(150); + tooltip.FadeOut(500, EasingTypes.OutQuint); + currentlyDisplayed = null; + } + + findTooltipTask?.Cancel(); + findTooltipTask = Scheduler.AddDelayed(delegate + { + var tooltipTarget = inputManager.HoveredDrawables.OfType().FirstOrDefault(); + + if (tooltipTarget == null) return; + + tooltip.TooltipText = tooltipTarget.TooltipText; + tooltip.FadeIn(500, EasingTypes.OutQuint); + + currentlyDisplayed = tooltipTarget; + }, (1 - tooltip.Alpha) * default_appear_delay); + } + } + + public class Tooltip : Container + { + private readonly Box background; + private readonly OsuSpriteText text; + + public string TooltipText + { + set + { + if (value == text.Text) return; + + text.Text = value; + if (Alpha > 0) + { + AutoSizeDuration = 250; + background.FlashColour(OsuColour.Gray(0.4f), 1000, EasingTypes.OutQuint); + } + else + AutoSizeDuration = 0; + } + } + + public override bool HandleInput => false; + + private const float text_size = 16; + + public Tooltip() + { + AutoSizeEasing = EasingTypes.OutQuint; + AutoSizeAxes = Axes.Both; + + CornerRadius = 5; + Masking = true; + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(40), + Radius = 5, + }; + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.9f, + }, + text = new OsuSpriteText + { + TextSize = text_size, + Padding = new MarginPadding(5), + Font = @"Exo2.0-Regular", + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + background.Colour = colour.Gray3; + } + } + } +} 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/IHasTooltip.cs b/osu.Game/Graphics/IHasTooltip.cs new file mode 100644 index 0000000000..dd51d68c41 --- /dev/null +++ b/osu.Game/Graphics/IHasTooltip.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; + +namespace osu.Game.Graphics +{ + public interface IHasTooltip : IDrawable + { + /// + /// Tooltip that shows when hovering the drawable + /// + string TooltipText { get; } + } +} diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index d8de4f6346..cd719431e7 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -80,6 +80,10 @@ namespace osu.Game.Graphics public Color4 GrayE = FromHex(@"eee"); public Color4 GrayF = FromHex(@"fff"); - public Color4 Red = FromHex(@"fc4549"); + public Color4 RedLighter = FromHex(@"ffeded"); + public Color4 RedLight = FromHex(@"ed7787"); + public Color4 Red = FromHex(@"ed1121"); + public Color4 RedDark = FromHex(@"ba0011"); + public Color4 RedDarker = FromHex(@"870000"); } } diff --git a/osu.Game/Graphics/TextAwesome.cs b/osu.Game/Graphics/TextAwesome.cs index 1bae165e45..69b0217444 100644 --- a/osu.Game/Graphics/TextAwesome.cs +++ b/osu.Game/Graphics/TextAwesome.cs @@ -817,13 +817,13 @@ namespace osu.Game.Graphics fa_youtube_play = 0xf16a, fa_youtube_square = 0xf166, - // gamemode icons in circles + // ruleset icons in circles fa_osu_osu_o = 0xe000, fa_osu_mania_o = 0xe001, fa_osu_fruits_o = 0xe002, fa_osu_taiko_o = 0xe003, - // gamemode icons without circles + // ruleset icons without circles fa_osu_filled_circle = 0xe004, fa_osu_cross_o = 0xe005, fa_osu_logo = 0xe006, 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 6a5151b90c..e81db4954e 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -23,19 +23,8 @@ 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) - Alpha = 0.3f; + Current.BindTo(bindable); } } @@ -83,18 +72,21 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Right = 5 }, } }; - } - private void bindableValueChanged(bool isChecked) - { - State = isChecked ? 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(); + }; + + Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; } protected override bool OnHover(InputState state) @@ -117,23 +109,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 fcc05822f0..59bee8baad 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..027473921f 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -2,10 +2,10 @@ // 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; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -13,15 +13,36 @@ using osu.Framework.Input; namespace osu.Game.Graphics.UserInterface { - public class OsuSliderBar : SliderBar where U : struct + public class OsuSliderBar : SliderBar, IHasTooltip where T : struct { private SampleChannel sample; private double lastSampleTime; + private T lastSampleValue; private readonly Nub nub; private readonly Box leftBox; private readonly Box rightBox; + public virtual string TooltipText + { + get + { + var bindableDouble = CurrentNumber as BindableNumber; + if (bindableDouble != null) + { + if (bindableDouble.MaxValue == 1 && (bindableDouble.MinValue == 0 || bindableDouble.MinValue == -1)) + return bindableDouble.Value.ToString(@"P0"); + return bindableDouble.Value.ToString(@"n1"); + } + + var bindableInt = CurrentNumber as BindableNumber; + if (bindableInt != null) + return bindableInt.Value.ToString(@"n0"); + + return Current.Value.ToString(); + } + } + public OsuSliderBar() { Height = 12; @@ -50,10 +71,14 @@ namespace osu.Game.Graphics.UserInterface nub = new Nub { Origin = Anchor.TopCentre, - State = CheckboxState.Unchecked, Expanded = true, } }; + + Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; } [BackgroundDependencyLoader] @@ -64,15 +89,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 +101,45 @@ 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; + + if (Current.Value.Equals(lastSampleValue)) + return; + + lastSampleValue = Current.Value; + + 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/OsuTabControlCheckbox.cs b/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/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index a54b122615..62b10b96ef 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -33,6 +33,11 @@ namespace osu.Game.Graphics.UserInterface Height = 40; TextContainer.Height = 0.5f; CornerRadius = 5; + + Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 12eeb771dd..e98867277a 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -10,10 +10,11 @@ using osu.Game.Graphics.Sprites; using System; using System.Collections.Generic; using System.Diagnostics; +using OpenTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container + public abstract class RollingCounter : Container, IHasAccentColour { /// /// The current value. @@ -80,6 +81,12 @@ namespace osu.Game.Graphics.UserInterface } } + public Color4 AccentColour + { + get { return DisplayedCountSpriteText.Colour; } + set { DisplayedCountSpriteText.Colour = value; } + } + /// /// Skeleton of a numeric counter which value rolls over time. /// @@ -108,11 +115,7 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - Flush(false, TransformType); - DisplayedCountSpriteText.Text = FormatCount(Current); - DisplayedCountSpriteText.Anchor = Anchor; - DisplayedCountSpriteText.Origin = Origin; } /// @@ -205,8 +208,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/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index c2b1b026b6..3e01b9e4f4 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -15,6 +15,8 @@ namespace osu.Game.Graphics.UserInterface protected override double RollingDuration => 1000; protected override EasingTypes RollingEasing => EasingTypes.Out; + public bool UseCommaSeparator; + /// /// How many leading zeroes the counter has. /// @@ -41,7 +43,12 @@ namespace osu.Game.Graphics.UserInterface protected override string FormatCount(double count) { - return ((long)count).ToString("D" + LeadingZeroes); + string format = new string('0', (int)LeadingZeroes); + if (UseCommaSeparator) + for (int i = format.Length - 3; i > 0; i -= 3) + format = format.Insert(i, @","); + + return ((long)count).ToString(format); } public override void Increment(double amount) diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs new file mode 100644 index 0000000000..8537e80f63 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -0,0 +1,61 @@ +// 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.Transforms; +using osu.Framework.MathUtils; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Used as an accuracy counter. Represented visually as a percentage. + /// + public class SimpleComboCounter : RollingCounter + { + protected override Type TransformType => typeof(TransformCounterCount); + + protected override double RollingDuration => 750; + + public SimpleComboCounter() + { + Current.Value = DisplayedCount = 0; + } + + protected override string FormatCount(int count) + { + return $@"{count}x"; + } + + protected override double GetProportionalDuration(int currentValue, int newValue) + { + return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; + } + + public override void Increment(int amount) + { + Current.Value = Current + amount; + } + + private class TransformCounterCount : Transform + { + public override int CurrentValue + { + get + { + double time = Time?.Current ?? 0; + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return (int)Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + } + + public override void Apply(Drawable d) + { + base.Apply(d); + ((SimpleComboCounter)d).DisplayedCount = CurrentValue; + } + } + } +} \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index c046749dad..295cdac81d 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -131,14 +131,14 @@ namespace osu.Game.Graphics.UserInterface foreach (var star in stars.Children) { star.ClearTransforms(true); - if (count <= newValue) - star.Delay(Math.Max(i - count, 0) * animationDelay, true); - else - star.Delay(Math.Max(count - 1 - i, 0) * animationDelay, true); - star.FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration); - star.Icon.ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing); - star.DelayReset(); + var delay = (count <= newValue ? Math.Max(i - count, 0) : Math.Max(count - 1 - i, 0)) * animationDelay; + + using (BeginDelayedSequence(delay, true)) + { + star.FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration); + star.Icon.ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing); + } i++; } diff --git a/osu.Game/Modes/Mods/Mod.cs b/osu.Game/Modes/Mods/Mod.cs deleted file mode 100644 index b6f09b8506..0000000000 --- a/osu.Game/Modes/Mods/Mod.cs +++ /dev/null @@ -1,175 +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; -using osu.Game.Graphics; -using osu.Game.Modes.Objects; -using osu.Game.Modes.UI; -using System; -using osu.Game.Modes.Scoring; - -namespace osu.Game.Modes.Mods -{ - /// - /// The base class for gameplay modifiers. - /// - public abstract class Mod - { - /// - /// The name of this mod. - /// - public abstract string Name { get; } - - /// - /// The icon of this mod. - /// - public virtual FontAwesome Icon => FontAwesome.fa_question; - - /// - /// The user readable description of this mod. - /// - public virtual string Description => string.Empty; - - /// - /// The score multiplier of this mod. - /// - public abstract double ScoreMultiplier { get; } - - /// - /// Returns if this mod is ranked. - /// - public virtual bool Ranked => false; - - /// - /// The mods this mod cannot be enabled with. - /// - public virtual Type[] IncompatibleMods => new Type[] { }; - } - - public class MultiMod : Mod - { - public override string Name => string.Empty; - public override string Description => string.Empty; - public override double ScoreMultiplier => 0.0; - - public Mod[] Mods; - } - - public abstract class ModNoFail : Mod - { - public override string Name => "NoFail"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; - public override string Description => "You can't fail, no matter what."; - public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; - } - - public abstract class ModEasy : Mod - { - public override string Name => "Easy"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; - public override string Description => "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."; - public override double ScoreMultiplier => 0.5; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; - } - - public abstract class ModHidden : Mod - { - public override string Name => "Hidden"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; - public override bool Ranked => true; - } - - public abstract class ModHardRock : Mod - { - public override string Name => "Hard Rock"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; - public override string Description => "Everything just got a bit harder..."; - public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; - } - - public abstract class ModSuddenDeath : Mod - { - public override string Name => "Sudden Death"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; - public override string Description => "Miss a note and fail."; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - } - - public abstract class ModDoubleTime : Mod - { - public override string Name => "Double Time"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; - public override string Description => "Zoooooooooom"; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; - } - - public abstract class ModRelax : Mod - { - public override string Name => "Relax"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; - public override double ScoreMultiplier => 0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; - } - - public abstract class ModHalfTime : Mod - { - public override string Name => "Half Time"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; - public override string Description => "Less zoom"; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; - } - - public abstract class ModNightcore : ModDoubleTime - { - public override string Name => "Nightcore"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; - public override string Description => "uguuuuuuuu"; - } - - public abstract class ModFlashlight : Mod - { - public override string Name => "Flashlight"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; - public override string Description => "Restricted view area."; - public override bool Ranked => true; - } - - public class ModAutoplay : Mod - { - public override string Name => "Autoplay"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; - public override string Description => "Watch a perfect automated play through the song"; - public override double ScoreMultiplier => 0; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; - } - - public abstract class ModAutoplay : ModAutoplay, IApplicableMod - where T : HitObject - { - protected abstract Score CreateReplayScore(Beatmap beatmap); - - public void Apply(HitRenderer hitRenderer) - { - hitRenderer.SetReplay(CreateReplayScore(hitRenderer.Beatmap)?.Replay); - } - } - - public abstract class ModPerfect : ModSuddenDeath - { - public override string Name => "Perfect"; - public override string Description => "SS or quit."; - } - - public class ModCinema : ModAutoplay - { - public override string Name => "Cinema"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; - } -} diff --git a/osu.Game/Modes/Objects/CurvedHitObject.cs b/osu.Game/Modes/Objects/CurvedHitObject.cs deleted file mode 100644 index ccb3d2497d..0000000000 --- a/osu.Game/Modes/Objects/CurvedHitObject.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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.Modes.Objects.Types; -using System.Collections.Generic; - -namespace osu.Game.Modes.Objects -{ - public class CurvedHitObject : HitObject, IHasCurve - { - public SliderCurve Curve { get; } = new SliderCurve(); - - public int RepeatCount { get; set; } = 1; - - public double EndTime => 0; - public double Duration => 0; - - public List ControlPoints - { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } - } - - public CurveType CurveType - { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } - } - - public double Distance - { - get { return Curve.Distance; } - set { Curve.Distance = value; } - } - - public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); - - public double ProgressAt(double progress) - { - var p = progress * RepeatCount % 1; - if (RepeatAt(progress) % 2 == 1) - p = 1 - p; - return p; - } - - public int RepeatAt(double progress) => (int)(progress * RepeatCount); - } -} diff --git a/osu.Game/Modes/Objects/HitObject.cs b/osu.Game/Modes/Objects/HitObject.cs deleted file mode 100644 index f362d6de63..0000000000 --- a/osu.Game/Modes/Objects/HitObject.cs +++ /dev/null @@ -1,52 +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.Audio; -using osu.Game.Beatmaps.Timing; -using osu.Game.Database; -using System.Collections.Generic; - -namespace osu.Game.Modes.Objects -{ - /// - /// A HitObject describes an object in a Beatmap. - /// - /// HitObjects may contain more properties for which you should be checking through the IHas* types. - /// - /// - public class HitObject - { - /// - /// The time at which the HitObject starts. - /// - public double StartTime { get; set; } - - /// - /// The samples to be played when this hit object is hit. - /// - 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) - { - 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/Legacy/LegacyHit.cs b/osu.Game/Modes/Objects/Legacy/LegacyHit.cs deleted file mode 100644 index 239c8982da..0000000000 --- a/osu.Game/Modes/Objects/Legacy/LegacyHit.cs +++ /dev/null @@ -1,18 +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.Types; -using OpenTK; - -namespace osu.Game.Modes.Objects.Legacy -{ - /// - /// Legacy Hit-type, used for parsing Beatmaps. - /// - public sealed class LegacyHit : HitObject, IHasPosition, IHasCombo - { - public Vector2 Position { get; set; } - - public bool NewCombo { get; set; } - } -} diff --git a/osu.Game/Modes/Objects/Legacy/LegacySlider.cs b/osu.Game/Modes/Objects/Legacy/LegacySlider.cs deleted file mode 100644 index bdfebb1983..0000000000 --- a/osu.Game/Modes/Objects/Legacy/LegacySlider.cs +++ /dev/null @@ -1,18 +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.Types; -using OpenTK; - -namespace osu.Game.Modes.Objects.Legacy -{ - /// - /// Legacy Slider-type, used for parsing Beatmaps. - /// - public sealed class LegacySlider : CurvedHitObject, IHasPosition, IHasCombo - { - public Vector2 Position { get; set; } - - public bool NewCombo { get; set; } - } -} diff --git a/osu.Game/Modes/Objects/LegacyHitObjectParser.cs b/osu.Game/Modes/Objects/LegacyHitObjectParser.cs deleted file mode 100644 index 2316e5dc5d..0000000000 --- a/osu.Game/Modes/Objects/LegacyHitObjectParser.cs +++ /dev/null @@ -1,204 +0,0 @@ -// 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.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 -{ - internal class LegacyHitObjectParser : HitObjectParser - { - public override HitObject Parse(string text) - { - string[] split = text.Split(','); - var type = (LegacyHitObjectType)int.Parse(split[3]) & ~LegacyHitObjectType.ColourHax; - 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) - { - result = new LegacyHit - { - 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) - { - CurveType curveType = CurveType.Catmull; - double length = 0; - List points = new List { new Vector2(int.Parse(split[0]), int.Parse(split[1])) }; - - string[] pointsplit = split[5].Split('|'); - foreach (string t in pointsplit) - { - if (t.Length == 1) - { - switch (t) - { - case @"C": - curveType = CurveType.Catmull; - break; - case @"B": - curveType = CurveType.Bezier; - break; - case @"L": - curveType = CurveType.Linear; - break; - case @"P": - curveType = CurveType.PerfectCurve; - break; - } - continue; - } - - string[] temp = t.Split(':'); - Vector2 v = new Vector2( - (int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), - (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture) - ); - points.Add(v); - } - - int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); - - if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); - - if (split.Length > 7) - length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); - - result = new LegacySlider - { - ControlPoints = points, - Distance = length, - CurveType = curveType, - RepeatCount = repeatCount, - 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) - { - result = new LegacySpinner - { - 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])), - NewCombo = combo - }; - } - else - throw new InvalidOperationException($@"Unknown hit object type {type}"); - - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); - - 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/PlayMode.cs b/osu.Game/Modes/PlayMode.cs deleted file mode 100644 index fa6d94a650..0000000000 --- a/osu.Game/Modes/PlayMode.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.Modes -{ - public enum PlayMode - { - Osu = 0, - Taiko = 1, - Catch = 2, - Mania = 3 - } -} diff --git a/osu.Game/Modes/Ruleset.cs b/osu.Game/Modes/Ruleset.cs deleted file mode 100644 index c97420fbe3..0000000000 --- a/osu.Game/Modes/Ruleset.cs +++ /dev/null @@ -1,60 +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; -using osu.Game.Graphics; -using osu.Game.Modes.Mods; -using osu.Game.Modes.UI; -using osu.Game.Screens.Play; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using osu.Game.Modes.Scoring; - -namespace osu.Game.Modes -{ - public class BeatmapStatistic - { - public FontAwesome Icon; - public string Content; - public string Name; - } - - public abstract class Ruleset - { - private static readonly ConcurrentDictionary available_rulesets = new ConcurrentDictionary(); - - public static IEnumerable PlayModes => available_rulesets.Keys; - - public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; - - public abstract IEnumerable GetModsFor(ModType type); - - public abstract HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap); - - public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap); - - public abstract ScoreProcessor CreateScoreProcessor(); - - public static void Register(Ruleset ruleset) => available_rulesets.TryAdd(ruleset.PlayMode, ruleset.GetType()); - - protected abstract PlayMode PlayMode { get; } - - public virtual FontAwesome Icon => FontAwesome.fa_question_circle; - - public abstract string Description { get; } - - public abstract IEnumerable CreateGameplayKeys(); - - public static Ruleset GetRuleset(PlayMode mode) - { - Type type; - - if (!available_rulesets.TryGetValue(mode, out type)) - return null; - - return Activator.CreateInstance(type) as Ruleset; - } - - } -} diff --git a/osu.Game/Modes/Scoring/Score.cs b/osu.Game/Modes/Scoring/Score.cs deleted file mode 100644 index c998b11f77..0000000000 --- a/osu.Game/Modes/Scoring/Score.cs +++ /dev/null @@ -1,96 +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.Collections.Generic; -using Newtonsoft.Json; -using osu.Game.Database; -using osu.Game.Modes.Mods; -using osu.Game.Users; -using System.IO; -using osu.Game.Modes.Replays; - -namespace osu.Game.Modes.Scoring -{ - public class Score - { - [JsonProperty(@"rank")] - public ScoreRank Rank { get; set; } - - [JsonProperty(@"score")] - public double TotalScore { get; set; } - public double Accuracy { get; set; } - public double Health { get; set; } - - [JsonProperty(@"maxcombo")] - public int MaxCombo { get; set; } - public int Combo { get; set; } - public Mod[] Mods { get; set; } - - public User User { get; set; } - - [JsonProperty(@"replay_data")] - public Replay Replay; - - public BeatmapInfo Beatmap; - - [JsonProperty(@"score_id")] - public long OnlineScoreID; - - [JsonProperty(@"username")] - public string Username; - - [JsonProperty(@"user_id")] - public long UserID; - - [JsonProperty(@"date")] - public DateTime Date; - - /// - /// Creates a replay which is read from a stream. - /// - /// The stream reader. - /// The replay. - public virtual Replay CreateReplay(StreamReader reader) - { - var frames = new List(); - - float lastTime = 0; - - foreach (var l in reader.ReadToEnd().Split(',')) - { - var split = l.Split('|'); - - if (split.Length < 4 || float.Parse(split[0]) < 0) continue; - - lastTime += float.Parse(split[0]); - - frames.Add(new ReplayFrame( - lastTime, - float.Parse(split[1]), - 384 - float.Parse(split[2]), - (ReplayButtonState)int.Parse(split[3]) - )); - } - - return new Replay { Frames = frames }; - } - - // [JsonProperty(@"count50")] 0, - //[JsonProperty(@"count100")] 0, - //[JsonProperty(@"count300")] 100, - //[JsonProperty(@"countmiss")] 0, - //[JsonProperty(@"countkatu")] 0, - //[JsonProperty(@"countgeki")] 31, - //[JsonProperty(@"perfect")] true, - //[JsonProperty(@"enabled_mods")] [ - // "DT", - // "FL", - // "HD", - // "HR" - //], - //[JsonProperty(@"rank")] "XH", - //[JsonProperty(@"pp")] 26.1816, - //[JsonProperty(@"replay")] true - } -} diff --git a/osu.Game/Modes/UI/StandardHealthDisplay.cs b/osu.Game/Modes/UI/StandardHealthDisplay.cs deleted file mode 100644 index d49e32ea8b..0000000000 --- a/osu.Game/Modes/UI/StandardHealthDisplay.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; - -namespace osu.Game.Modes.UI -{ - public class StandardHealthDisplay : HealthDisplay - { - private readonly Container fill; - - public StandardHealthDisplay() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - fill = new Container - { - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0, 1), - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - } - } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - fill.Colour = colours.BlueLighter; - fill.EdgeEffect = new EdgeEffect - { - Colour = colours.BlueDarker.Opacity(0.6f), - Radius = 8, - Type = EdgeEffectType.Glow - }; - } - - protected override void SetHealth(float value) => fill.ScaleTo(new Vector2(value, 1), 200, EasingTypes.OutQuint); - } -} diff --git a/osu.Game/Modes/UI/StandardHudOverlay.cs b/osu.Game/Modes/UI/StandardHudOverlay.cs deleted file mode 100644 index f07e421f00..0000000000 --- a/osu.Game/Modes/UI/StandardHudOverlay.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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.Primitives; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play; - -namespace osu.Game.Modes.UI -{ - public class StandardHudOverlay : HudOverlay - { - protected override PercentageCounter CreateAccuracyCounter() => new PercentageCounter - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Position = new Vector2(0, 65), - TextSize = 20, - Margin = new MarginPadding { Right = 5 }, - }; - - protected override ComboCounter CreateComboCounter() => new StandardComboCounter - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }; - - protected override HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay - { - Size = new Vector2(1, 5), - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Top = 20 } - }; - - protected override KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection - { - IsCounting = true, - FadeTime = 50, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding(10), - Y = - TwoLayerButton.SIZE_RETRACTED.Y, - }; - - protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextSize = 40, - Position = new Vector2(0, 30), - Margin = new MarginPadding { Right = 5 }, - }; - } -} diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs new file mode 100644 index 0000000000..43e14e59de --- /dev/null +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Database; + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapDetailsRequest : APIRequest + { + private readonly BeatmapInfo beatmap; + + private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={beatmap.Path}"; + + public GetBeatmapDetailsRequest(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + protected override string Target => $@"beatmaps/{lookupString}"; + } + + public class GetBeatmapDeatilsResponse : BeatmapMetrics + { + //the online API returns some metrics as a nested object. + [JsonProperty(@"failtimes")] + private BeatmapMetrics failTimes + { + set + { + Fails = value.Fails; + Retries = value.Retries; + } + } + + //and other metrics in the beatmap set. + [JsonProperty(@"beatmapset")] + private BeatmapMetrics beatmapSet + { + set + { + Ratings = value.Ratings; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 66c5e6c72d..5e6bf1ea9f 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Database; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Online.API.Requests { @@ -21,19 +21,16 @@ namespace osu.Game.Online.API.Requests protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.AddParameter(@"c", beatmap.Hash); - req.AddParameter(@"f", beatmap.Path); + //req.AddParameter(@"c", beatmap.Hash); + //req.AddParameter(@"f", beatmap.Path); return req; } - protected override string Target => @"beatmaps/scores"; + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; } public class GetScoresResponse { - [JsonProperty(@"beatmap")] - public BeatmapInfo Beatmap; - [JsonProperty(@"scores")] public IEnumerable Scores; } diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs new file mode 100644 index 0000000000..52269d9fe8 --- /dev/null +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Extensions; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class PostMessageRequest : APIRequest + { + private readonly Message message; + + public PostMessageRequest(Message message) + { + this.message = message; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Method = HttpMethod.POST; + req.AddParameter(@"target_type", message.TargetType.GetDescription()); + req.AddParameter(@"target_id", message.TargetId.ToString()); + req.AddParameter(@"message", message.Content); + + return req; + } + + protected override string Target => @"chat/messages"; + } +} \ No newline at end of file diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index d895b93336..04ebf0a389 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; +using osu.Framework.Lists; namespace osu.Game.Online.Chat { @@ -21,7 +23,7 @@ namespace osu.Game.Online.Chat [JsonProperty(@"channel_id")] public int Id; - public List Messages = new List(); + public SortedList Messages = new SortedList((m1, m2) => m1.Id.CompareTo(m2.Id)); //internal bool Joined; @@ -36,7 +38,10 @@ namespace osu.Game.Online.Chat public void AddNewMessages(IEnumerable messages) { + messages = messages.Except(Messages).ToList(); + Messages.AddRange(messages); + purgeOldMessages(); NewMessagesArrived?.Invoke(messages); diff --git a/osu.Game/Online/Chat/Drawables/ChatLine.cs b/osu.Game/Online/Chat/Drawables/ChatLine.cs index bfbcf3d707..6bfa25755f 100644 --- a/osu.Game/Online/Chat/Drawables/ChatLine.cs +++ b/osu.Game/Online/Chat/Drawables/ChatLine.cs @@ -55,6 +55,9 @@ namespace osu.Game.Online.Chat.Drawables private Color4 getUsernameColour(Message message) { + if (!string.IsNullOrEmpty(message.Sender?.Colour)) + return OsuColour.FromHex(message.Sender.Colour); + //todo: use User instead of Message when user_id is correctly populated. return username_colours[message.UserId % username_colours.Length]; } @@ -83,7 +86,7 @@ namespace osu.Game.Online.Chat.Drawables Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = @"Exo2.0-SemiBold", - Text = $@"{Message.Timestamp.LocalDateTime:hh:mm:ss}", + Text = $@"{Message.Timestamp.LocalDateTime:HH:mm:ss}", FixedWidth = true, TextSize = text_size * 0.75f, Alpha = 0.4f, @@ -91,7 +94,7 @@ namespace osu.Game.Online.Chat.Drawables new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Text = $@"{Message.User.Username}:", + Text = $@"{Message.Sender.Username}:", Colour = getUsernameColour(Message), TextSize = text_size, Origin = Anchor.TopRight, diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index b267cf63ac..372e43be38 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.ComponentModel; using Newtonsoft.Json; using osu.Game.Users; @@ -10,14 +11,17 @@ namespace osu.Game.Online.Chat public class Message { [JsonProperty(@"message_id")] - public long Id; + public readonly long Id; //todo: this should be inside sender. - [JsonProperty(@"user_id")] + [JsonProperty(@"sender_id")] public int UserId; - [JsonProperty(@"channel_id")] - public int ChannelId; + [JsonProperty(@"target_type")] + public TargetType TargetType; + + [JsonProperty(@"target_id")] + public int TargetId; [JsonProperty(@"timestamp")] public DateTimeOffset Timestamp; @@ -26,11 +30,31 @@ namespace osu.Game.Online.Chat public string Content; [JsonProperty(@"sender")] - public User User; + public User Sender; [JsonConstructor] public Message() { } + + public override bool Equals(object obj) + { + var objMessage = obj as Message; + + return Id == objMessage?.Id; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + } + + public enum TargetType + { + [Description(@"channel")] + Channel, + [Description(@"user")] + User } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7172aba3be..c9f41de5f2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -15,7 +15,6 @@ using osu.Framework.Logging; using osu.Game.Graphics.UserInterface.Volume; using osu.Framework.Allocation; using osu.Framework.Timing; -using osu.Game.Modes; using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; @@ -24,8 +23,9 @@ using System.Linq; using osu.Framework.Graphics.Primitives; using System.Threading.Tasks; using osu.Framework.Threading; +using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Play; @@ -58,7 +58,8 @@ namespace osu.Game private VolumeControl volume; - public Bindable PlayMode; + private Bindable configRuleset; + public Bindable Ruleset = new Bindable(); private readonly string[] args; @@ -88,7 +89,9 @@ namespace osu.Game Dependencies.Cache(this); - PlayMode = LocalConfig.GetBindable(OsuConfig.PlayMode); + configRuleset = LocalConfig.GetBindable(OsuConfig.Ruleset); + Ruleset.Value = RulesetDatabase.GetRuleset(configRuleset.Value); + Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; } private ScheduledDelegate scoreLoad; @@ -191,6 +194,7 @@ namespace osu.Game }; Dependencies.Cache(options); + Dependencies.Cache(chat); Dependencies.Cache(musicController); Dependencies.Cache(notificationManager); Dependencies.Cache(dialogOverlay); @@ -199,13 +203,7 @@ namespace osu.Game { Depth = -3, OnHome = delegate { intro?.ChildScreen?.MakeCurrent(); }, - OnPlayModeChange = m => PlayMode.Value = m, - }, t => - { - PlayMode.ValueChanged += delegate { Toolbar.SetGameMode(PlayMode.Value); }; - PlayMode.TriggerChange(); - overlayContent.Add(Toolbar); - }); + }, overlayContent.Add); options.StateChanged += delegate { @@ -276,7 +274,7 @@ namespace osu.Game return; } - //central game mode change logic. + //central game screen change logic. if (!currentScreen.ShowOverlays) { options.State = Visibility.Hidden; @@ -307,6 +305,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(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f95e8c3ac6..c9d7d856db 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Processing; using osu.Game.Online.API; +using SQLite.Net; namespace osu.Game { @@ -27,6 +28,8 @@ namespace osu.Game protected BeatmapDatabase BeatmapDatabase; + protected RulesetDatabase RulesetDatabase; + protected ScoreDatabase ScoreDatabase; protected override string MainResourceFile => @"osu.Game.Resources.dll"; @@ -80,8 +83,12 @@ namespace osu.Game { Dependencies.Cache(this); Dependencies.Cache(LocalConfig); - Dependencies.Cache(BeatmapDatabase = new BeatmapDatabase(Host.Storage, Host)); - Dependencies.Cache(ScoreDatabase = new ScoreDatabase(Host.Storage, Host, BeatmapDatabase)); + + SQLiteConnection connection = Host.Storage.GetDatabase(@"client"); + + Dependencies.Cache(RulesetDatabase = new RulesetDatabase(Host.Storage, connection)); + Dependencies.Cache(BeatmapDatabase = new BeatmapDatabase(Host.Storage, connection, RulesetDatabase, Host)); + Dependencies.Cache(ScoreDatabase = new ScoreDatabase(Host.Storage, connection, Host, BeatmapDatabase)); Dependencies.Cache(new OsuColour()); //this completely overrides the framework default. will need to change once we make a proper FontStore. @@ -109,6 +116,7 @@ namespace osu.Game Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera")); + Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); OszArchiveReader.Register(); @@ -137,9 +145,19 @@ namespace osu.Game AddInternal(ratioContainer = new RatioAdjust { - Children = new[] + Children = new Drawable[] { - Cursor = new MenuCursor { Depth = float.MinValue } + new Container + { + AlwaysReceiveInput = true, + RelativeSizeAxes = Axes.Both, + Depth = float.MinValue, + Children = new Drawable[] + { + Cursor = new MenuCursor(), + new TooltipContainer(Cursor) { Depth = -1 }, + } + }, } }); } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 0bb3d3dc71..457611dfab 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -38,6 +38,10 @@ namespace osu.Game.Overlays private APIAccess api; + private const int transition_length = 500; + + private GetMessagesRequest fetchReq; + public ChatOverlay() { RelativeSizeAxes = Axes.X; @@ -82,94 +86,6 @@ namespace osu.Game.Overlays }); } - protected override bool OnFocus(InputState state) - { - //this is necessary as inputTextBox is masked away and therefore can't get focus :( - inputTextBox.TriggerFocus(); - return false; - } - - private void postMessage(TextBox sender, bool newText) - { - var postText = sender.Text; - - if (!string.IsNullOrEmpty(postText)) - { - //todo: actually send to server - careChannels.FirstOrDefault()?.AddNewMessages(new[] - { - new Message - { - User = api.LocalUser.Value, - Timestamp = DateTimeOffset.Now, - Content = postText - } - }); - } - - sender.Text = string.Empty; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - api.Register(this); - } - - private long? lastMessageId; - - private List careChannels; - - private void addChannel(Channel channel) - { - Add(new DrawableChannel(channel)); - careChannels.Add(channel); - } - - private GetMessagesRequest fetchReq; - - public void FetchNewMessages(APIAccess api) - { - if (fetchReq != null) return; - - fetchReq = new GetMessagesRequest(careChannels, lastMessageId); - fetchReq.Success += delegate (List messages) - { - var ids = messages.Select(m => m.ChannelId).Distinct(); - - //batch messages per channel. - foreach (var id in ids) - careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.ChannelId == id)); - - lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; - - Debug.Write("success!"); - fetchReq = null; - }; - fetchReq.Failure += delegate - { - Debug.Write("failure!"); - fetchReq = null; - }; - - api.Queue(fetchReq); - } - - private const int transition_length = 500; - - protected override void PopIn() - { - MoveToY(0, transition_length, EasingTypes.OutQuint); - FadeIn(transition_length, EasingTypes.OutQuint); - } - - protected override void PopOut() - { - MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine); - FadeOut(transition_length, EasingTypes.InSine); - } - public void APIStateChanged(APIAccess api, APIState state) { switch (state) @@ -183,19 +99,46 @@ namespace osu.Game.Overlays } } + protected override bool OnFocus(InputState state) + { + //this is necessary as inputTextBox is masked away and therefore can't get focus :( + inputTextBox.TriggerFocus(); + return false; + } + + protected override void PopIn() + { + MoveToY(0, transition_length, EasingTypes.OutQuint); + FadeIn(transition_length, EasingTypes.OutQuint); + } + + protected override void PopOut() + { + MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine); + FadeOut(transition_length, EasingTypes.InSine); + } + + [BackgroundDependencyLoader] + private void load(APIAccess api) + { + this.api = api; + api.Register(this); + } + + private long? lastMessageId; + + private List careChannels; + private void initializeChannels() { Clear(); careChannels = new List(); - //if (api.State != APIAccess.APIState.Online) - // return; - SpriteText loading; Add(loading = new OsuSpriteText { - Text = @"Loading available channels...", + Text = @"initialising chat...", Anchor = Anchor.Centre, Origin = Anchor.Centre, TextSize = 40, @@ -211,15 +154,85 @@ namespace osu.Game.Overlays Scheduler.Add(delegate { loading.FadeOut(100); - addChannel(channels.Find(c => c.Name == @"#osu")); + addChannel(channels.Find(c => c.Name == @"#lazer")); }); - //addChannel(channels.Find(c => c.Name == @"#lobby")); - //addChannel(channels.Find(c => c.Name == @"#english")); - - messageRequest = Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true); + messageRequest = Scheduler.AddDelayed(fetchNewMessages, 1000, true); }; api.Queue(req); } + + private void addChannel(Channel channel) + { + Add(new DrawableChannel(channel)); + careChannels.Add(channel); + } + + private void fetchNewMessages() + { + if (fetchReq != null) return; + + fetchReq = new GetMessagesRequest(careChannels, lastMessageId); + fetchReq.Success += delegate (List messages) + { + var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct(); + + //batch messages per channel. + foreach (var id in ids) + careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.TargetId == id)); + + lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; + + Debug.Write("success!"); + fetchReq = null; + }; + fetchReq.Failure += delegate + { + Debug.Write("failure!"); + fetchReq = null; + }; + + api.Queue(fetchReq); + } + + private void postMessage(TextBox textbox, bool newText) + { + var postText = textbox.Text; + + if (!string.IsNullOrEmpty(postText) && api.LocalUser.Value != null) + { + var currentChannel = careChannels.FirstOrDefault(); + + if (currentChannel == null) return; + + var message = new Message + { + Sender = api.LocalUser.Value, + Timestamp = DateTimeOffset.Now, + TargetType = TargetType.Channel, //TODO: read this from currentChannel + TargetId = currentChannel.Id, + Content = postText + }; + + textbox.ReadOnly = true; + var req = new PostMessageRequest(message); + + req.Failure += e => + { + textbox.FlashColour(Color4.Red, 1000); + textbox.ReadOnly = false; + }; + + req.Success += m => + { + currentChannel.AddNewMessages(new[] { m }); + + textbox.ReadOnly = false; + textbox.Text = string.Empty; + }; + + api.Queue(req); + } + } } } diff --git a/osu.Game/Overlays/DragBar.cs b/osu.Game/Overlays/DragBar.cs index 90991bb195..bb28f08553 100644 --- a/osu.Game/Overlays/DragBar.cs +++ b/osu.Game/Overlays/DragBar.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays { public class DragBar : Container { - private readonly Box fill; + protected readonly Container Fill; public Action SeekRequested; @@ -27,7 +27,7 @@ namespace osu.Game.Overlays { enabled = value; if (!enabled) - fill.Width = 0; + Fill.Width = 0; } } @@ -37,12 +37,20 @@ namespace osu.Game.Overlays Children = new Drawable[] { - fill = new Box + Fill = new Container { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, + Name = "FillContainer", + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, - Width = 0 + Width = 0, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both + } + } } }; } @@ -51,21 +59,23 @@ namespace osu.Game.Overlays { if (IsSeeking || !IsEnabled) return; - updatePosition(position); + updatePosition(position, false); } private void seek(InputState state) { - if (!IsEnabled) return; float seekLocation = state.Mouse.Position.X / DrawWidth; + + if (!IsEnabled) return; + SeekRequested?.Invoke(seekLocation); updatePosition(seekLocation); } - private void updatePosition(float position) + private void updatePosition(float position, bool easing = true) { position = MathHelper.Clamp(position, 0, 1); - fill.TransformTo(fill.Width, position, 200, EasingTypes.OutQuint, new TransformSeek()); + Fill.TransformTo(() => Fill.Width, position, easing ? 200 : 0, EasingTypes.OutQuint, new TransformSeek()); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) diff --git a/osu.Game/Overlays/Mods/AssistedSection.cs b/osu.Game/Overlays/Mods/AssistedSection.cs index a1ec7a3fdc..b4263fa309 100644 --- a/osu.Game/Overlays/Mods/AssistedSection.cs +++ b/osu.Game/Overlays/Mods/AssistedSection.cs @@ -4,7 +4,7 @@ using OpenTK.Input; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { diff --git a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs index 13df5aabfb..0a293416dc 100644 --- a/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyIncreaseSection.cs @@ -4,7 +4,7 @@ using OpenTK.Input; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { diff --git a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs index f8ac4551ef..3a373e6f09 100644 --- a/osu.Game/Overlays/Mods/DifficultyReductionSection.cs +++ b/osu.Game/Overlays/Mods/DifficultyReductionSection.cs @@ -4,7 +4,7 @@ using OpenTK.Input; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index b787935d57..f380c19d8a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -12,8 +12,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Graphics.Sprites; -using osu.Game.Modes.Mods; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using System; using System.Linq; diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 0e93a5520d..c2af12f49e 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Game.Graphics.Sprites; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; using System; namespace osu.Game.Overlays.Mods diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 2b9f8e86a9..dadfb808f7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -13,11 +13,11 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; -using osu.Game.Modes; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Database; namespace osu.Game.Overlays.Mods { @@ -37,26 +37,30 @@ namespace osu.Game.Overlays.Mods public readonly Bindable> SelectedMods = new Bindable>(); - public readonly Bindable PlayMode = new Bindable(); + public readonly Bindable Ruleset = new Bindable(); - private void modeChanged(PlayMode newMode) + private void rulesetChanged(RulesetInfo newRuleset) { - var ruleset = Ruleset.GetRuleset(newMode); + var instance = newRuleset.CreateInstance(); + foreach (ModSection section in modSectionsContainer.Children) - section.Buttons = ruleset.GetModsFor(section.ModType).Select(m => new ModButton(m)).ToArray(); + section.Buttons = instance.GetModsFor(section.ModType).Select(m => new ModButton(m)).ToArray(); refreshSelectedMods(); } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, OsuGame osu) + private void load(OsuColour colours, OsuGame osu, RulesetDatabase rulesets) { lowMultiplierColour = colours.Red; highMultiplierColour = colours.Green; if (osu != null) - PlayMode.BindTo(osu.PlayMode); - PlayMode.ValueChanged += modeChanged; - PlayMode.TriggerChange(); + Ruleset.BindTo(osu.Ruleset); + else + Ruleset.Value = rulesets.AllRulesets.First(); + + Ruleset.ValueChanged += rulesetChanged; + Ruleset.TriggerChange(); } protected override void PopOut() diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 340a6c6123..def2c7c416 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -10,18 +10,19 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Configuration; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Game.Graphics.Sprites; -using osu.Framework.Extensions.Color4Extensions; using System.Linq; namespace osu.Game.Overlays @@ -43,9 +44,9 @@ namespace osu.Game.Overlays private TrackManager trackManager; private Bindable beatmapSource; - private Bindable preferUnicode; private WorkingBeatmap current; private BeatmapDatabase beatmaps; + private LocalisationEngine localisation; private Container dragContainer; private Container playerContainer; @@ -86,7 +87,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuConfigManager config, BeatmapDatabase beatmaps, OsuColour colours) + private void load(OsuGameBase game, BeatmapDatabase beatmaps, OsuColour colours, LocalisationEngine localisation) { activeColour = colours.Yellow; @@ -223,8 +224,7 @@ namespace osu.Game.Overlays this.beatmaps = beatmaps; trackManager = game.Audio.Track; - preferUnicode = config.GetBindable(OsuConfig.ShowUnicode); - preferUnicode.ValueChanged += unicode => updateDisplay(current, TransformDirection.None); + this.localisation = localisation; beatmapSource = game.Beatmap ?? new Bindable(); @@ -306,7 +306,7 @@ namespace osu.Game.Overlays trackManager.SetExclusive(current.Track); current.Track.Start(); beatmapSource.Value = current; - }); + }).ContinueWith(task => Schedule(task.ThrowIfFaulted), TaskContinuationOptions.OnlyOnFaulted); updateDisplay(current, isNext ? TransformDirection.Next : TransformDirection.Prev); } @@ -320,16 +320,19 @@ namespace osu.Game.Overlays { Task.Run(() => { - if (beatmap?.Beatmap == null) + if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists { + title.Current = null; title.Text = @"Nothing to play"; + + artist.Current = null; artist.Text = @"Nothing to play"; } else { BeatmapMetadata metadata = beatmap.Beatmap.BeatmapInfo.Metadata; - title.Text = preferUnicode ? metadata.TitleUnicode : metadata.Title; - artist.Text = preferUnicode ? metadata.ArtistUnicode : metadata.Artist; + title.Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title); + artist.Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist); playlist.Current = beatmap.BeatmapSetInfo; } }); diff --git a/osu.Game/Overlays/Options/OptionDropdown.cs b/osu.Game/Overlays/Options/OptionDropdown.cs index 9ae02a17d3..8642b132df 100644 --- a/osu.Game/Overlays/Options/OptionDropdown.cs +++ b/osu.Game/Overlays/Options/OptionDropdown.cs @@ -33,9 +33,7 @@ namespace osu.Game.Overlays.Options set { bindable = value; - dropdown.SelectedValue.BindTo(bindable); - if (bindable.Disabled) - Alpha = 0.3f; + dropdown.Current.BindTo(bindable); } } @@ -75,6 +73,11 @@ namespace osu.Game.Overlays.Options Items = Items, } }; + + dropdown.Current.DisabledChanged += disabled => + { + Alpha = disabled ? 0.3f : 1; + }; } } } diff --git a/osu.Game/Overlays/Options/OptionSlider.cs b/osu.Game/Overlays/Options/OptionSlider.cs index 8fa9bf063d..42bf4170fa 100644 --- a/osu.Game/Overlays/Options/OptionSlider.cs +++ b/osu.Game/Overlays/Options/OptionSlider.cs @@ -12,7 +12,11 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options { - public class OptionSlider : FillFlowContainer where T : struct + public class OptionSlider : OptionSlider> where T: struct + { + } + + public class OptionSlider : FillFlowContainer where T : struct where U : SliderBar, new() { private readonly SliderBar slider; private readonly SpriteText text; @@ -27,14 +31,14 @@ namespace osu.Game.Overlays.Options } } - public BindableNumber Bindable + private Bindable bindable; + + public Bindable Bindable { - get { return slider.Value; } set { - slider.Value = value; - if (value?.Disabled ?? true) - Alpha = 0.3f; + bindable = value; + slider.Current.BindTo(bindable); } } @@ -50,7 +54,7 @@ namespace osu.Game.Overlays.Options { Alpha = 0, }, - slider = new OsuSliderBar + slider = new U() { Margin = new MarginPadding { Top = 5, Bottom = 5 }, RelativeSizeAxes = Axes.X diff --git a/osu.Game/Overlays/Options/OptionTextBox.cs b/osu.Game/Overlays/Options/OptionTextBox.cs index 722f24d50d..4927122181 100644 --- a/osu.Game/Overlays/Options/OptionTextBox.cs +++ b/osu.Game/Overlays/Options/OptionTextBox.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Configuration; -using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options @@ -15,38 +14,9 @@ namespace osu.Game.Overlays.Options { set { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; bindable = value; - if (bindable != null) - { - Text = bindable.Value; - bindable.ValueChanged += bindableValueChanged; - } - - if (bindable?.Disabled ?? true) - Alpha = 0.3f; + Current.BindTo(bindable); } } - - public OptionTextBox() - { - OnChange += onChange; - } - - private void onChange(TextBox sender, bool newText) - { - if (bindable != null) - bindable.Value = Text; - } - - private void bindableValueChanged(string newValue) => Text = newValue; - - protected override void Dispose(bool isDisposing) - { - if (bindable != null) - bindable.ValueChanged -= bindableValueChanged; - base.Dispose(isDisposing); - } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Options/OptionsFooter.cs b/osu.Game/Overlays/Options/OptionsFooter.cs index c42fe42428..c785f2d0c0 100644 --- a/osu.Game/Overlays/Options/OptionsFooter.cs +++ b/osu.Game/Overlays/Options/OptionsFooter.cs @@ -1,15 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Modes; using OpenTK; using OpenTK.Graphics; @@ -18,7 +17,7 @@ namespace osu.Game.Overlays.Options public class OptionsFooter : FillFlowContainer { [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours) + private void load(OsuGameBase game, OsuColour colours, RulesetDatabase rulesets) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -27,13 +26,15 @@ namespace osu.Game.Overlays.Options var modes = new List(); - foreach (PlayMode m in Enum.GetValues(typeof(PlayMode))) + foreach (var ruleset in rulesets.AllRulesets) + { modes.Add(new TextAwesome { - Icon = Ruleset.GetRuleset(m).Icon, + Icon = ruleset.CreateInstance().Icon, Colour = Color4.Gray, TextSize = 20 }); + } Children = new Drawable[] { diff --git a/osu.Game/Overlays/Options/Sections/Audio/OffsetOptions.cs b/osu.Game/Overlays/Options/Sections/Audio/OffsetOptions.cs index 2602565c29..72c3dd071a 100644 --- a/osu.Game/Overlays/Options/Sections/Audio/OffsetOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Audio/OffsetOptions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -18,10 +17,10 @@ namespace osu.Game.Overlays.Options.Sections.Audio { Children = new Drawable[] { - new OptionSlider + new OptionSlider { - LabelText = "Universal Offset", - Bindable = (BindableInt)config.GetBindable(OsuConfig.Offset) + LabelText = "Audio Offset", + Bindable = config.GetBindable(OsuConfig.AudioOffset) }, new OsuButton { @@ -30,5 +29,10 @@ namespace osu.Game.Overlays.Options.Sections.Audio } }; } + + private class OffsetSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0ms"); + } } } diff --git a/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs b/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs index ee6778a47a..99f9ecbaed 100644 --- a/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Gameplay/GeneralOptions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -18,10 +17,10 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay { Children = new Drawable[] { - new OptionSlider + new OptionSlider { LabelText = "Background dim", - Bindable = (BindableInt)config.GetBindable(OsuConfig.DimLevel) + Bindable = config.GetBindable(OsuConfig.DimLevel) }, new OptionEnumDropdown { @@ -36,7 +35,7 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay new OptionSlider { LabelText = "Score meter size", - Bindable = (BindableDouble)config.GetBindable(OsuConfig.ScoreMeterScale) + Bindable = config.GetBindable(OsuConfig.ScoreMeterScale) }, new OsuCheckbox { diff --git a/osu.Game/Overlays/Options/Sections/Gameplay/SongSelectOptions.cs b/osu.Game/Overlays/Options/Sections/Gameplay/SongSelectOptions.cs index be38a9847a..910eae9a5a 100644 --- a/osu.Game/Overlays/Options/Sections/Gameplay/SongSelectOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Gameplay/SongSelectOptions.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Options.Sections.Gameplay { @@ -17,18 +17,23 @@ namespace osu.Game.Overlays.Options.Sections.Gameplay { Children = new Drawable[] { - new OptionSlider + new OptionSlider { LabelText = "Display beatmaps from", - Bindable = (BindableDouble)config.GetBindable(OsuConfig.DisplayStarsMinimum) + Bindable = config.GetBindable(OsuConfig.DisplayStarsMinimum) }, - new OptionSlider + new OptionSlider { LabelText = "up to", - Bindable = (BindableDouble)config.GetBindable(OsuConfig.DisplayStarsMaximum) + Bindable = config.GetBindable(OsuConfig.DisplayStarsMaximum) }, }; } + + private class StarSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0.## stars"); + } } } diff --git a/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs b/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs index 98b67342cb..1387f981d3 100644 --- a/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs +++ b/osu.Game/Overlays/Options/Sections/General/LanguageOptions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Options.Sections.General protected override string Header => "Language"; [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager osuConfig, FrameworkConfigManager frameworkConfig) { Children = new Drawable[] { @@ -21,12 +22,12 @@ namespace osu.Game.Overlays.Options.Sections.General new OsuCheckbox { LabelText = "Prefer metadata in original language", - Bindable = config.GetBindable(OsuConfig.ShowUnicode) + Bindable = frameworkConfig.GetBindable(FrameworkConfig.ShowUnicode) }, new OsuCheckbox { LabelText = "Use alternative font for chat display", - Bindable = config.GetBindable(OsuConfig.AlternativeChatFont) + Bindable = osuConfig.GetBindable(OsuConfig.AlternativeChatFont) }, }; } diff --git a/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs b/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs index b04f853ec4..9f5e3458f3 100644 --- a/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Graphics/LayoutOptions.cs @@ -12,8 +12,8 @@ namespace osu.Game.Overlays.Options.Sections.Graphics { protected override string Header => "Layout"; - private OptionSlider letterboxPositionX; - private OptionSlider letterboxPositionY; + private OptionSlider letterboxPositionX; + private OptionSlider letterboxPositionY; private Bindable letterboxing; @@ -35,15 +35,15 @@ namespace osu.Game.Overlays.Options.Sections.Graphics LabelText = "Letterboxing", Bindable = letterboxing, }, - letterboxPositionX = new OptionSlider + letterboxPositionX = new OptionSlider { LabelText = "Horizontal position", - Bindable = (BindableInt)config.GetBindable(FrameworkConfig.LetterboxPositionX) + Bindable = config.GetBindable(FrameworkConfig.LetterboxPositionX) }, - letterboxPositionY = new OptionSlider + letterboxPositionY = new OptionSlider { LabelText = "Vertical position", - Bindable = (BindableInt)config.GetBindable(FrameworkConfig.LetterboxPositionY) + Bindable = config.GetBindable(FrameworkConfig.LetterboxPositionY) }, }; diff --git a/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs b/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs index 92be00bdb0..4a48c9ec86 100644 --- a/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs +++ b/osu.Game/Overlays/Options/Sections/Input/MouseOptions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -18,10 +17,10 @@ namespace osu.Game.Overlays.Options.Sections.Input { Children = new Drawable[] { - new OptionSlider + new OptionSlider { LabelText = "Sensitivity", - Bindable = (BindableDouble)config.GetBindable(OsuConfig.MouseSpeed), + Bindable = config.GetBindable(OsuConfig.MouseSpeed) }, new OsuCheckbox { @@ -55,5 +54,10 @@ namespace osu.Game.Overlays.Options.Sections.Input }, }; } + + private class SensitivitySlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0.##x"); + } } } diff --git a/osu.Game/Overlays/Options/Sections/SkinSection.cs b/osu.Game/Overlays/Options/Sections/SkinSection.cs index 78f4f1e380..0e533928a8 100644 --- a/osu.Game/Overlays/Options/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Options/Sections/SkinSection.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics; @@ -59,15 +58,15 @@ namespace osu.Game.Overlays.Options.Sections LabelText = "Always use skin cursor", Bindable = config.GetBindable(OsuConfig.UseSkinCursor) }, - new OptionSlider + new OptionSlider { LabelText = "Menu cursor size", - Bindable = (BindableDouble)config.GetBindable(OsuConfig.MenuCursorSize) + Bindable = config.GetBindable(OsuConfig.MenuCursorSize) }, - new OptionSlider + new OptionSlider { LabelText = "Gameplay cursor size", - Bindable = (BindableDouble)config.GetBindable(OsuConfig.GameplayCursorSize) + Bindable = config.GetBindable(OsuConfig.GameplayCursorSize) }, new OsuCheckbox { @@ -76,5 +75,10 @@ namespace osu.Game.Overlays.Options.Sections }, }; } + + private class SizeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString(@"0.##x"); + } } } diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 9e7b4f1519..43c3cd32f2 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Graphics; -using osu.Game.Modes; using OpenTK; namespace osu.Game.Overlays.Toolbar @@ -20,14 +19,12 @@ namespace osu.Game.Overlays.Toolbar public const float TOOLTIP_HEIGHT = 30; public Action OnHome; - public Action OnPlayModeChange; - private readonly ToolbarModeSelector modeSelector; private readonly ToolbarUserArea userArea; protected override bool HideOnEscape => false; - protected override bool BlockPassThroughInput => false; + protected override bool BlockPassThroughMouse => false; private const double transition_time = 500; @@ -53,13 +50,7 @@ namespace osu.Game.Overlays.Toolbar { Action = () => OnHome?.Invoke() }, - modeSelector = new ToolbarModeSelector - { - OnPlayModeChange = mode => - { - OnPlayModeChange?.Invoke(mode); - } - } + new ToolbarModeSelector() } }, new FillFlowContainer @@ -72,6 +63,7 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { + new ToolbarChatButton(), new ToolbarMusicButton(), new ToolbarButton { @@ -129,8 +121,6 @@ namespace osu.Game.Overlays.Toolbar } } - public void SetGameMode(PlayMode mode) => modeSelector.SetGameMode(mode); - protected override void PopIn() { MoveToY(0, transition_time, EasingTypes.OutQuint); diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs new file mode 100644 index 0000000000..ca612662e1 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -0,0 +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.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + internal class ToolbarChatButton : ToolbarOverlayToggleButton + { + public ToolbarChatButton() + { + Icon = FontAwesome.fa_comments; + } + + [BackgroundDependencyLoader] + private void load(ChatOverlay chat) + { + StateContainer = chat; + Action = chat.ToggleVisibility; + } + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs index 62359b05ae..dd70289f7d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeButton.cs @@ -2,23 +2,26 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics.Containers; -using osu.Game.Modes; +using osu.Game.Database; using OpenTK.Graphics; namespace osu.Game.Overlays.Toolbar { public class ToolbarModeButton : ToolbarButton { - private PlayMode mode; - public PlayMode Mode + private RulesetInfo ruleset; + public RulesetInfo Ruleset { - get { return mode; } + get { return ruleset; } set { - mode = value; - TooltipMain = Ruleset.GetRuleset(mode).Description; - TooltipSub = $"Play some {Ruleset.GetRuleset(mode).Description}"; - Icon = Ruleset.GetRuleset(mode).Icon; + ruleset = value; + + var rInstance = ruleset.CreateInstance(); + + TooltipMain = rInstance.Description; + TooltipSub = $"Play some {rInstance.Description}"; + Icon = rInstance.Icon; } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index e117089166..209b64e709 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.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 System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; -using osu.Game.Modes; +using osu.Game.Database; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Toolbar { @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Toolbar private readonly Drawable modeButtonLine; private ToolbarModeButton activeButton; - public Action OnPlayModeChange; + private readonly Bindable ruleset = new Bindable(); public ToolbarModeSelector() { @@ -62,33 +63,43 @@ namespace osu.Game.Overlays.Toolbar } } }; + } - foreach (PlayMode m in Ruleset.PlayModes) + [BackgroundDependencyLoader] + private void load(RulesetDatabase rulesets, OsuGame game) + { + foreach (var r in rulesets.AllRulesets) { modeButtons.Add(new ToolbarModeButton { - Mode = m, + Ruleset = r, Action = delegate { - SetGameMode(m); - OnPlayModeChange?.Invoke(m); + ruleset.Value = r; } }); } + + ruleset.ValueChanged += rulesetChanged; + ruleset.DisabledChanged += disabledChanged; + ruleset.BindTo(game.Ruleset); } + public override bool HandleInput => !ruleset.Disabled; + + private void disabledChanged(bool isDisabled) => FadeColour(isDisabled ? Color4.Gray : Color4.White, 300); + protected override void Update() { base.Update(); - Size = new Vector2(modeButtons.DrawSize.X, 1); } - public void SetGameMode(PlayMode mode) + private void rulesetChanged(RulesetInfo ruleset) { foreach (ToolbarModeButton m in modeButtons.Children.Cast()) { - bool isActive = m.Mode == mode; + bool isActive = m.Ruleset.ID == ruleset.ID; m.Active = isActive; if (isActive) activeButton = m; diff --git a/osu.Game/Rulesets/BeatmapStatistic.cs b/osu.Game/Rulesets/BeatmapStatistic.cs new file mode 100644 index 0000000000..11ac698851 --- /dev/null +++ b/osu.Game/Rulesets/BeatmapStatistic.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets +{ + public class BeatmapStatistic + { + public FontAwesome Icon; + public string Content; + public string Name; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs b/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs new file mode 100644 index 0000000000..07aae6a26e --- /dev/null +++ b/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Beatmaps +{ + /// + /// Converts a Beatmap for another mode. + /// + /// The type of HitObject stored in the Beatmap. + public abstract class BeatmapConverter where T : HitObject + { + /// + /// Checks if a Beatmap can be converted using this Beatmap Converter. + /// + /// The Beatmap to check. + /// Whether the Beatmap can be converted using this Beatmap Converter. + public bool CanConvert(Beatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType)); + + /// + /// Converts a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + /// The converted Beatmap. + public Beatmap Convert(Beatmap original) + { + // We always operate on a clone of the original beatmap, to not modify it game-wide + return ConvertBeatmap(new Beatmap(original)); + } + + /// + /// Performs the conversion of a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + /// The converted Beatmap. + protected virtual Beatmap ConvertBeatmap(Beatmap original) + { + return new Beatmap + { + BeatmapInfo = original.BeatmapInfo, + TimingInfo = original.TimingInfo, + HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList() + }; + } + + /// + /// Converts a hit object. + /// + /// The hit object to convert. + /// The un-converted Beatmap. + /// The converted hit object. + private IEnumerable convert(HitObject original, Beatmap beatmap) + { + // Check if the hitobject is already the converted type + T tObject = original as T; + if (tObject != null) + { + yield return tObject; + yield break; + } + + // Convert the hit object + foreach (var obj in ConvertHitObject(original, beatmap)) + { + if (obj == null) + continue; + + yield return obj; + } + } + + /// + /// The types of HitObjects that can be converted to be used for this Beatmap. + /// + protected abstract IEnumerable ValidConversionTypes { get; } + + /// + /// Performs the conversion of a hit object. + /// + /// The hit object to convert. + /// The un-converted Beatmap. + /// The converted hit object. + protected abstract IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap); + } +} diff --git a/osu.Game/Beatmaps/IBeatmapProcessor.cs b/osu.Game/Rulesets/Beatmaps/BeatmapProcessor.cs similarity index 74% rename from osu.Game/Beatmaps/IBeatmapProcessor.cs rename to osu.Game/Rulesets/Beatmaps/BeatmapProcessor.cs index 9157a760b1..ee9fc30f7b 100644 --- a/osu.Game/Beatmaps/IBeatmapProcessor.cs +++ b/osu.Game/Rulesets/Beatmaps/BeatmapProcessor.cs @@ -1,15 +1,16 @@ // 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; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; -namespace osu.Game.Beatmaps +namespace osu.Game.Rulesets.Beatmaps { /// /// Processes a post-converted Beatmap. /// /// The type of HitObject contained in the Beatmap. - public interface IBeatmapProcessor + public class BeatmapProcessor where TObject : HitObject { /// @@ -19,6 +20,6 @@ namespace osu.Game.Beatmaps /// /// /// The Beatmap to process. - void PostProcess(Beatmap beatmap); + public virtual void PostProcess(Beatmap beatmap) { } } } diff --git a/osu.Game/Modes/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs similarity index 94% rename from osu.Game/Modes/Judgements/DrawableJudgement.cs rename to osu.Game/Rulesets/Judgements/DrawableJudgement.cs index eabcb5c161..3a82827497 100644 --- a/osu.Game/Modes/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -9,9 +9,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Modes.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Modes.Judgements +namespace osu.Game.Rulesets.Judgements { /// /// A drawable object which visualises the hit result of a . diff --git a/osu.Game/Modes/Judgements/IPartialJudgement.cs b/osu.Game/Rulesets/Judgements/IPartialJudgement.cs similarity index 56% rename from osu.Game/Modes/Judgements/IPartialJudgement.cs rename to osu.Game/Rulesets/Judgements/IPartialJudgement.cs index 2ca1ffce4d..38080835e0 100644 --- a/osu.Game/Modes/Judgements/IPartialJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IPartialJudgement.cs @@ -1,22 +1,15 @@ // 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.Scoring; - -namespace osu.Game.Modes.Judgements +namespace osu.Game.Rulesets.Judgements { /// /// Inidicates that the judgement this is attached to is a partial judgement and the scoring value may change. - /// - /// This judgement will be continually processed by - /// unless the result is a miss and will trigger a full re-process of the when changed. - /// /// public interface IPartialJudgement { /// - /// Indicates that this partial judgement has changed and requires a full re-process of the . + /// Indicates that this partial judgement has changed and requires reprocessing. /// /// This is set to false once the judgement has been re-processed. /// diff --git a/osu.Game/Modes/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs similarity index 88% rename from osu.Game/Modes/Judgements/Judgement.cs rename to osu.Game/Rulesets/Judgements/Judgement.cs index 1bf898d25c..ed33cee5d4 100644 --- a/osu.Game/Modes/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Modes.Judgements +namespace osu.Game.Rulesets.Judgements { public abstract class Judgement { diff --git a/osu.Game/Modes/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs similarity index 77% rename from osu.Game/Modes/Mods/IApplicableMod.cs rename to osu.Game/Rulesets/Mods/IApplicableMod.cs index 90547f4402..18e1ae4b3d 100644 --- a/osu.Game/Modes/Mods/IApplicableMod.cs +++ b/osu.Game/Rulesets/Mods/IApplicableMod.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.Modes.Objects; -using osu.Game.Modes.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; -namespace osu.Game.Modes.Mods +namespace osu.Game.Rulesets.Mods { /// /// An interface for mods that are applied to a HitRenderer. @@ -17,6 +17,6 @@ namespace osu.Game.Modes.Mods /// Applies the mod to a HitRenderer. /// /// The HitRenderer to apply the mod to. - void Apply(HitRenderer hitRenderer); + void ApplyToHitRenderer(HitRenderer hitRenderer); } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToClock.cs b/osu.Game/Rulesets/Mods/IApplicableToClock.cs new file mode 100644 index 0000000000..f0502cf346 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToClock.cs @@ -0,0 +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.Timing; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make adjustments to the track. + /// + public interface IApplicableToClock + { + void ApplyToClock(IAdjustableClock clock); + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs new file mode 100644 index 0000000000..a2846c1d2f --- /dev/null +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -0,0 +1,44 @@ +// 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 System; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// The base class for gameplay modifiers. + /// + public abstract class Mod + { + /// + /// The name of this mod. + /// + public abstract string Name { get; } + + /// + /// The icon of this mod. + /// + public virtual FontAwesome Icon => FontAwesome.fa_question; + + /// + /// The user readable description of this mod. + /// + public virtual string Description => string.Empty; + + /// + /// The score multiplier of this mod. + /// + public abstract double ScoreMultiplier { get; } + + /// + /// Returns if this mod is ranked. + /// + public virtual bool Ranked => false; + + /// + /// The mods this mod cannot be enabled with. + /// + public virtual Type[] IncompatibleMods => new Type[] { }; + } +} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs new file mode 100644 index 0000000000..1217bf835f --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModAutoplay : ModAutoplay, IApplicableMod + where T : HitObject + { + protected abstract Score CreateReplayScore(Beatmap beatmap); + + public void ApplyToHitRenderer(HitRenderer hitRenderer) + { + hitRenderer.SetReplay(CreateReplayScore(hitRenderer.Beatmap)?.Replay); + } + } + + public class ModAutoplay : Mod + { + public override string Name => "Autoplay"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; + public override string Description => "Watch a perfect automated play through the song"; + public override double ScoreMultiplier => 0; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs new file mode 100644 index 0000000000..332bd2c5ac --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public class ModCinema : ModAutoplay + { + public override string Name => "Cinema"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs new file mode 100644 index 0000000000..377a4c2180 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public class ModDoubleTime : Mod, IApplicableToClock + { + public override string Name => "Double Time"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override string Description => "Zoooooooooom"; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; + + public override double ScoreMultiplier => 1.12; + + public virtual void ApplyToClock(IAdjustableClock clock) + { + clock.Rate = 1.5; + } + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs new file mode 100644 index 0000000000..bef3f04af3 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -0,0 +1,18 @@ +// 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.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModEasy : Mod + { + public override string Name => "Easy"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override string Description => "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."; + public override double ScoreMultiplier => 0.5; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs new file mode 100644 index 0000000000..63c534dc7d --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModFlashlight : Mod + { + public override string Name => "Flashlight"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override string Description => "Restricted view area."; + public override bool Ranked => true; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs new file mode 100644 index 0000000000..235fc7ad76 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModHalfTime : Mod, IApplicableToClock + { + public override string Name => "Half Time"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override string Description => "Less zoom"; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; + + public override double ScoreMultiplier => 1.12; + + public void ApplyToClock(IAdjustableClock clock) + { + clock.Rate = 0.75; + } + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs new file mode 100644 index 0000000000..b729b5ae15 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -0,0 +1,16 @@ +// 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.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModHardRock : Mod + { + public override string Name => "Hard Rock"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override string Description => "Everything just got a bit harder..."; + public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs new file mode 100644 index 0000000000..12c788ce54 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModHidden : Mod + { + public override string Name => "Hidden"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override bool Ranked => true; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs new file mode 100644 index 0000000000..d04643fb8b --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Audio; +using osu.Framework.Timing; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModNightcore : ModDoubleTime + { + public override string Name => "Nightcore"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; + public override string Description => "uguuuuuuuu"; + + public override void ApplyToClock(IAdjustableClock clock) + { + var pitchAdjust = clock as IHasPitchAdjust; + if (pitchAdjust != null) + pitchAdjust.PitchAdjust = 1.5; + else + base.ApplyToClock(clock); + } + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs new file mode 100644 index 0000000000..0c8726bbc1 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -0,0 +1,18 @@ +// 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.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModNoFail : Mod + { + public override string Name => "NoFail"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override string Description => "You can't fail, no matter what."; + public override double ScoreMultiplier => 0.5; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs new file mode 100644 index 0000000000..35217c8305 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModPerfect : ModSuddenDeath + { + public override string Name => "Perfect"; + public override string Description => "SS or quit."; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs new file mode 100644 index 0000000000..5491f8fc58 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -0,0 +1,16 @@ +// 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.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModRelax : Mod + { + public override string Name => "Relax"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; + public override double ScoreMultiplier => 0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs new file mode 100644 index 0000000000..a7dcbbc9ed --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -0,0 +1,18 @@ +// 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.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModSuddenDeath : Mod + { + public override string Name => "Sudden Death"; + public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override string Description => "Miss a note and fail."; + public override double ScoreMultiplier => 1; + public override bool Ranked => true; + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; + } +} \ No newline at end of file diff --git a/osu.Game/Modes/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs similarity index 85% rename from osu.Game/Modes/Mods/ModType.cs rename to osu.Game/Rulesets/Mods/ModType.cs index b1d0e781e1..15ffc4ad63 100644 --- a/osu.Game/Modes/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Mods +namespace osu.Game.Rulesets.Mods { public enum ModType { diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs new file mode 100644 index 0000000000..c5fac250d0 --- /dev/null +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + public class MultiMod : Mod + { + public override string Name => string.Empty; + public override string Description => string.Empty; + public override double ScoreMultiplier => 0.0; + + public Mod[] Mods; + } +} \ No newline at end of file diff --git a/osu.Game/Modes/Objects/BezierApproximator.cs b/osu.Game/Rulesets/Objects/BezierApproximator.cs similarity index 97% rename from osu.Game/Modes/Objects/BezierApproximator.cs rename to osu.Game/Rulesets/Objects/BezierApproximator.cs index 6688e6b2ce..12be591aab 100644 --- a/osu.Game/Modes/Objects/BezierApproximator.cs +++ b/osu.Game/Rulesets/Objects/BezierApproximator.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using OpenTK; -namespace osu.Game.Modes.Objects +namespace osu.Game.Rulesets.Objects { public class BezierApproximator { diff --git a/osu.Game/Modes/Objects/CircularArcApproximator.cs b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs similarity index 96% rename from osu.Game/Modes/Objects/CircularArcApproximator.cs rename to osu.Game/Rulesets/Objects/CircularArcApproximator.cs index 73db5fab29..642793fa3f 100644 --- a/osu.Game/Modes/Objects/CircularArcApproximator.cs +++ b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Framework.MathUtils; using OpenTK; -namespace osu.Game.Modes.Objects +namespace osu.Game.Rulesets.Objects { public class CircularArcApproximator { diff --git a/osu.Game/Modes/Objects/Drawables/ArmedState.cs b/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs similarity index 80% rename from osu.Game/Modes/Objects/Drawables/ArmedState.cs rename to osu.Game/Rulesets/Objects/Drawables/ArmedState.cs index 2f8d3ad07a..5e57c57f4d 100644 --- a/osu.Game/Modes/Objects/Drawables/ArmedState.cs +++ b/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Drawables +namespace osu.Game.Rulesets.Objects.Drawables { public enum ArmedState { diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs similarity index 94% rename from osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs rename to osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e346a22813..a300eeab31 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,13 +7,13 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; using Container = osu.Framework.Graphics.Containers.Container; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Audio; -namespace osu.Game.Modes.Objects.Drawables +namespace osu.Game.Rulesets.Objects.Drawables { public abstract class DrawableHitObject : Container, IStateful where TJudgement : Judgement diff --git a/osu.Game/Modes/Objects/Drawables/HitResult.cs b/osu.Game/Rulesets/Objects/Drawables/HitResult.cs similarity index 90% rename from osu.Game/Modes/Objects/Drawables/HitResult.cs rename to osu.Game/Rulesets/Objects/Drawables/HitResult.cs index e036610ae2..7492c0ab96 100644 --- a/osu.Game/Modes/Objects/Drawables/HitResult.cs +++ b/osu.Game/Rulesets/Objects/Drawables/HitResult.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Modes.Objects.Drawables +namespace osu.Game.Rulesets.Objects.Drawables { public enum HitResult { diff --git a/osu.Game/Modes/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs similarity index 83% rename from osu.Game/Modes/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs rename to osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs index 33a1f51414..0314ef3037 100644 --- a/osu.Game/Modes/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs +++ b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; -namespace osu.Game.Modes.Objects.Drawables +namespace osu.Game.Rulesets.Objects.Drawables { public interface IDrawableHitObjectWithProxiedApproach { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs new file mode 100644 index 0000000000..46fb5fcf70 --- /dev/null +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Audio; +using osu.Game.Beatmaps.Timing; +using osu.Game.Database; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects +{ + /// + /// A HitObject describes an object in a Beatmap. + /// + /// HitObjects may contain more properties for which you should be checking through the IHas* types. + /// + /// + public class HitObject + { + /// + /// The time at which the HitObject starts. + /// + public double StartTime; + + /// + /// The samples to be played when this hit object is hit. + /// + /// In the case of types, this is the sample of the curve body + /// and can be treated as the default samples for the hit object. + /// + /// + public SampleInfoList Samples = new SampleInfoList(); + + /// + /// Applies default values to this HitObject. + /// + /// The difficulty settings to use. + /// The timing settings to use. + public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + { + ControlPoint overridePoint; + ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint); + + ControlPoint samplePoint = overridePoint ?? timingPoint; + + // Initialize first sample + Samples.ForEach(s => initializeSampleInfo(s, samplePoint)); + + // Initialize any repeat samples + var repeatData = this as IHasRepeats; + repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, samplePoint))); + } + + private void initializeSampleInfo(SampleInfo sample, ControlPoint controlPoint) + { + if (sample.Volume == 0) + sample.Volume = controlPoint?.SampleVolume ?? 0; + + // If the bank is not assigned a name, assign it from the control point + if (string.IsNullOrEmpty(sample.Bank)) + sample.Bank = controlPoint?.SampleBank ?? @"normal"; + } + } +} diff --git a/osu.Game/Modes/Objects/HitObjectParser.cs b/osu.Game/Rulesets/Objects/HitObjectParser.cs similarity index 85% rename from osu.Game/Modes/Objects/HitObjectParser.cs rename to osu.Game/Rulesets/Objects/HitObjectParser.cs index 5aa9f08589..ea0b5e0d2e 100644 --- a/osu.Game/Modes/Objects/HitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/HitObjectParser.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects +namespace osu.Game.Rulesets.Objects { public abstract class HitObjectParser { diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs new file mode 100644 index 0000000000..30c10c302a --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// Legacy osu!catch Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasCombo, IHasXPosition + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs new file mode 100644 index 0000000000..5c534456ef --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -0,0 +1,47 @@ +// 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.Audio; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// A HitObjectParser to parse legacy osu!catch Beatmaps. + /// + internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + X = position.X, + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + { + return new ConvertSlider + { + X = position.X, + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + EndTime = endTime + }; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs new file mode 100644 index 0000000000..781fe8f7fa --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Catch +{ + /// + /// Legacy osu!catch Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Modes/Objects/Legacy/LegacySpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs similarity index 56% rename from osu.Game/Modes/Objects/Legacy/LegacySpinner.cs rename to osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 8f65d5e8a1..0652737b12 100644 --- a/osu.Game/Modes/Objects/Legacy/LegacySpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -1,14 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Modes.Objects.Legacy +namespace osu.Game.Rulesets.Objects.Legacy.Catch { /// - /// Legacy Spinner-type, used for parsing Beatmaps. + /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal class LegacySpinner : HitObject, IHasEndTime + internal sealed class ConvertSpinner : HitObject, IHasEndTime { public double EndTime { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs new file mode 100644 index 0000000000..c5551082ec --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -0,0 +1,284 @@ +// 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.Rulesets.Objects.Types; +using System; +using System.Collections.Generic; +using System.Globalization; +using osu.Game.Beatmaps.Formats; +using osu.Game.Audio; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + /// + /// A HitObjectParser to parse legacy Beatmaps. + /// + internal abstract class ConvertHitObjectParser : HitObjectParser + { + public override HitObject Parse(string text) + { + string[] split = text.Split(','); + ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax; + bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); + type &= ~ConvertHitObjectType.NewCombo; + + var soundType = (LegacySoundType)int.Parse(split[4]); + var bankInfo = new SampleBankInfo(); + + HitObject result; + + if ((type & ConvertHitObjectType.Circle) > 0) + { + result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo); + + if (split.Length > 5) + readCustomSampleBanks(split[5], bankInfo); + } + else if ((type & ConvertHitObjectType.Slider) > 0) + { + CurveType curveType = CurveType.Catmull; + double length = 0; + var points = new List { new Vector2(int.Parse(split[0]), int.Parse(split[1])) }; + + string[] pointsplit = split[5].Split('|'); + foreach (string t in pointsplit) + { + if (t.Length == 1) + { + switch (t) + { + case @"C": + curveType = CurveType.Catmull; + break; + case @"B": + curveType = CurveType.Bezier; + break; + case @"L": + curveType = CurveType.Linear; + break; + case @"P": + curveType = CurveType.PerfectCurve; + break; + } + continue; + } + + string[] temp = t.Split(':'); + points.Add(new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture))); + } + + int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); + + if (repeatCount > 9000) + throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + + if (split.Length > 7) + length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); + + if (split.Length > 10) + readCustomSampleBanks(split[10], bankInfo); + + // One node for each repeat + the start and end nodes + // Note that the first length of the slider is considered a repeat, but there are no actual repeats happening + int nodes = Math.Max(0, repeatCount - 1) + 2; + + // Populate node sample bank infos with the default hit object sample bank + var nodeBankInfos = new List(); + for (int i = 0; i < nodes; i++) + nodeBankInfos.Add(bankInfo.Clone()); + + // Read any per-node sample banks + if (split.Length > 9 && split[9].Length > 0) + { + string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) + { + if (i >= sets.Length) + break; + + SampleBankInfo info = nodeBankInfos[i]; + readCustomSampleBanks(sets[i], info); + } + } + + // Populate node sound types with the default hit object sound type + var nodeSoundTypes = new List(); + for (int i = 0; i < nodes; i++) + nodeSoundTypes.Add(soundType); + + // Read any per-node sound types + if (split.Length > 8 && split[8].Length > 0) + { + string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) + { + if (i >= adds.Length) + break; + + int sound; + int.TryParse(adds[i], out sound); + nodeSoundTypes[i] = (LegacySoundType)sound; + } + } + + // Generate the final per-node samples + var nodeSamples = new List(nodes); + for (int i = 0; i <= repeatCount; i++) + nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); + + result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples); + } + else if ((type & ConvertHitObjectType.Spinner) > 0) + { + result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture)); + + if (split.Length > 6) + readCustomSampleBanks(split[6], bankInfo); + } + else if ((type & ConvertHitObjectType.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 ConvertHold + { + Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])), + NewCombo = combo + }; + } + else + throw new InvalidOperationException($@"Unknown hit object type {type}"); + + result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + result.Samples = convertSoundType(soundType, bankInfo); + + return result; + } + + private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) + { + 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; + + bankInfo.Normal = stringBank; + bankInfo.Add = stringAddBank; + + if (split.Length > 3) + bankInfo.Volume = int.Parse(split[3]); + } + + /// + /// Creates a legacy Hit-type hit object. + /// + /// The position of the hit object. + /// Whether the hit object creates a new combo. + /// The hit object. + protected abstract HitObject CreateHit(Vector2 position, bool newCombo); + + /// + /// Creats a legacy Slider-type hit object. + /// + /// The position of the hit object. + /// Whether the hit object creates a new combo. + /// The slider control points. + /// The slider length. + /// The slider curve type. + /// The slider repeat count. + /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. + /// The hit object. + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples); + + /// + /// Creates a legacy Spinner-type hit object. + /// + /// The position of the hit object. + /// The spinner end time. + /// The hit object. + protected abstract HitObject CreateSpinner(Vector2 position, double endTime); + + private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) + { + var soundTypes = new SampleInfoList + { + new SampleInfo + { + Bank = bankInfo.Normal, + Name = SampleInfo.HIT_NORMAL, + Volume = bankInfo.Volume + } + }; + + if ((type & LegacySoundType.Finish) > 0) + { + soundTypes.Add(new SampleInfo + { + Bank = bankInfo.Add, + Name = SampleInfo.HIT_FINISH, + Volume = bankInfo.Volume + }); + } + + if ((type & LegacySoundType.Whistle) > 0) + { + soundTypes.Add(new SampleInfo + { + Bank = bankInfo.Add, + Name = SampleInfo.HIT_WHISTLE, + Volume = bankInfo.Volume + }); + } + + if ((type & LegacySoundType.Clap) > 0) + { + soundTypes.Add(new SampleInfo + { + Bank = bankInfo.Add, + Name = SampleInfo.HIT_CLAP, + Volume = bankInfo.Volume + }); + } + + return soundTypes; + } + + private class SampleBankInfo + { + public string Normal; + public string Add; + public int Volume; + + public SampleBankInfo Clone() + { + return (SampleBankInfo)MemberwiseClone(); + } + } + + [Flags] + private enum LegacySoundType + { + None = 0, + Normal = 1, + Whistle = 2, + Finish = 4, + Clap = 8 + } + } +} diff --git a/osu.Game/Modes/Objects/Legacy/LegacyHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs similarity index 77% rename from osu.Game/Modes/Objects/Legacy/LegacyHitObjectType.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs index 416e1abe76..09f005e666 100644 --- a/osu.Game/Modes/Objects/Legacy/LegacyHitObjectType.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs @@ -3,10 +3,10 @@ using System; -namespace osu.Game.Modes.Objects.Legacy +namespace osu.Game.Rulesets.Objects.Legacy { [Flags] - public enum LegacyHitObjectType + internal enum ConvertHitObjectType { Circle = 1 << 0, Slider = 1 << 1, diff --git a/osu.Game/Modes/Objects/Legacy/LegacyHold.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs similarity index 58% rename from osu.Game/Modes/Objects/Legacy/LegacyHold.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs index 4f858c16f1..d79f6e324e 100644 --- a/osu.Game/Modes/Objects/Legacy/LegacyHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs @@ -2,17 +2,21 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Modes.Objects.Legacy +namespace osu.Game.Rulesets.Objects.Legacy { /// /// Legacy Hold-type, used for parsing "specials" in beatmaps. /// - public sealed class LegacyHold : HitObject, IHasPosition, IHasCombo, IHasHold + internal sealed class ConvertHold : HitObject, IHasPosition, IHasCombo, IHasHold { public Vector2 Position { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + public bool NewCombo { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs new file mode 100644 index 0000000000..7580404e81 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using System; +using System.Collections.Generic; +using OpenTK; +using osu.Game.Audio; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + internal abstract class ConvertSlider : HitObject, IHasCurve + { + public List ControlPoints { get; set; } + public CurveType CurveType { get; set; } + public double Distance { get; set; } + + public List RepeatSamples { get; set; } + public int RepeatCount { get; set; } = 1; + + public double EndTime { get; set; } + public double Duration { get; set; } + + public Vector2 PositionAt(double progress) + { + throw new NotImplementedException(); + } + + public double ProgressAt(double progress) + { + throw new NotImplementedException(); + } + + public int RepeatAt(double progress) + { + throw new NotImplementedException(); + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs new file mode 100644 index 0000000000..2a65b853b7 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// Legacy osu!mania Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasXPosition, IHasCombo + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs new file mode 100644 index 0000000000..224f068323 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -0,0 +1,48 @@ +// 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.Audio; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// A HitObjectParser to parse legacy osu!mania Beatmaps. + /// + internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + X = position.X, + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + { + return new ConvertSlider + { + X = position.X, + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + X = position.X, + EndTime = endTime + }; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs new file mode 100644 index 0000000000..adc0c064bc --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// Legacy osu!mania Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo + { + public float X { get; set; } + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs new file mode 100644 index 0000000000..f72c5b9894 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Mania +{ + /// + /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + + public float X { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs new file mode 100644 index 0000000000..0c1000965c --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// Legacy osu! Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo + { + public Vector2 Position { get; set; } + + public float X => Position.X; + + public float Y => Position.Y; + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs new file mode 100644 index 0000000000..41bf142831 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -0,0 +1,48 @@ +// 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.Audio; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// A HitObjectParser to parse legacy osu! Beatmaps. + /// + internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + Position = position, + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + { + return new ConvertSlider + { + Position = position, + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + Position = position, + EndTime = endTime + }; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs new file mode 100644 index 0000000000..75a6d80560 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// Legacy osu! Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo + { + public Vector2 Position { get; set; } + + public float X => Position.X; + + public float Y => Position.Y; + + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs new file mode 100644 index 0000000000..2b2dbe0765 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Objects.Legacy.Osu +{ + /// + /// Legacy osu! Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + + public Vector2 Position { get; set; } + + public float X => Position.X; + + public float Y => Position.Y; + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs new file mode 100644 index 0000000000..7088cc480b --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. + /// + internal sealed class ConvertHit : HitObject, IHasCombo + { + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs new file mode 100644 index 0000000000..0d755d7527 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.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 OpenTK; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Types; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// A HitObjectParser to parse legacy osu!taiko Beatmaps. + /// + internal class ConvertHitObjectParser : Legacy.ConvertHitObjectParser + { + protected override HitObject CreateHit(Vector2 position, bool newCombo) + { + return new ConvertHit + { + NewCombo = newCombo, + }; + } + + protected override HitObject CreateSlider(Vector2 position, bool newCombo, List controlPoints, double length, CurveType curveType, int repeatCount, List repeatSamples) + { + return new ConvertSlider + { + NewCombo = newCombo, + ControlPoints = controlPoints, + Distance = length, + CurveType = curveType, + RepeatSamples = repeatSamples, + RepeatCount = repeatCount + }; + } + + protected override HitObject CreateSpinner(Vector2 position, double endTime) + { + return new ConvertSpinner + { + EndTime = endTime + }; + } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs new file mode 100644 index 0000000000..b472423a1d --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// Legacy osu!taiko Slider-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo + { + public bool NewCombo { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs new file mode 100644 index 0000000000..abef667d91 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Objects.Legacy.Taiko +{ + /// + /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. + /// + internal sealed class ConvertSpinner : HitObject, IHasEndTime + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + } +} diff --git a/osu.Game/Modes/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs similarity index 96% rename from osu.Game/Modes/Objects/SliderCurve.cs rename to osu.Game/Rulesets/Objects/SliderCurve.cs index 642a65af21..8bf85e498c 100644 --- a/osu.Game/Modes/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderCurve.cs @@ -4,10 +4,10 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.MathUtils; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets.Objects.Types; using OpenTK; -namespace osu.Game.Modes.Objects +namespace osu.Game.Rulesets.Objects { public class SliderCurve { diff --git a/osu.Game/Modes/Objects/Types/CurveType.cs b/osu.Game/Rulesets/Objects/Types/CurveType.cs similarity index 82% rename from osu.Game/Modes/Objects/Types/CurveType.cs rename to osu.Game/Rulesets/Objects/Types/CurveType.cs index ba5d3f37ac..18db712a65 100644 --- a/osu.Game/Modes/Objects/Types/CurveType.cs +++ b/osu.Game/Rulesets/Objects/Types/CurveType.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { public enum CurveType { diff --git a/osu.Game/Modes/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs similarity index 87% rename from osu.Game/Modes/Objects/Types/IHasCombo.cs rename to osu.Game/Rulesets/Objects/Types/IHasCombo.cs index 1ca381372d..f053fdcf49 100644 --- a/osu.Game/Modes/Objects/Types/IHasCombo.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A HitObject that is part of a combo. diff --git a/osu.Game/Modes/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs similarity index 90% rename from osu.Game/Modes/Objects/Types/IHasCurve.cs rename to osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 0db799a15f..399138bc24 100644 --- a/osu.Game/Modes/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -4,18 +4,13 @@ using System.Collections.Generic; using OpenTK; -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A HitObject that has a curve. /// public interface IHasCurve : IHasDistance, IHasRepeats { - /// - /// The curve. - /// - SliderCurve Curve { get; } - /// /// The control points that shape the curve. /// diff --git a/osu.Game/Modes/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs similarity index 88% rename from osu.Game/Modes/Objects/Types/IHasDistance.cs rename to osu.Game/Rulesets/Objects/Types/IHasDistance.cs index 87863e64e6..a5e487a0ba 100644 --- a/osu.Game/Modes/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A HitObject that has a positional length. diff --git a/osu.Game/Modes/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs similarity index 89% rename from osu.Game/Modes/Objects/Types/IHasEndTime.cs rename to osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index e96258812c..ac30afe5fb 100644 --- a/osu.Game/Modes/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A HitObject that ends at a different time than its start time. diff --git a/osu.Game/Modes/Objects/Types/IHasHold.cs b/osu.Game/Rulesets/Objects/Types/IHasHold.cs similarity index 85% rename from osu.Game/Modes/Objects/Types/IHasHold.cs rename to osu.Game/Rulesets/Objects/Types/IHasHold.cs index b9f4939091..82ec790524 100644 --- a/osu.Game/Modes/Objects/Types/IHasHold.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasHold.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A special type of HitObject, mostly used for legacy conversion of "holds". diff --git a/osu.Game/Modes/Objects/Types/IHasPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs similarity index 76% rename from osu.Game/Modes/Objects/Types/IHasPosition.cs rename to osu.Game/Rulesets/Objects/Types/IHasPosition.cs index 8138bf6662..6eca86656d 100644 --- a/osu.Game/Modes/Objects/Types/IHasPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs @@ -3,12 +3,12 @@ using OpenTK; -namespace osu.Game.Modes.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A HitObject that has a starting position. /// - public interface IHasPosition + public interface IHasPosition : IHasXPosition, IHasYPosition { /// /// The starting position of the HitObject. diff --git a/osu.Game/Modes/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs similarity index 55% rename from osu.Game/Modes/Objects/Types/IHasRepeats.cs rename to osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index a34774d0ef..5abad2d661 100644 --- a/osu.Game/Modes/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -1,7 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Modes.Objects.Types +using osu.Game.Audio; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Objects.Types { /// /// A HitObject that spans some length. @@ -12,5 +15,10 @@ namespace osu.Game.Modes.Objects.Types /// The amount of times the HitObject repeats. /// int RepeatCount { get; } + + /// + /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). + /// + List RepeatSamples { get; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs new file mode 100644 index 0000000000..b0ad3af7d2 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a starting X-position. + /// + public interface IHasXPosition + { + /// + /// The starting X-position of this HitObject. + /// + float X { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs new file mode 100644 index 0000000000..222e8f762f --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that has a starting Y-position. + /// + public interface IHasYPosition + { + /// + /// The starting Y-position of this HitObject. + /// + float Y { get; } + } +} diff --git a/osu.Game/Modes/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs similarity index 95% rename from osu.Game/Modes/Replays/FramedReplayInputHandler.cs rename to osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index ae20ece515..60da35fd91 100644 --- a/osu.Game/Modes/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -12,7 +12,7 @@ using OpenTK.Input; using KeyboardState = osu.Framework.Input.KeyboardState; using MouseState = osu.Framework.Input.MouseState; -namespace osu.Game.Modes.Replays +namespace osu.Game.Rulesets.Replays { /// /// The ReplayHandler will take a replay and handle the propagation of updates to the input stack. @@ -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/Replays/Replay.cs b/osu.Game/Rulesets/Replays/Replay.cs similarity index 65% rename from osu.Game/Modes/Replays/Replay.cs rename to osu.Game/Rulesets/Replays/Replay.cs index 62f60358e0..36e1b24e73 100644 --- a/osu.Game/Modes/Replays/Replay.cs +++ b/osu.Game/Rulesets/Replays/Replay.cs @@ -2,11 +2,16 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using osu.Game.Users; -namespace osu.Game.Modes.Replays +namespace osu.Game.Rulesets.Replays { public class Replay { + protected const double KEY_UP_DELAY = 50; + + public User User; + public List Frames = new List(); } -} \ No newline at end of file +} diff --git a/osu.Game/Modes/Replays/ReplayButtonState.cs b/osu.Game/Rulesets/Replays/ReplayButtonState.cs similarity index 86% rename from osu.Game/Modes/Replays/ReplayButtonState.cs rename to osu.Game/Rulesets/Replays/ReplayButtonState.cs index d49139226c..be55a153cb 100644 --- a/osu.Game/Modes/Replays/ReplayButtonState.cs +++ b/osu.Game/Rulesets/Replays/ReplayButtonState.cs @@ -3,7 +3,7 @@ using System; -namespace osu.Game.Modes.Replays +namespace osu.Game.Rulesets.Replays { [Flags] public enum ReplayButtonState diff --git a/osu.Game/Modes/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs similarity index 77% rename from osu.Game/Modes/Replays/ReplayFrame.cs rename to osu.Game/Rulesets/Replays/ReplayFrame.cs index 4cd681beaf..b0f62e5271 100644 --- a/osu.Game/Modes/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -3,16 +3,16 @@ using OpenTK; -namespace osu.Game.Modes.Replays +namespace osu.Game.Rulesets.Replays { public class ReplayFrame { - public Vector2 Position => new Vector2(MouseX, MouseY); + public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); - public bool IsImportant => MouseLeft || MouseRight; + public bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight); - public float MouseX; - public float MouseY; + public float? MouseX; + public float? MouseY; public bool MouseLeft => MouseLeft1 || MouseLeft2; public bool MouseRight => MouseRight1 || MouseRight2; @@ -55,10 +55,10 @@ namespace osu.Game.Modes.Replays } - public ReplayFrame(double time, float posX, float posY, ReplayButtonState buttonState) + public ReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState) { - MouseX = posX; - MouseY = posY; + MouseX = mouseX; + MouseY = mouseY; ButtonState = buttonState; Time = time; } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs new file mode 100644 index 0000000000..ea35e61b36 --- /dev/null +++ b/osu.Game/Rulesets/Ruleset.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using System.Collections.Generic; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets +{ + public abstract class Ruleset + { + public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; + + public abstract IEnumerable GetModsFor(ModType type); + + /// + /// Attempt to create a HitRenderer for the provided beatmap. + /// + /// + /// Unable to successfully load the beatmap to be usable with this ruleset. + /// + public abstract HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap); + + public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap); + + public abstract ScoreProcessor CreateScoreProcessor(); + + public virtual FontAwesome Icon => FontAwesome.fa_question_circle; + + public abstract string Description { get; } + + public abstract IEnumerable CreateGameplayKeys(); + + /// + /// Do not override this unless you are a legacy mode. + /// + public virtual int LegacyID => -1; + } +} diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs new file mode 100644 index 0000000000..15d8690322 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Database; +using osu.Game.Rulesets.Mods; +using osu.Game.Users; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Scoring +{ + public class Score + { + public ScoreRank Rank { get; set; } + + [JsonProperty(@"score")] + public double TotalScore { get; set; } + + public double Accuracy { get; set; } + + public double Health { get; set; } = 1; + + [JsonProperty(@"max_combo")] + public int MaxCombo { get; set; } + + public int Combo { get; set; } + + [JsonProperty(@"mods")] + protected string[] ModStrings { get; set; } //todo: parse to Mod objects + + public RulesetInfo Ruleset { get; set; } + + public Mod[] Mods { get; set; } + + [JsonProperty(@"user")] + public User User; + + [JsonProperty(@"replay_data")] + public Replay Replay; + + public BeatmapInfo Beatmap; + + [JsonProperty(@"score_id")] + public long OnlineScoreID; + + [JsonProperty(@"created_at")] + public DateTime Date; + + [JsonProperty(@"statistics")] + private Dictionary jsonStats + { + set + { + foreach (var kvp in value) + { + string key = kvp.Key; + switch (key) + { + case @"count_300": + key = @"300"; + break; + case @"count_100": + key = @"100"; + break; + case @"count_50": + key = @"50"; + break; + case @"count_miss": + key = @"x"; + break; + default: + continue; + } + + Statistics.Add(key, kvp.Value); + } + } + } + + public Dictionary Statistics = new Dictionary(); + } +} diff --git a/osu.Game/Modes/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs similarity index 75% rename from osu.Game/Modes/Scoring/ScoreProcessor.cs rename to osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ba845b84dc..11c1c273e2 100644 --- a/osu.Game/Modes/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -5,12 +5,12 @@ using System; using System.Collections.Generic; using osu.Framework.Configuration; 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; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Modes.Scoring +namespace osu.Game.Rulesets.Scoring { public abstract class ScoreProcessor { @@ -19,6 +19,11 @@ namespace osu.Game.Modes.Scoring /// public event Action Failed; + /// + /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the . + /// + public event Action NewJudgement; + /// /// The current total score. /// @@ -61,18 +66,20 @@ namespace osu.Game.Modes.Scoring Reset(); } - /// - /// Creates a Score applicable to the game mode in which this ScoreProcessor resides. - /// - /// The Score. - public virtual Score CreateScore() => new Score + private ScoreRank rankFrom(double acc) { - TotalScore = TotalScore, - Combo = Combo, - MaxCombo = HighestCombo, - Accuracy = Accuracy, - Health = Health, - }; + if (acc == 1) + return ScoreRank.X; + if (acc > 0.95) + return ScoreRank.S; + if (acc > 0.9) + return ScoreRank.A; + if (acc > 0.8) + return ScoreRank.B; + if (acc > 0.7) + return ScoreRank.C; + return ScoreRank.D; + } /// /// Resets this ScoreProcessor to a default state. @@ -102,6 +109,29 @@ namespace osu.Game.Modes.Scoring alreadyFailed = true; Failed?.Invoke(); } + + /// + /// Notifies subscribers of that a new judgement has occurred. + /// + /// The judgement to notify subscribers of. + protected void NotifyNewJudgement(Judgement judgement) + { + NewJudgement?.Invoke(judgement); + } + + /// + /// Retrieve a score populated with data for the current play this processor is responsible for. + /// + public virtual void PopulateScore(Score score) + { + score.TotalScore = TotalScore; + score.Combo = Combo; + score.MaxCombo = HighestCombo; + score.Accuracy = Accuracy; + score.Rank = rankFrom(Accuracy); + score.Date = DateTime.Now; + score.Health = Health; + } } public abstract class ScoreProcessor : ScoreProcessor @@ -161,6 +191,8 @@ namespace osu.Game.Modes.Scoring Judgements.Add(judgement); OnNewJudgement(judgement); + + NotifyNewJudgement(judgement); } else OnJudgementChanged(judgement); @@ -193,4 +225,4 @@ namespace osu.Game.Modes.Scoring /// The judgement that triggered this calculation. protected virtual void OnJudgementChanged(TJudgement judgement) { } } -} \ No newline at end of file +} diff --git a/osu.Game/Modes/Scoring/ScoreRank.cs b/osu.Game/Rulesets/Scoring/ScoreRank.cs similarity index 90% rename from osu.Game/Modes/Scoring/ScoreRank.cs rename to osu.Game/Rulesets/Scoring/ScoreRank.cs index 743f24ecd6..f4a6a1e03c 100644 --- a/osu.Game/Modes/Scoring/ScoreRank.cs +++ b/osu.Game/Rulesets/Scoring/ScoreRank.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Modes.Scoring +namespace osu.Game.Rulesets.Scoring { public enum ScoreRank { diff --git a/osu.Game/Modes/UI/ComboCounter.cs b/osu.Game/Rulesets/UI/ComboCounter.cs similarity index 96% rename from osu.Game/Modes/UI/ComboCounter.cs rename to osu.Game/Rulesets/UI/ComboCounter.cs index 8c0327fa04..d21059cbdb 100644 --- a/osu.Game/Modes/UI/ComboCounter.cs +++ b/osu.Game/Rulesets/UI/ComboCounter.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Transforms; using osu.Framework.MathUtils; using osu.Game.Graphics.Sprites; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { public abstract class ComboCounter : Container { diff --git a/osu.Game/Modes/UI/ComboResultCounter.cs b/osu.Game/Rulesets/UI/ComboResultCounter.cs similarity index 95% rename from osu.Game/Modes/UI/ComboResultCounter.cs rename to osu.Game/Rulesets/UI/ComboResultCounter.cs index 63009c5351..4b19b2c1ff 100644 --- a/osu.Game/Modes/UI/ComboResultCounter.cs +++ b/osu.Game/Rulesets/UI/ComboResultCounter.cs @@ -7,7 +7,7 @@ using osu.Framework.MathUtils; using osu.Game.Graphics.UserInterface; using System; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { /// /// Used to display combo with a roll-up animation in results screen. diff --git a/osu.Game/Modes/UI/HealthDisplay.cs b/osu.Game/Rulesets/UI/HealthDisplay.cs similarity index 91% rename from osu.Game/Modes/UI/HealthDisplay.cs rename to osu.Game/Rulesets/UI/HealthDisplay.cs index 4c8d7e4ab8..5c6b9d2fe3 100644 --- a/osu.Game/Modes/UI/HealthDisplay.cs +++ b/osu.Game/Rulesets/UI/HealthDisplay.cs @@ -4,7 +4,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { public abstract class HealthDisplay : Container { diff --git a/osu.Game/Modes/UI/HitRenderer.cs b/osu.Game/Rulesets/UI/HitRenderer.cs similarity index 75% rename from osu.Game/Modes/UI/HitRenderer.cs rename to osu.Game/Rulesets/UI/HitRenderer.cs index a958c61c68..25d8bae205 100644 --- a/osu.Game/Modes/UI/HitRenderer.cs +++ b/osu.Game/Rulesets/UI/HitRenderer.cs @@ -5,19 +5,21 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Modes.Judgements; -using osu.Game.Modes.Mods; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Objects.Drawables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Play; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Game.Modes.Replays; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using OpenTK; +using osu.Game.Rulesets.Beatmaps; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { /// /// Base HitRenderer. Doesn't hold objects. @@ -32,6 +34,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. /// @@ -52,6 +59,8 @@ namespace osu.Game.Modes.UI /// public bool HasReplayLoaded => InputManager.ReplayInputHandler != null; + public abstract IEnumerable Objects { get; } + /// /// Whether all the HitObjects have been judged. /// @@ -82,11 +91,17 @@ namespace osu.Game.Modes.UI protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new FramedReplayInputHandler(replay); + public Replay Replay { get; private set; } + /// /// Sets a replay to be used, overriding local input. /// /// The replay, null for local input. - public void SetReplay(Replay replay) => InputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; + public void SetReplay(Replay replay) + { + Replay = replay; + InputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; + } } /// @@ -101,7 +116,7 @@ namespace osu.Game.Modes.UI where TObject : HitObject { /// - /// The Beatmap + /// The Beatmap /// public Beatmap Beatmap; @@ -111,8 +126,12 @@ namespace osu.Game.Modes.UI RelativeSizeAxes = Axes.Both; - IBeatmapConverter converter = CreateBeatmapConverter(); - IBeatmapProcessor processor = CreateBeatmapProcessor(); + BeatmapConverter converter = CreateBeatmapConverter(); + BeatmapProcessor processor = CreateBeatmapProcessor(); + + // Check if the beatmap can be converted + if (!converter.CanConvert(beatmap.Beatmap)) + throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can't be converted for the current ruleset."); // Convert the beatmap Beatmap = converter.Convert(beatmap.Beatmap); @@ -128,7 +147,6 @@ namespace osu.Game.Modes.UI applyMods(beatmap.Mods.Value); } - /// /// Applies the active mods to this HitRenderer. /// @@ -139,21 +157,21 @@ namespace osu.Game.Modes.UI return; foreach (var mod in mods.OfType>()) - mod.Apply(this); + mod.ApplyToHitRenderer(this); } - /// - /// Creates a converter to convert Beatmap to a specific mode. - /// - /// The Beatmap converter. - protected abstract IBeatmapConverter CreateBeatmapConverter(); - /// /// Creates a processor to perform post-processing operations /// on HitObjects in converted Beatmaps. /// /// The Beatmap processor. - protected abstract IBeatmapProcessor CreateBeatmapProcessor(); + protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); + + /// + /// Creates a converter to convert Beatmap to a specific mode. + /// + /// The Beatmap converter. + protected abstract BeatmapConverter CreateBeatmapConverter(); } /// @@ -179,6 +197,8 @@ namespace osu.Game.Modes.UI private readonly Container content; + public override IEnumerable Objects => Beatmap.HitObjects; + protected HitRenderer(WorkingBeatmap beatmap) : base(beatmap) { @@ -219,6 +239,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. /// @@ -245,4 +278,12 @@ namespace osu.Game.Modes.UI /// The Playfield. protected abstract Playfield CreatePlayfield(); } + + public class BeatmapInvalidForRulesetException : Exception + { + public BeatmapInvalidForRulesetException(string text) + : base(text) + { + } + } } diff --git a/osu.Game/Modes/UI/HudOverlay.cs b/osu.Game/Rulesets/UI/HudOverlay.cs similarity index 83% rename from osu.Game/Modes/UI/HudOverlay.cs rename to osu.Game/Rulesets/UI/HudOverlay.cs index 355b62bc57..47cf157732 100644 --- a/osu.Game/Modes/UI/HudOverlay.cs +++ b/osu.Game/Rulesets/UI/HudOverlay.cs @@ -8,13 +8,13 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Scoring; using osu.Framework.Input; using OpenTK.Input; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { public abstract class HudOverlay : Container { @@ -22,10 +22,11 @@ namespace osu.Game.Modes.UI private readonly Container content; public readonly KeyCounterCollection KeyCounter; - public readonly ComboCounter ComboCounter; + public readonly RollingCounter ComboCounter; public readonly ScoreCounter ScoreCounter; - public readonly PercentageCounter AccuracyCounter; + public readonly RollingCounter AccuracyCounter; public readonly HealthDisplay HealthDisplay; + public readonly SongProgress Progress; private Bindable showKeyCounter; private Bindable showHud; @@ -33,10 +34,11 @@ namespace osu.Game.Modes.UI private static bool hasShownNotificationOnce; protected abstract KeyCounterCollection CreateKeyCounter(); - protected abstract ComboCounter CreateComboCounter(); - protected abstract PercentageCounter CreateAccuracyCounter(); + protected abstract RollingCounter CreateComboCounter(); + protected abstract RollingCounter CreateAccuracyCounter(); protected abstract ScoreCounter CreateScoreCounter(); protected abstract HealthDisplay CreateHealthDisplay(); + protected abstract SongProgress CreateProgress(); protected HudOverlay() { @@ -53,6 +55,7 @@ namespace osu.Game.Modes.UI ScoreCounter = CreateScoreCounter(), AccuracyCounter = CreateAccuracyCounter(), HealthDisplay = CreateHealthDisplay(), + Progress = CreateProgress(), } }); } @@ -91,7 +94,7 @@ namespace osu.Game.Modes.UI } } - public void BindProcessor(ScoreProcessor processor) + public virtual void BindProcessor(ScoreProcessor processor) { ScoreCounter?.Current.BindTo(processor.TotalScore); AccuracyCounter?.Current.BindTo(processor.Accuracy); @@ -99,7 +102,7 @@ namespace osu.Game.Modes.UI HealthDisplay?.Current.BindTo(processor.Health); } - public void BindHitRenderer(HitRenderer hitRenderer) + public virtual void BindHitRenderer(HitRenderer hitRenderer) { hitRenderer.InputManager.Add(KeyCounter.GetReceptor()); } diff --git a/osu.Game/Modes/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs similarity index 94% rename from osu.Game/Modes/UI/ModIcon.cs rename to osu.Game/Rulesets/UI/ModIcon.cs index 1e0aa89a41..8301796c1f 100644 --- a/osu.Game/Modes/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { public class ModIcon : Container { diff --git a/osu.Game/Modes/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs similarity index 83% rename from osu.Game/Modes/UI/Playfield.cs rename to osu.Game/Rulesets/UI/Playfield.cs index f31ee0f189..0586c0385a 100644 --- a/osu.Game/Modes/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -1,15 +1,16 @@ // 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; -using osu.Game.Modes.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using OpenTK; -using osu.Game.Modes.Judgements; +using osu.Game.Rulesets.Judgements; using osu.Framework.Allocation; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { public abstract class Playfield : Container where TObject : HitObject @@ -38,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, @@ -46,6 +50,7 @@ namespace osu.Game.Modes.UI { content = new Container { + AlwaysReceiveInput = true, RelativeSizeAxes = Axes.Both, } } @@ -63,6 +68,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/StandardComboCounter.cs b/osu.Game/Rulesets/UI/StandardComboCounter.cs similarity index 96% rename from osu.Game/Modes/UI/StandardComboCounter.cs rename to osu.Game/Rulesets/UI/StandardComboCounter.cs index 08bb3add84..ad05c83839 100644 --- a/osu.Game/Modes/UI/StandardComboCounter.cs +++ b/osu.Game/Rulesets/UI/StandardComboCounter.cs @@ -3,7 +3,7 @@ using OpenTK; -namespace osu.Game.Modes.UI +namespace osu.Game.Rulesets.UI { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. diff --git a/osu.Game/Rulesets/UI/StandardHealthDisplay.cs b/osu.Game/Rulesets/UI/StandardHealthDisplay.cs new file mode 100644 index 0000000000..3d9a5489dc --- /dev/null +++ b/osu.Game/Rulesets/UI/StandardHealthDisplay.cs @@ -0,0 +1,107 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using System; + +namespace osu.Game.Rulesets.UI +{ + public class StandardHealthDisplay : HealthDisplay, IHasAccentColour + { + /// + /// The base opacity of the glow. + /// + private const float base_glow_opacity = 0.6f; + + /// + /// The number of sequential hits required within to reach the maximum glow opacity. + /// + private const int glow_max_hits = 8; + + /// + /// The amount of time to delay before fading the glow opacity back to . + /// + /// This is calculated to require a stream snapped to 1/4 at 150bpm to reach the maximum glow opacity with hits. + /// + /// + private const float glow_fade_delay = 100; + + /// + /// The amount of time to fade the glow to after . + /// + private const double glow_fade_time = 500; + + private readonly Container fill; + + public Color4 AccentColour + { + get { return fill.Colour; } + set { fill.Colour = value; } + } + + private Color4 glowColour; + public Color4 GlowColour + { + get { return glowColour; } + set + { + if (glowColour == value) + return; + glowColour = value; + + fill.EdgeEffect = new EdgeEffect + { + Colour = glowColour.Opacity(base_glow_opacity), + Radius = 8, + Roundness = 4, + Type = EdgeEffectType.Glow, + }; + } + } + + public StandardHealthDisplay() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + fill = new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0, 1), + Masking = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }, + }; + } + + public void Flash(Judgement judgement) + { + if (judgement.Result == HitResult.Miss) + return; + + fill.FadeEdgeEffectTo(Math.Min(1, fill.EdgeEffect.Colour.Linear.A + (1f - base_glow_opacity) / glow_max_hits), 50, EasingTypes.OutQuint); + fill.Delay(glow_fade_delay); + fill.FadeEdgeEffectTo(base_glow_opacity, glow_fade_time, EasingTypes.OutQuint); + } + + protected override void SetHealth(float value) => fill.ResizeTo(new Vector2(value, 1), 200, EasingTypes.OutQuint); + } +} diff --git a/osu.Game/Rulesets/UI/StandardHudOverlay.cs b/osu.Game/Rulesets/UI/StandardHudOverlay.cs new file mode 100644 index 0000000000..c68e29f98a --- /dev/null +++ b/osu.Game/Rulesets/UI/StandardHudOverlay.cs @@ -0,0 +1,91 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + public class StandardHudOverlay : HudOverlay + { + protected override RollingCounter CreateAccuracyCounter() => new PercentageCounter + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Position = new Vector2(0, 35), + TextSize = 20, + Margin = new MarginPadding { Right = 140 }, + }; + + protected override RollingCounter CreateComboCounter() => new SimpleComboCounter + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopLeft, + Position = new Vector2(0, 35), + Margin = new MarginPadding { Left = 140 }, + TextSize = 20, + }; + + protected override HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay + { + Size = new Vector2(1, 5), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Top = 20 } + }; + + protected override KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection + { + IsCounting = true, + FadeTime = 50, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(10), + Y = - TwoLayerButton.SIZE_RETRACTED.Y, + }; + + protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextSize = 40, + Position = new Vector2(0, 30), + }; + + protected override SongProgress CreateProgress() => new SongProgress + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ComboCounter.AccentColour = colours.BlueLighter; + AccuracyCounter.AccentColour = colours.BlueLighter; + ScoreCounter.AccentColour = colours.BlueLighter; + + var shd = HealthDisplay as StandardHealthDisplay; + if (shd != null) + { + shd.AccentColour = colours.BlueLighter; + shd.GlowColour = colours.BlueDarker; + } + } + + public override void BindProcessor(ScoreProcessor processor) + { + base.BindProcessor(processor); + + var shd = HealthDisplay as StandardHealthDisplay; + if (shd != null) + processor.NewJudgement += shd.Flash; + } + } +} diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index fd40141fcb..a9cb93876c 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens public override bool Push(Screen screen) { - // When trying to push a non-loaded GameMode, load it asynchronously and re-invoke Push + // When trying to push a non-loaded screen, load it asynchronously and re-invoke Push // once it's done. if (screen.LoadState == LoadState.NotLoaded) { @@ -36,7 +36,7 @@ namespace osu.Game.Screens return true; } - // Make sure the in-progress loading is complete before pushing the GameMode. + // Make sure the in-progress loading is complete before pushing the screen. while (screen.LoadState < LoadState.Loaded) Thread.Sleep(1); diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index ac926cba0c..92032e5120 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -5,8 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; +using osu.Framework.Configuration; using osu.Framework.Screens; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using OpenTK.Graphics; @@ -56,25 +58,33 @@ namespace osu.Game.Screens.Menu }; } + private Bindable menuVoice; + private Bindable menuMusic; + [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, OsuConfigManager config) { - welcome = audio.Sample.Get(@"welcome"); - seeya = audio.Sample.Get(@"seeya"); + menuVoice = config.GetBindable(OsuConfig.MenuVoice); + menuMusic = config.GetBindable(OsuConfig.MenuMusic); bgm = audio.Track.Get(@"circles"); bgm.Looping = true; + + welcome = audio.Sample.Get(@"welcome"); + seeya = audio.Sample.Get(@"seeya"); } protected override void OnEntering(Screen last) { base.OnEntering(last); - welcome.Play(); + if (menuVoice) + welcome.Play(); Scheduler.AddDelayed(delegate { - bgm.Start(); + if (menuMusic) + bgm.Start(); LoadComponentAsync(mainMenu = new MainMenu()); @@ -109,15 +119,17 @@ namespace osu.Game.Screens.Menu if (!(last is MainMenu)) Content.FadeIn(300); + double fadeOutTime = 2000; //we also handle the exit transition. - seeya.Play(); + if (menuVoice) + seeya.Play(); + else + fadeOutTime = 500; - const double fade_out_time = 2000; - - Scheduler.AddDelayed(Exit, fade_out_time); + Scheduler.AddDelayed(Exit, fadeOutTime); //don't want to fade out completely else we will stop running updates and shit will hit the fan. - Game.FadeTo(0.01f, fade_out_time); + Game.FadeTo(0.01f, fadeOutTime); base.OnResuming(last); } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 59528dad91..dc4ec92ee2 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,8 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.MathUtils; using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; @@ -15,6 +21,7 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Tournament; using osu.Framework.Input; using OpenTK.Input; +using System.Threading.Tasks; namespace osu.Game.Screens.Menu { @@ -54,11 +61,27 @@ namespace osu.Game.Screens.Menu }; } + private Bindable menuMusic; + private TrackManager trackManager; + private WorkingBeatmap song; + [BackgroundDependencyLoader] - private void load(OsuGame game) + private void load(OsuGame game, OsuConfigManager config, BeatmapDatabase beatmaps) { + menuMusic = config.GetBindable(OsuConfig.MenuMusic); LoadComponentAsync(background); + if (!menuMusic) + { + trackManager = game.Audio.Track; + int choosableBeatmapsetAmmount = beatmaps.Query().Count(); + if (choosableBeatmapsetAmmount > 0) + { + song = beatmaps.GetWorkingBeatmap(beatmaps.GetWithChildren(RNG.Next(1, choosableBeatmapsetAmmount)).Beatmaps[0]); + Beatmap = song; + } + } + buttons.OnSettings = game.ToggleOptions; preloadSongSelect(); @@ -81,6 +104,17 @@ namespace osu.Game.Screens.Menu { base.OnEntering(last); buttons.FadeInFromZero(500); + if (last is Intro && song != null) + { + Task.Run(() => + { + trackManager.SetExclusive(song.Track); + song.Track.Seek(song.Beatmap.Metadata.PreviewTime); + if (song.Beatmap.Metadata.PreviewTime == -1) + song.Track.Seek(song.Track.Length * 0.4f); + song.Track.Start(); + }); + } } protected override void OnSuspending(Screen next) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 3e832b36fa..0b3ecb4f5a 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; namespace osu.Game.Screens @@ -14,7 +15,7 @@ namespace osu.Game.Screens internal BackgroundScreen Background { get; private set; } /// - /// Override to create a BackgroundMode for the current GameMode. + /// Override to create a BackgroundMode for the current screen. /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. /// protected virtual BackgroundScreen CreateBackground() => null; @@ -25,8 +26,12 @@ namespace osu.Game.Screens internal virtual bool HasLocalCursorDisplayed => false; + internal virtual bool AllowRulesetChange => true; + private readonly Bindable beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); + public WorkingBeatmap Beatmap { get @@ -40,7 +45,7 @@ namespace osu.Game.Screens } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuGameBase game) + private void load(OsuGameBase game, OsuGame osuGame) { if (game != null) { @@ -52,11 +57,23 @@ namespace osu.Game.Screens } beatmap.ValueChanged += OnBeatmapChanged; + + if (osuGame != null) + ruleset.BindTo(osuGame.Ruleset); } + /// + /// The global Beatmap was changed. + /// protected virtual void OnBeatmapChanged(WorkingBeatmap beatmap) { + } + protected override void Update() + { + if (!IsCurrentScreen) return; + + ruleset.Disabled = !AllowRulesetChange; } protected override void OnEntering(Screen last) @@ -99,7 +116,7 @@ namespace osu.Game.Screens if (Background != null && !Background.Equals(nextOsu?.Background)) { if (nextOsu != null) - //We need to use MakeCurrent in case we are jumping up multiple game modes. + //We need to use MakeCurrent in case we are jumping up multiple game screens. nextOsu.Background?.MakeCurrent(); else Background.Exit(); diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 7a32e19338..faff687ddb 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -1,31 +1,19 @@ // 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.Input; using OpenTK.Input; using osu.Game.Graphics; using OpenTK.Graphics; using osu.Framework.Allocation; +using System.Linq; namespace osu.Game.Screens.Play { public class FailOverlay : MenuOverlay { - public override string Header => "failed"; public override string Description => "you're dead, try again?"; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Key == Key.Escape) - { - if (State == Visibility.Hidden) return false; - OnQuit(); - return true; - } - - return base.OnKeyDown(state, args); - } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -33,5 +21,16 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, OnRetry); AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!args.Repeat && args.Key == Key.Escape) + { + Buttons.Children.Last().TriggerClick(); + return true; + } + + return base.OnKeyDown(state, args); + } } } diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs new file mode 100644 index 0000000000..16062bebe5 --- /dev/null +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input; +using OpenTK.Input; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio; +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class HotkeyRetryOverlay : Container + { + public Action Action; + + private SampleChannel retrySample; + private Box overlay; + + private const int activate_delay = 400; + private const int fadeout_delay = 200; + + private bool fired; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + retrySample = audio.Sample.Get(@"Menu/menuback"); + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + + Children = new Drawable[] + { + overlay = new Box + { + Alpha = 0, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + } + }; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) return false; + + if (args.Key == Key.Tilde) + { + overlay.FadeIn(activate_delay, EasingTypes.Out); + return true; + } + + return base.OnKeyDown(state, args); + } + + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + { + if (args.Key == Key.Tilde && !fired) + { + overlay.FadeOut(fadeout_delay, EasingTypes.Out); + return true; + } + + return base.OnKeyUp(state, args); + } + + protected override void Update() + { + base.Update(); + if (!fired && overlay.Alpha == 1) + { + fired = true; + retrySample.Play(); + Action?.Invoke(); + } + } + } +} diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/MenuOverlay.cs index ede49065a7..fa522956f7 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/MenuOverlay.cs @@ -13,10 +13,11 @@ using OpenTK; using OpenTK.Graphics; using osu.Game.Graphics; using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Play { - public abstract class MenuOverlay : OverlayContainer + public abstract class MenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition { private const int transition_duration = 200; private const int button_height = 70; @@ -24,13 +25,15 @@ namespace osu.Game.Screens.Play protected override bool HideOnEscape => false; + protected override bool BlockPassThroughKeyboard => true; + public Action OnRetry; public Action OnQuit; public abstract string Header { get; } public abstract string Description { get; } - private FillFlowContainer buttons; + protected FillFlowContainer Buttons; public int Retries { @@ -80,11 +83,13 @@ namespace osu.Game.Screens.Play // Don't let mouse down events through the overlay or people can click circles while paused. protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + protected override bool OnMouseMove(InputState state) => true; protected void AddButton(string text, Color4 colour, Action action) { - buttons.Add(new PauseButton + Buttons.Add(new PauseButton { Text = text, ButtonColour = colour, @@ -151,7 +156,7 @@ namespace osu.Game.Screens.Play } } }, - buttons = new FillFlowContainer + Buttons = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index f9706d263e..9561979751 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -2,10 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Linq; using osu.Framework.Input; using osu.Game.Graphics; using OpenTK.Input; -using osu.Framework.Graphics.Containers; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -20,10 +20,9 @@ namespace osu.Game.Screens.Play protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (args.Key == Key.Escape) + if (!args.Repeat && args.Key == Key.Escape) { - if (State == Visibility.Hidden) return false; - OnResume(); + Buttons.Children.First().TriggerClick(); return true; } @@ -39,4 +38,3 @@ namespace osu.Game.Screens.Play } } } - \ No newline at end of file diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 36ab1ab946..37b4cf5b45 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -14,14 +14,15 @@ using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Modes; -using osu.Game.Modes.UI; +using osu.Game.Rulesets; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Ranking; using System; using System.Linq; using osu.Framework.Threading; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { @@ -35,7 +36,11 @@ namespace osu.Game.Screens.Play public BeatmapInfo BeatmapInfo; - public bool IsPaused { get; private set; } + public Action RestartRequested; + + public bool IsPaused => !decoupledClock.IsRunning; + + internal override bool AllowRulesetChange => false; public bool HasFailed { get; private set; } @@ -44,47 +49,67 @@ namespace osu.Game.Screens.Play private const double pause_cooldown = 1000; private double lastPauseActionTime; - private bool canPause => Time.Current >= lastPauseActionTime + pause_cooldown; + private bool canPause => ValidForResume && !HasFailed && Time.Current >= lastPauseActionTime + pause_cooldown; - private IAdjustableClock sourceClock; - private IFrameBasedClock interpolatedSourceClock; + private IAdjustableClock adjustableSourceClock; + private FramedOffsetClock offsetClock; + private DecoupleableInterpolatingFramedClock decoupledClock; - private Ruleset ruleset; + private RulesetInfo ruleset; private ScoreProcessor scoreProcessor; protected HitRenderer HitRenderer; - private Bindable dimLevel; + + #region User Settings + + private Bindable dimLevel; + private Bindable mouseWheelDisabled; + private Bindable userAudioOffset; + + #endregion + private SkipButton skipButton; + private Container hitRendererContainer; + private HudOverlay hudOverlay; private PauseOverlay pauseOverlay; private FailOverlay failOverlay; - [BackgroundDependencyLoader] - private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuConfigManager config) + [BackgroundDependencyLoader(permitNulls: true)] + private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuConfigManager config, OsuGame osu) { - var beatmap = Beatmap.Beatmap; - - if (beatmap.BeatmapInfo?.Mode > PlayMode.Taiko) - { - //we only support osu! mode for now because the hitobject parsing is crappy and needs a refactor. - Exit(); - return; - } - - dimLevel = config.GetBindable(OsuConfig.DimLevel); + dimLevel = config.GetBindable(OsuConfig.DimLevel); mouseWheelDisabled = config.GetBindable(OsuConfig.MouseDisableWheel); + Ruleset rulesetInstance; + try { if (Beatmap == null) Beatmap = beatmaps.GetWorkingBeatmap(BeatmapInfo, withStoryboard: true); - if ((Beatmap?.Beatmap?.HitObjects.Count ?? 0) == 0) - throw new Exception("No valid objects were found!"); - - if (Beatmap == null) + if (Beatmap?.Beatmap == null) throw new Exception("Beatmap was not loaded"); + + ruleset = osu?.Ruleset.Value ?? Beatmap.BeatmapInfo.Ruleset; + rulesetInstance = ruleset.CreateInstance(); + + try + { + HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap); + } + catch (BeatmapInvalidForRulesetException) + { + // we may fail to create a HitRenderer if the beatmap cannot be loaded with the user's preferred ruleset + // let's try again forcing the beatmap's ruleset. + ruleset = Beatmap.BeatmapInfo.Ruleset; + rulesetInstance = ruleset.CreateInstance(); + HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap); + } + + if (!HitRenderer.Objects.Any()) + throw new Exception("Beatmap contains no hit objects!"); } catch (Exception e) { @@ -100,27 +125,50 @@ namespace osu.Game.Screens.Play if (track != null) { audio.Track.SetExclusive(track); - sourceClock = track; + adjustableSourceClock = track; } - sourceClock = (IAdjustableClock)track ?? new StopwatchClock(); - interpolatedSourceClock = new InterpolatingFramedClock(sourceClock); + adjustableSourceClock = (IAdjustableClock)track ?? new StopwatchClock(); + + decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + + var firstObjectTime = HitRenderer.Objects.First().StartTime; + decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.TimingInfo.BeatLengthAt(firstObjectTime) * 4, Beatmap.BeatmapInfo.AudioLeadIn))); + decoupledClock.ProcessFrame(); + + offsetClock = new FramedOffsetClock(decoupledClock); + + userAudioOffset = config.GetBindable(OsuConfig.AudioOffset); + userAudioOffset.ValueChanged += v => offsetClock.Offset = v; + userAudioOffset.TriggerChange(); Schedule(() => { - sourceClock.Reset(); - }); + adjustableSourceClock.Reset(); - ruleset = Ruleset.GetRuleset(Beatmap.PlayMode); - HitRenderer = ruleset.CreateHitRendererWith(Beatmap); + foreach (var mod in Beatmap.Mods.Value.OfType()) + mod.ApplyToClock(adjustableSourceClock); + + decoupledClock.ChangeSource(adjustableSourceClock); + }); scoreProcessor = HitRenderer.CreateScoreProcessor(); - hudOverlay = new StandardHudOverlay(); - hudOverlay.KeyCounter.Add(ruleset.CreateGameplayKeys()); + hudOverlay = new StandardHudOverlay() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + + hudOverlay.KeyCounter.Add(rulesetInstance.CreateGameplayKeys()); hudOverlay.BindProcessor(scoreProcessor); hudOverlay.BindHitRenderer(HitRenderer); + hudOverlay.Progress.Objects = HitRenderer.Objects; + hudOverlay.Progress.AudioClock = decoupledClock; + hudOverlay.Progress.AllowSeeking = HitRenderer.HasReplayLoaded; + hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos); + //bind HitRenderer to ScoreProcessor and ourselves (for a pass situation) HitRenderer.OnAllJudged += onCompletion; @@ -129,16 +177,20 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - new Container + hitRendererContainer = new Container { RelativeSizeAxes = Axes.Both, - Clock = interpolatedSourceClock, Children = new Drawable[] { - HitRenderer, - skipButton = new SkipButton + new Container { - Alpha = 0 + RelativeSizeAxes = Axes.Both, + Clock = offsetClock, + Children = new Drawable[] + { + HitRenderer, + skipButton = new SkipButton { Alpha = 0 }, + } }, } }, @@ -157,6 +209,15 @@ namespace osu.Game.Screens.Play { OnRetry = Restart, OnQuit = Exit, + }, + new HotkeyRetryOverlay + { + Action = () => { + //we want to hide the hitrenderer immediately (looks better). + //we may be able to remove this once the mouse cursor trail is improved. + HitRenderer?.Hide(); + Restart(); + }, } }; } @@ -179,7 +240,7 @@ namespace osu.Game.Screens.Play skipButton.Action = () => { - sourceClock.Seek(firstHitObject - skip_required_cutoff - fade_time); + decoupledClock.Seek(firstHitObject - skip_required_cutoff - fade_time); skipButton.Action = null; }; @@ -190,52 +251,47 @@ namespace osu.Game.Screens.Play public void Pause(bool force = false) { - if (canPause || force) + if (!canPause && !force) return; + + // the actual pausing is potentially happening on a different thread. + // we want to wait for the source clock to stop so we can be sure all components are in a stable state. + if (!IsPaused) { + decoupledClock.Stop(); + + Schedule(() => Pause(force)); + return; + } + + // we need to do a final check after all of our children have processed up to the paused clock time. + // this is to cover cases where, for instance, the player fails in the last processed frame (which would change canPause). + // as the scheduler runs before children updates, let's schedule for the next frame. + Schedule(() => + { + if (!canPause) return; + lastPauseActionTime = Time.Current; hudOverlay.KeyCounter.IsCounting = false; + hudOverlay.Progress.Show(); pauseOverlay.Retries = RestartCount; pauseOverlay.Show(); - sourceClock.Stop(); - IsPaused = true; - } - else - { - IsPaused = false; - } + }); } public void Resume() { lastPauseActionTime = Time.Current; hudOverlay.KeyCounter.IsCounting = true; + hudOverlay.Progress.Hide(); pauseOverlay.Hide(); - sourceClock.Start(); - IsPaused = false; - } - - public void TogglePaused() - { - IsPaused = !IsPaused; - if (IsPaused) Pause(); else Resume(); + decoupledClock.Start(); } public void Restart() { - sourceClock.Stop(); // If the clock is running and Restart is called the game will lag until relaunch - - var newPlayer = new Player(); - - LoadComponentAsync(newPlayer, delegate - { - newPlayer.RestartCount = RestartCount + 1; - ValidForResume = false; - - if (!Push(newPlayer)) - { - // Error(?) - } - }); + ValidForResume = false; + RestartRequested?.Invoke(); + Exit(); } private ScheduledDelegate onCompletionEvent; @@ -246,22 +302,25 @@ namespace osu.Game.Screens.Play if (scoreProcessor.HasFailed || onCompletionEvent != null) return; + ValidForResume = false; + Delay(1000); onCompletionEvent = Schedule(delegate { - ValidForResume = false; - Push(new Results + var score = new Score { - Score = scoreProcessor.CreateScore() - }); + Beatmap = Beatmap.BeatmapInfo, + Ruleset = ruleset + }; + scoreProcessor.PopulateScore(score); + score.User = HitRenderer.Replay?.User ?? (Game as OsuGame)?.API?.LocalUser?.Value; + Push(new Results(score)); }); } private void onFail() { - sourceClock.Stop(); - - Delay(500); + decoupledClock.Stop(); HasFailed = true; failOverlay.Retries = RestartCount; @@ -273,11 +332,11 @@ namespace osu.Game.Screens.Play base.OnEntering(last); (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1500, EasingTypes.OutQuint); - Background?.FadeTo((100f - dimLevel) / 100, 1500, EasingTypes.OutQuint); + Background?.FadeTo(1 - (float)dimLevel, 1500, EasingTypes.OutQuint); Content.Alpha = 0; - dimLevel.ValueChanged += newDim => Background?.FadeTo((100f - newDim) / 100, 800); + dimLevel.ValueChanged += newDim => Background?.FadeTo(1 - (float)newDim, 800); Content.ScaleTo(0.7f); @@ -289,47 +348,53 @@ namespace osu.Game.Screens.Play Delay(750); Schedule(() => { - sourceClock.Start(); + decoupledClock.Start(); initializeSkipButton(); }); - //keep in mind this is using the interpolatedSourceClock so won't be run as early as we may expect. - HitRenderer.Alpha = 0; - HitRenderer.FadeIn(750, EasingTypes.OutQuint); + hitRendererContainer.Alpha = 0; + hitRendererContainer.FadeIn(750, EasingTypes.OutQuint); } protected override void OnSuspending(Screen next) { - Content.FadeOut(350); - Content.ScaleTo(0.7f, 750, EasingTypes.InQuint); - + fadeOut(); base.OnSuspending(next); } protected override bool OnExiting(Screen next) { - if (pauseOverlay != null && !HitRenderer.HasReplayLoaded) + if (!HasFailed && ValidForResume) { - //pause screen override logic. - if (pauseOverlay?.State == Visibility.Hidden && !canPause) return true; - - if (!IsPaused && sourceClock.IsRunning) // For if the user presses escape quickly when entering the map + if (pauseOverlay != null && !HitRenderer.HasReplayLoaded) { - Pause(); - return true; + //pause screen override logic. + if (pauseOverlay?.State == Visibility.Hidden && !canPause) return true; + + if (!IsPaused) // For if the user presses escape quickly when entering the map + { + Pause(); + return true; + } } } - HitRenderer?.FadeOut(60); - - FadeOut(250); - Content.ScaleTo(0.7f, 750, EasingTypes.InQuint); - Background?.FadeTo(1f, 200); + fadeOut(); return base.OnExiting(next); } - private Bindable mouseWheelDisabled; + private void fadeOut() + { + const float fade_out_duration = 250; + + HitRenderer?.FadeOut(fade_out_duration); + Content.FadeOut(fade_out_duration); + + hudOverlay.ScaleTo(0.7f, fade_out_duration * 3, EasingTypes.In); + + Background?.FadeTo(1f, fade_out_duration); + } protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value && !IsPaused; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/PlayerInputManager.cs b/osu.Game/Screens/Play/PlayerInputManager.cs index 6604dbfc7e..9707ccbc35 100644 --- a/osu.Game/Screens/Play/PlayerInputManager.cs +++ b/osu.Game/Screens/Play/PlayerInputManager.cs @@ -1,21 +1,28 @@ // 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.Allocation; +using osu.Framework.Configuration; using osu.Framework.Input; using osu.Framework.Timing; +using osu.Game.Configuration; using osu.Game.Input.Handlers; namespace osu.Game.Screens.Play { public class PlayerInputManager : PassThroughInputManager { - private readonly ManualClock clock = new ManualClock(); + private ManualClock clock; private IFrameBasedClock parentClock; private ReplayInputHandler replayInputHandler; public ReplayInputHandler ReplayInputHandler { - get { return replayInputHandler; } + get + { + return replayInputHandler; + } set { if (replayInputHandler != null) RemoveHandler(replayInputHandler); @@ -28,44 +35,105 @@ namespace osu.Game.Screens.Play } } + private Bindable mouseDisabled; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + mouseDisabled = config.GetBindable(OsuConfig.MouseDisableButtons); + } + protected override void LoadComplete() { base.LoadComplete(); + //our clock will now be our parent's clock, but we want to replace this to allow manual control. parentClock = Clock; - Clock = new FramedClock(clock); + + Clock = new FramedClock(clock = new ManualClock + { + CurrentTime = parentClock.CurrentTime, + Rate = parentClock.Rate, + }); + } + + /// + /// Whether we running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + private bool requireMoreUpdateLoops; + + /// + /// Whether we in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + private bool validState; + + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + + private bool isAttached => replayInputHandler != null && !UseParentState; + + private const int max_catch_up_updates_per_frame = 50; + + public override bool UpdateSubTree() + { + requireMoreUpdateLoops = true; + validState = true; + + int loops = 0; + + while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + if (!base.UpdateSubTree()) + return false; + + return true; } protected override void Update() { - base.Update(); - if (parentClock == null) return; clock.Rate = parentClock.Rate; clock.IsRunning = parentClock.IsRunning; - //if a replayHandler is not attached, we should just pass-through. - if (UseParentState || replayInputHandler == null) + if (!isAttached) { clock.CurrentTime = parentClock.CurrentTime; - base.Update(); - return; } - - while (true) + else { double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime); if (newTime == null) - //we shouldn't execute for this time value - break; - - if (clock.CurrentTime == parentClock.CurrentTime) - break; + { + // we shouldn't execute for this time value. probably waiting on more replay data. + validState = false; + return; + } clock.CurrentTime = newTime.Value; - base.Update(); + } + + requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; + base.Update(); + } + + protected override void TransformState(InputState state) + { + base.TransformState(state); + + // we don't want to transform the state if a replay is present (for now, at least). + if (replayInputHandler != null) return; + + var mouse = state.Mouse as Framework.Input.MouseState; + + if (mouse != null) + { + if (mouseDisabled.Value) + { + mouse.SetPressed(MouseButton.Left, false); + mouse.SetPressed(MouseButton.Right, false); + } } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 64d17fd5bb..765abd9873 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -19,17 +19,27 @@ namespace osu.Game.Screens.Play { public class PlayerLoader : OsuScreen { - private readonly Player player; + private Player player; + private readonly OsuLogo logo; private BeatmapMetadataDisplay info; + private bool showOverlays = true; + internal override bool ShowOverlays => showOverlays; + + internal override bool AllowRulesetChange => false; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); public PlayerLoader(Player player) { - ValidForResume = false; this.player = player; + player.RestartRequested = () => { + showOverlays = false; + ValidForResume = true; + }; + Children = new Drawable[] { logo = new OsuLogo @@ -53,6 +63,37 @@ namespace osu.Game.Screens.Play LoadComponentAsync(player); } + protected override void OnResuming(Screen last) + { + base.OnResuming(last); + + contentIn(); + + //we will only be resumed if the player has requested a re-run (see ValidForResume setting above) + LoadComponentAsync(player = new Player + { + RestartCount = player.RestartCount + 1, + RestartRequested = player.RestartRequested, + Beatmap = player.Beatmap, + }); + + Delay(400); + + Schedule(pushWhenLoaded); + } + + private void contentIn() + { + Content.ScaleTo(1, 650, EasingTypes.OutQuint); + Content.FadeInFromZero(400); + } + + private void contentOut() + { + Content.ScaleTo(0.7f, 300, EasingTypes.InQuint); + Content.FadeOut(250); + } + protected override void OnEntering(Screen last) { base.OnEntering(last); @@ -60,20 +101,27 @@ namespace osu.Game.Screens.Play Background.FadeTo(0.4f, 250); Content.ScaleTo(0.7f); - Content.ScaleTo(1, 750, EasingTypes.OutQuint); - Content.FadeInFromZero(500); - Delay(1000, true); + contentIn(); + + Delay(500, true); logo.MoveToOffset(new Vector2(0, -180), 500, EasingTypes.InOutExpo); Delay(250, true); info.FadeIn(500); - Delay(2000, true); + Delay(1400, true); - Content.ScaleTo(0.7f, 300, EasingTypes.InQuint); - Content.FadeOut(250); + Schedule(pushWhenLoaded); + } + + private void pushWhenLoaded() + { + if (!player.IsLoaded) + Schedule(pushWhenLoaded); + + contentOut(); Delay(250); @@ -83,6 +131,12 @@ namespace osu.Game.Screens.Play if (!Push(player)) Exit(); + else + { + //By default, we want to load the player and never be returned to. + //Note that this may change if the player we load requested a re-run. + ValidForResume = false; + } }); } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 4593656a2e..860675b62a 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Modes.Replays; +using osu.Game.Rulesets.Replays; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs new file mode 100644 index 0000000000..ed57dad644 --- /dev/null +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -0,0 +1,139 @@ +// 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; +using System.Collections.Generic; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using System.Linq; +using osu.Framework.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Framework.Graphics.Primitives; + +namespace osu.Game.Screens.Play +{ + public class SongProgress : OverlayContainer + { + private const int bottom_bar_height = 5; + + protected override bool HideOnEscape => false; + + private static readonly Vector2 handle_size = new Vector2(14, 25); + + private const float transition_duration = 200; + + private readonly SongProgressBar bar; + private readonly SongProgressGraph graph; + + public Action OnSeek; + + public IClock AudioClock; + + private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; + + private double firstHitTime => objects.First().StartTime; + + private IEnumerable objects; + + public IEnumerable Objects + { + set + { + graph.Objects = objects = value; + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + graph.FillColour = bar.FillColour = colours.BlueLighter; + } + + public SongProgress() + { + const float graph_height = SquareGraph.Column.WIDTH * 6; + + Height = bottom_bar_height + graph_height + handle_size.Y; + Y = bottom_bar_height; + + Children = new Drawable[] + { + graph = new SongProgressGraph + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Height = graph_height, + Margin = new MarginPadding { Bottom = bottom_bar_height }, + }, + bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) + { + Alpha = 0, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + SeekRequested = delegate (float position) + { + OnSeek?.Invoke(firstHitTime + position * (lastHitTime - firstHitTime)); + }, + }, + }; + } + + protected override void LoadComplete() + { + State = Visibility.Visible; + } + + private bool allowSeeking; + + public bool AllowSeeking + { + get + { + return allowSeeking; + } + + set + { + if (allowSeeking == value) return; + + allowSeeking = value; + updateBarVisibility(); + } + } + + private void updateBarVisibility() + { + bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, EasingTypes.In); + MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, EasingTypes.In); + } + + protected override void PopIn() + { + updateBarVisibility(); + FadeIn(500, EasingTypes.OutQuint); + } + + protected override void PopOut() + { + FadeOut(100); + } + + protected override void Update() + { + base.Update(); + + if (objects == null) + return; + + double progress = ((AudioClock?.CurrentTime ?? Time.Current) - firstHitTime) / lastHitTime; + + bar.UpdatePosition((float)progress); + graph.Progress = (int)(graph.ColumnCount * progress); + } + } +} diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs new file mode 100644 index 0000000000..d0fb3c8a3d --- /dev/null +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -0,0 +1,74 @@ +// 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.Game.Overlays; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Screens.Play +{ + public class SongProgressBar : DragBar + { + public Color4 FillColour + { + get { return Fill.Colour; } + set { Fill.Colour = value; } + } + + public SongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) + { + Height = barHeight + handleBarHeight + handleSize.Y; + + Fill.RelativeSizeAxes = Axes.X; + Fill.Height = barHeight; + + Add(new Box + { + Name = "Background", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = barHeight, + Colour = Color4.Black, + Alpha = 0.5f, + Depth = 1 + }); + + Fill.Add(new Container + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomRight, + Width = 2, + Height = barHeight + handleBarHeight, + Colour = Color4.White, + Position = new Vector2(2, 0), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.TopCentre, + Size = handleSize, + CornerRadius = 5, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White + } + } + } + } + }); + } + } +} diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs new file mode 100644 index 0000000000..20548970e5 --- /dev/null +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Play +{ + public class SongProgressGraph : SquareGraph + { + private IEnumerable objects; + + public IEnumerable Objects + { + set + { + objects = value; + + const int granularity = 200; + + var lastHit = (objects.Last() as IHasEndTime)?.EndTime ?? 0; + + if (lastHit == 0) + lastHit = objects.Last().StartTime; + + var interval = (lastHit + 1) / granularity; + + var values = new int[granularity]; + + foreach (var h in objects) + { + IHasEndTime end = h as IHasEndTime; + + int startRange = (int)(h.StartTime / interval); + int endRange = (int)((end?.EndTime > 0 ? end.EndTime : h.StartTime) / interval); + for (int i = startRange; i <= endRange; i++) + values[i]++; + } + + Values = values; + } + } + } +} diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs new file mode 100644 index 0000000000..43a8253b53 --- /dev/null +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -0,0 +1,238 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework; +using osu.Framework.Caching; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class SquareGraph : BufferedContainer + { + private Column[] columns = { }; + + public int ColumnCount => columns.Length; + + public override bool HandleInput => false; + + private int progress; + public int Progress + { + get { return progress; } + set + { + if (value == progress) return; + progress = value; + redrawProgress(); + } + } + + private float[] calculatedValues = { }; // values but adjusted to fit the amount of columns + + private int[] values; + public int[] Values + { + get { return values; } + set + { + if (value == values) return; + values = value; + layout.Invalidate(); + } + } + + private Color4 fillColour; + public Color4 FillColour + { + get { return fillColour; } + set + { + if (value == fillColour) return; + fillColour = value; + redrawFilled(); + } + } + + public SquareGraph() + { + CacheDrawnFrameBuffer = true; + } + + private Cached layout = new Cached(); + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.SizeInParentSpace) > 0) + layout.Invalidate(); + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + layout.Refresh(recreateGraph); + } + + /// + /// Redraws all the columns to match their lit/dimmed state. + /// + private void redrawProgress() + { + for (int i = 0; i < columns.Length; i++) + columns[i].State = i <= progress ? ColumnState.Lit : ColumnState.Dimmed; + ForceRedraw(); + } + + /// + /// Redraws the filled amount of all the columns. + /// + private void redrawFilled() + { + for (int i = 0; i < ColumnCount; i++) + columns[i].Filled = calculatedValues.ElementAtOrDefault(i); + ForceRedraw(); + } + + /// + /// Takes and adjusts it to fit the amount of columns. + /// + private void recalculateValues() + { + var newValues = new List(); + + if (values == null) + { + for (float i = 0; i < ColumnCount; i++) + newValues.Add(0); + + return; + } + + var max = values.Max(); + + float step = values.Length / (float)ColumnCount; + for (float i = 0; i < values.Length; i += step) + { + newValues.Add((float)values[(int)i] / max); + } + + calculatedValues = newValues.ToArray(); + } + + /// + /// Recreates the entire graph. + /// + private void recreateGraph() + { + var newColumns = new List(); + + for (float x = 0; x < DrawWidth; x += Column.WIDTH) + { + newColumns.Add(new Column + { + LitColour = fillColour, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = DrawHeight, + Position = new Vector2(x, 0), + State = ColumnState.Dimmed, + }); + } + + columns = newColumns.ToArray(); + Children = columns; + + recalculateValues(); + redrawFilled(); + redrawProgress(); + } + + public class Column : Container, IStateful + { + protected readonly Color4 EmptyColour = Color4.White.Opacity(20); + public Color4 LitColour = Color4.LightBlue; + protected readonly Color4 DimmedColour = Color4.White.Opacity(140); + + private float cubeCount => DrawHeight / WIDTH; + private const float cube_size = 4; + private const float padding = 2; + public const float WIDTH = cube_size + padding; + + private readonly List drawableRows = new List(); + + private float filled; + public float Filled + { + get { return filled; } + set + { + if (value == filled) return; + filled = value; + + fillActive(); + } + } + + private ColumnState state; + public ColumnState State + { + get { return state; } + set + { + if (value == state) return; + state = value; + + if (IsLoaded) + fillActive(); + } + } + + public Column() + { + Width = WIDTH; + } + + protected override void LoadComplete() + { + for (int r = 0; r < cubeCount; r++) + { + drawableRows.Add(new Box + { + Size = new Vector2(cube_size), + Position = new Vector2(0, r * WIDTH + padding), + }); + } + + Children = drawableRows; + + // Reverse drawableRows so when iterating through them they start at the bottom + drawableRows.Reverse(); + + fillActive(); + } + + private void fillActive() + { + Color4 colour = State == ColumnState.Lit ? LitColour : DimmedColour; + + int countFilled = (int)MathHelper.Clamp(filled * drawableRows.Count, 0, drawableRows.Count); + + for (int i = 0; i < drawableRows.Count; i++) + drawableRows[i].Colour = i < countFilled ? colour : EmptyColour; + } + } + + public enum ColumnState + { + Lit, + Dimmed + } + } +} diff --git a/osu.Game/Screens/Ranking/AspectContainer.cs b/osu.Game/Screens/Ranking/AspectContainer.cs new file mode 100644 index 0000000000..4699b4ab92 --- /dev/null +++ b/osu.Game/Screens/Ranking/AspectContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Ranking +{ + public class AspectContainer : Container + { + protected override void Update() + { + base.Update(); + if (RelativeSizeAxes == Axes.X) + Height = DrawWidth; + else + Width = DrawHeight; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Ranking/ResultMode.cs b/osu.Game/Screens/Ranking/ResultMode.cs new file mode 100644 index 0000000000..eeb04033ea --- /dev/null +++ b/osu.Game/Screens/Ranking/ResultMode.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Ranking +{ + public enum ResultMode + { + Summary, + Ranking, + Share + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs new file mode 100644 index 0000000000..fc62342860 --- /dev/null +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -0,0 +1,108 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Ranking +{ + public class ResultModeButton : TabItem + { + private readonly FontAwesome icon; + private Color4 activeColour; + private Color4 inactiveColour; + private CircularContainer colouredPart; + + public ResultModeButton(ResultMode mode) : base(mode) + { + switch (mode) + { + case ResultMode.Summary: + icon = FontAwesome.fa_asterisk; + break; + case ResultMode.Ranking: + icon = FontAwesome.fa_list; + break; + case ResultMode.Share: + icon = FontAwesome.fa_camera; + break; + } + } + + public override bool Active + { + get + { + return base.Active; + } + set + { + base.Active = value; + colouredPart.FadeColour(Active ? activeColour : inactiveColour, 200, EasingTypes.OutQuint); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Size = new Vector2(50); + + Masking = true; + CornerRadius = 25; + + activeColour = colours.PinkDarker; + inactiveColour = OsuColour.Gray(0.8f); + + EdgeEffect = new EdgeEffect + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 5, + }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + colouredPart = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + BorderThickness = 4, + BorderColour = Color4.White, + Colour = inactiveColour, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, //for border rendering + RelativeSizeAxes = Axes.Both, + Colour = Color4.Transparent, + }, + new TextAwesome + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + Colour = OsuColour.Gray(0.95f), + Icon = icon, + TextSize = 20, + } + } + } + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/ResultModeTabControl.cs b/osu.Game/Screens/Ranking/ResultModeTabControl.cs new file mode 100644 index 0000000000..346bff5720 --- /dev/null +++ b/osu.Game/Screens/Ranking/ResultModeTabControl.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Screens.Ranking +{ + public class ResultModeTabControl : TabControl + { + public ResultModeTabControl() + { + TabContainer.Anchor = Anchor.BottomCentre; + TabContainer.Origin = Anchor.BottomCentre; + TabContainer.Spacing = new Vector2(15); + + TabContainer.Masking = false; + TabContainer.Padding = new MarginPadding(5); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(ResultMode value) => new ResultModeButton(value) + { + Anchor = TabContainer.Anchor, + Origin = TabContainer.Origin + }; + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 37770bea3e..5bcaba7813 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -1,93 +1,285 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Screens; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Modes.Scoring; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using OpenTK; using OpenTK.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Ranking { - internal class Results : OsuScreen + public class Results : OsuScreen { - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); + private readonly Score score; + private Container circleOuterBackground; + private Container circleOuter; + private Container circleInner; + + private ParallaxContainer backgroundParallax; + + private ResultModeTabControl modeChangeButtons; + + internal override bool AllowRulesetChange => false; + + private Container currentPage; private static readonly Vector2 background_blur = new Vector2(20); - private ScoreDisplay scoreDisplay; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); + + private const float overscan = 1.3f; + + private const float circle_outer_scale = 0.96f; + + public Results(Score score) + { + this.score = score; + } + + private const float transition_time = 800; + + private IEnumerable allCircles => new Drawable[] { circleOuterBackground, circleInner, circleOuter }; protected override void OnEntering(Screen last) { base.OnEntering(last); - Background.Schedule(() => (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 1000)); + (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, EasingTypes.OutQuint); + + allCircles.ForEach(c => + { + c.FadeOut(); + c.ScaleTo(0); + }); + + backgroundParallax.FadeOut(); + modeChangeButtons.FadeOut(); + currentPage.FadeOut(); + + circleOuterBackground.ScaleTo(1, transition_time, EasingTypes.OutQuint); + circleOuterBackground.FadeTo(1, transition_time, EasingTypes.OutQuint); + + using (BeginDelayedSequence(transition_time * 0.25f, true)) + { + + circleOuter.ScaleTo(1, transition_time, EasingTypes.OutQuint); + circleOuter.FadeTo(1, transition_time, EasingTypes.OutQuint); + + using (BeginDelayedSequence(transition_time * 0.3f, true)) + { + backgroundParallax.FadeIn(transition_time, EasingTypes.OutQuint); + + circleInner.ScaleTo(1, transition_time, EasingTypes.OutQuint); + circleInner.FadeTo(1, transition_time, EasingTypes.OutQuint); + + using (BeginDelayedSequence(transition_time * 0.4f, true)) + { + modeChangeButtons.FadeIn(transition_time, EasingTypes.OutQuint); + currentPage.FadeIn(transition_time, EasingTypes.OutQuint); + } + } + } } protected override bool OnExiting(Screen next) { - Background.Schedule(() => Background.FadeColour(Color4.White, 500)); + allCircles.ForEach(c => + { + c.ScaleTo(0, transition_time, EasingTypes.OutSine); + }); + + Content.FadeOut(transition_time / 4); + return base.OnExiting(next); } - public Score Score + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - set - { - scoreDisplay?.FadeOut(500); - scoreDisplay?.Expire(); - - scoreDisplay = new ScoreDisplay(value) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - - Add(scoreDisplay); - - scoreDisplay.FadeIn(500); - scoreDisplay.ScaleTo(0.1f); - scoreDisplay.ScaleTo(1, 1000, EasingTypes.OutElastic); - scoreDisplay.RotateTo(360 * 5, 1000, EasingTypes.OutElastic); - - } - } - } - - internal class ScoreDisplay : Container - { - public ScoreDisplay(Score s) - { - AutoSizeAxes = Axes.Both; - Children = new Drawable[] { - new FillFlowContainer + new AspectContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = overscan, Children = new Drawable[] { - new OsuSpriteText + circleOuterBackground = new CircularContainer { - TextSize = 40, - Text = $@"Accuracy: {s.Accuracy:#0.00%}", + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + Alpha = 0.2f, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + } + } }, - new OsuSpriteText + circleOuter = new CircularContainer { - TextSize = 40, - Text = $@"Score: {s.TotalScore}", + Size = new Vector2(circle_outer_scale), + EdgeEffect = new EdgeEffect + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + backgroundParallax = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + ParallaxAmount = 0.01f, + Scale = new Vector2(1 / circle_outer_scale / overscan), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Sprite + { + Alpha = 0.2f, + Texture = Beatmap?.Background, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + } + } + }, + modeChangeButtons = new ResultModeTabControl + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 50, + Margin = new MarginPadding { Bottom = 110 }, + }, + new SpriteText + { + Text = $"{score.MaxCombo}x", + TextSize = 40, + RelativePositionAxes = Axes.X, + Font = @"Exo2.0-Bold", + X = 0.1f, + Colour = colours.BlueDarker, + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + }, + new SpriteText + { + Text = "max combo", + TextSize = 20, + RelativePositionAxes = Axes.X, + X = 0.1f, + Colour = colours.Gray6, + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, + }, + new SpriteText + { + Text = $"{score.Accuracy:P2}", + TextSize = 40, + RelativePositionAxes = Axes.X, + Font = @"Exo2.0-Bold", + X = 0.9f, + Colour = colours.BlueDarker, + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + }, + new SpriteText + { + Text = "accuracy", + TextSize = 20, + RelativePositionAxes = Axes.X, + X = 0.9f, + Colour = colours.Gray6, + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, + }, + } }, - new OsuSpriteText + circleInner = new CircularContainer { - TextSize = 40, - Text = $@"MaxCombo: {s.MaxCombo}", + Size = new Vector2(0.6f), + EdgeEffect = new EdgeEffect + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + } } } - } + }, + new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = Exit + }, }; + + modeChangeButtons.AddItem(ResultMode.Summary); + modeChangeButtons.AddItem(ResultMode.Ranking); + //modeChangeButtons.AddItem(ResultMode.Share); + + modeChangeButtons.Current.ValueChanged += mode => + { + currentPage?.FadeOut(); + currentPage?.Expire(); + + switch (mode) + { + case ResultMode.Summary: + currentPage = new ResultsPageScore(score, Beatmap); + break; + case ResultMode.Ranking: + currentPage = new ResultsPageRanking(score, Beatmap); + break; + } + + if (currentPage != null) + circleInner.Add(currentPage); + }; + + modeChangeButtons.Current.TriggerChange(); } } } diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs new file mode 100644 index 0000000000..02eae3643b --- /dev/null +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Ranking +{ + internal class ResultsPage : Container + { + protected readonly Score Score; + protected readonly WorkingBeatmap Beatmap; + private CircularContainer content; + private Box fill; + + protected override Container Content => content; + + public ResultsPage(Score score, WorkingBeatmap beatmap) + { + Score = score; + Beatmap = beatmap; + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + fill.Delay(400); + fill.FadeInFromZero(600); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddInternal(new Drawable[] + { + fill = new Box + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray6 + }, + new CircularContainer + { + EdgeEffect = new EdgeEffect + { + Colour = colours.GrayF.Opacity(0.8f), + Type = EdgeEffectType.Shadow, + Radius = 1, + }, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 20, + BorderColour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box{ + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + } + }, + content = new CircularContainer + { + EdgeEffect = new EdgeEffect + { + Colour = Color4.Black.Opacity(0.2f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Masking = true, + Size = new Vector2(0.88f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + } + + } +} diff --git a/osu.Game/Screens/Ranking/ResultsPageRanking.cs b/osu.Game/Screens/Ranking/ResultsPageRanking.cs new file mode 100644 index 0000000000..827abd556e --- /dev/null +++ b/osu.Game/Screens/Ranking/ResultsPageRanking.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using OpenTK; + +namespace osu.Game.Screens.Ranking +{ + internal class ResultsPageRanking : ResultsPage + { + public ResultsPageRanking(Score score, WorkingBeatmap beatmap = null) : base(score, beatmap) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + Colour = colours.GrayE, + RelativeSizeAxes = Axes.Both, + }, + new Leaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Beatmap = Beatmap.BeatmapInfo ?? Score.Beatmap, + Scale = new Vector2(0.7f) + } + }; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs new file mode 100644 index 0000000000..4bfd998c54 --- /dev/null +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -0,0 +1,384 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; + +namespace osu.Game.Screens.Ranking +{ + internal class ResultsPageScore : ResultsPage + { + private ScoreCounter scoreCounter; + + public ResultsPageScore(Score score, WorkingBeatmap beatmap) : base(score, beatmap) { } + + private FillFlowContainer statisticsContainer; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + const float user_header_height = 120; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = user_header_height }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new UserHeader(Score.User) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = user_header_height, + }, + new DrawableRank(Score.Rank) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(150, 60), + Margin = new MarginPadding(20), + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 60, + Children = new Drawable[] + { + new SongProgressGraph + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + Objects = Beatmap.Beatmap.HitObjects, + }, + scoreCounter = new SlowScoreCounter(6) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = colours.PinkDarker, + Y = 10, + TextSize = 56, + }, + } + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = colours.PinkDarker, + Shadow = false, + Font = @"Exo2.0-Bold", + TextSize = 16, + Text = "total score", + Margin = new MarginPadding { Bottom = 15 }, + }, + new BeatmapDetails(Beatmap.BeatmapInfo) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Bottom = 10 }, + }, + new DateDisplay(Score.Date) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Size = new Vector2(0.75f, 1), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 10, Bottom = 10 }, + Children = new Drawable[] + { + new Box + { + ColourInfo = ColourInfo.GradientHorizontal( + colours.GrayC.Opacity(0), + colours.GrayC.Opacity(0.9f)), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f, 1), + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + ColourInfo = ColourInfo.GradientHorizontal( + colours.GrayC.Opacity(0.9f), + colours.GrayC.Opacity(0)), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f, 1), + }, + } + }, + statisticsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Horizontal, + LayoutDuration = 200, + LayoutEasing = EasingTypes.OutQuint + } + } + } + }; + + statisticsContainer.Children = Score.Statistics.Select(s => new DrawableScoreStatistic(s)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Schedule(() => + { + scoreCounter.Increment(Score.TotalScore); + + int delay = 0; + foreach (var s in statisticsContainer.Children) + { + s.FadeOut(); + s.Delay(delay += 200); + s.FadeIn(300 + delay, EasingTypes.Out); + } + }); + } + + private class DrawableScoreStatistic : Container + { + private readonly KeyValuePair statistic; + + public DrawableScoreStatistic(KeyValuePair statistic) + { + this.statistic = statistic; + + AutoSizeAxes = Axes.Both; + Margin = new MarginPadding { Left = 5, Right = 5 }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new SpriteText { + Text = statistic.Value.ToString().PadLeft(4, '0'), + Colour = colours.Gray7, + TextSize = 30, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new SpriteText { + Text = statistic.Key, + Colour = colours.Gray7, + Font = @"Exo2.0-Bold", + Y = 26, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + }; + } + } + + private class DateDisplay : Container + { + private DateTime date; + + public DateDisplay(DateTime date) + { + this.date = date; + + AutoSizeAxes = Axes.Y; + + Width = 140; + + Masking = true; + CornerRadius = 5; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray6, + }, + new OsuSpriteText + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = date.ToString("HH:mm"), + Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Colour = Color4.White, + }, + new OsuSpriteText + { + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Text = date.ToString("yyyy/MM/dd"), + Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Colour = Color4.White, + } + }; + } + } + + private class BeatmapDetails : Container + { + private readonly BeatmapInfo beatmap; + + private readonly OsuSpriteText title; + private readonly OsuSpriteText artist; + private readonly OsuSpriteText versionMapper; + + public BeatmapDetails(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + title = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Shadow = false, + TextSize = 24, + Font = @"Exo2.0-BoldItalic", + }, + artist = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Shadow = false, + TextSize = 20, + Font = @"Exo2.0-BoldItalic", + }, + versionMapper = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Shadow = false, + TextSize = 16, + Font = @"Exo2.0-Bold", + }, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, LocalisationEngine localisation) + { + title.Colour = artist.Colour = colours.BlueDarker; + versionMapper.Colour = colours.Gray8; + + versionMapper.Text = $"{beatmap.Version} - mapped by {beatmap.Metadata.Author}"; + title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); + artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); + } + } + + private class UserHeader : Container + { + private readonly User user; + private readonly Sprite cover; + + public UserHeader(User user) + { + this.user = user; + Children = new Drawable[] + { + cover = new Sprite + { + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new OsuSpriteText + { + Font = @"Exo2.0-RegularItalic", + Text = user.Username, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + TextSize = 30, + Padding = new MarginPadding { Bottom = 10 }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + if (!string.IsNullOrEmpty(user.CoverUrl)) + cover.Texture = textures.Get(user.CoverUrl); + } + } + + private class SlowScoreCounter : ScoreCounter + { + protected override double RollingDuration => 3000; + + protected override EasingTypes RollingEasing => EasingTypes.OutPow10; + + public SlowScoreCounter(uint leading = 0) : base(leading) + { + DisplayedCountSpriteText.Shadow = false; + DisplayedCountSpriteText.Font = @"Venera-Light"; + UseCommaSeparator = true; + } + } + } +} diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index bebdbb8c2a..04432058dc 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -7,12 +7,15 @@ using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Backgrounds; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens { @@ -24,8 +27,8 @@ namespace osu.Game.Screens protected virtual IEnumerable PossibleChildren => null; - private readonly Container textContainer; - private readonly Box box; + private readonly FillFlowContainer textContainer; + private readonly Container boxContainer; protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2"); @@ -33,20 +36,20 @@ namespace osu.Game.Screens { base.OnEntering(last); - //only show the pop button if we are entered form another gamemode. + //only show the pop button if we are entered form another screen. if (last != null) popButton.Alpha = 1; Content.Alpha = 0; textContainer.Position = new Vector2(DrawSize.X / 16, 0); - box.ScaleTo(0.2f); - box.RotateTo(-20); + boxContainer.ScaleTo(0.2f); + boxContainer.RotateTo(-20); Content.Delay(300, true); - box.ScaleTo(1, transition_time, EasingTypes.OutElastic); - box.RotateTo(0, transition_time / 2, EasingTypes.OutQuint); + boxContainer.ScaleTo(1, transition_time, EasingTypes.OutElastic); + boxContainer.RotateTo(0, transition_time / 2, EasingTypes.OutQuint); textContainer.MoveTo(Vector2.Zero, transition_time, EasingTypes.OutExpo); Content.FadeIn(transition_time, EasingTypes.OutExpo); @@ -82,36 +85,62 @@ namespace osu.Game.Screens Children = new Drawable[] { - box = new Box + boxContainer = new Container { - RelativeSizeAxes = Axes.Both, Size = new Vector2(0.3f), + RelativeSizeAxes = Axes.Both, + CornerRadius = 20, + Masking = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = getColourFor(GetType()), - Alpha = 1, - BlendingMode = BlendingMode.Additive, - }, - textContainer = new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new[] + Children = new Drawable[] { - new OsuSpriteText + new Box { - Text = GetType().Name, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = 50, + RelativeSizeAxes = Axes.Both, + + Colour = getColourFor(GetType()), + Alpha = 0.2f, + BlendingMode = BlendingMode.Additive, }, - new OsuSpriteText + textContainer = new FillFlowContainer { - Text = GetType().Namespace, + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Position = new Vector2(0, 30) + Direction = FillDirection.Vertical, + Children = new[] + { + new TextAwesome + { + Icon = FontAwesome.fa_universal_access, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextSize = 50, + }, + new OsuSpriteText + { + Text = GetType().Name, + Colour = getColourFor(GetType()).Lighten(0.8f), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextSize = 50, + }, + new OsuSpriteText + { + Text = "is not yet ready for use!", + TextSize = 20, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new OsuSpriteText + { + Text = "please check back a bit later.", + TextSize = 14, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + } }, } }, @@ -120,17 +149,15 @@ namespace osu.Game.Screens Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Alpha = 0, - Action = delegate { - Exit(); - } + Action = Exit }, childModeButtons = new FillFlowContainer { Direction = FillDirection.Vertical, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.1f, 1) + RelativeSizeAxes = Axes.Y, + Size = new Vector2(TwoLayerButton.SIZE_RETRACTED.X, 1) } }; @@ -138,14 +165,11 @@ namespace osu.Game.Screens { foreach (Type t in PossibleChildren) { - childModeButtons.Add(new Button + childModeButtons.Add(new ChildModeButton { Text = $@"{t.Name}", - RelativeSizeAxes = Axes.X, - Size = new Vector2(1, 40), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, BackgroundColour = getColourFor(t), + HoverColour = getColourFor(t).Lighten(0.2f), Action = delegate { Push(Activator.CreateInstance(t) as Screen); @@ -163,5 +187,21 @@ namespace osu.Game.Screens byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); return new Color4(r, g, b, 255); } + + public class ChildModeButton : TwoLayerButton + { + public ChildModeButton() + { + Icon = FontAwesome.fa_osu_right_o; + Anchor = Anchor.BottomRight; + Origin = Anchor.BottomRight; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + ActivationSound = audio.Sample.Get(@"Menu/menuhit"); + } + } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f104bf9a37..06aaea041a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Select } } - int startIndex = groups.IndexOf(selectedGroup); + int startIndex = Math.Max(0, groups.IndexOf(selectedGroup)); int index = startIndex; do @@ -221,7 +221,12 @@ namespace osu.Game.Screens.Select private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet) { database.GetChildren(beatmapSet); - beatmapSet.Beatmaps.ForEach(b => { if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; }); + beatmapSet.Beatmaps.ForEach(b => + { + database.GetChildren(b); + if (b.Metadata == null) + b.Metadata = beatmapSet.Metadata; + }); return new BeatmapGroup(beatmapSet, database) { diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index dae909f2b7..cc22cca8bf 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -1,13 +1,10 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Beatmaps; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Screens.Select @@ -17,11 +14,9 @@ namespace osu.Game.Screens.Select private readonly Container content; protected override Container Content => content; - public readonly Container Details; //todo: replace with a real details view when added + public readonly BeatmapDetails Details; public readonly Leaderboard Leaderboard; - private APIAccess api; - private WorkingBeatmap beatmap; public WorkingBeatmap Beatmap { @@ -32,7 +27,8 @@ namespace osu.Game.Screens.Select set { beatmap = value; - if (IsLoaded) Schedule(updateScores); + Leaderboard.Beatmap = beatmap?.BeatmapInfo; + Details.Beatmap = beatmap?.BeatmapInfo; } } @@ -51,14 +47,12 @@ namespace osu.Game.Screens.Select Details.Show(); Leaderboard.Hide(); break; + default: Details.Hide(); Leaderboard.Show(); break; } - - //for now let's always update scores. - updateScores(); }, }, content = new Container @@ -70,9 +64,12 @@ namespace osu.Game.Screens.Select Add(new Drawable[] { - Details = new Container + Details = new BeatmapDetails { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Masking = true, + Height = 352, + Alpha = 0, }, Leaderboard = new Leaderboard { @@ -80,32 +77,5 @@ namespace osu.Game.Screens.Select } }); } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateScores(); - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api) - { - this.api = api; - } - - private GetScoresRequest getScoresRequest; - private void updateScores() - { - if (!IsLoaded) return; - - Leaderboard.Scores = null; - getScoresRequest?.Cancel(); - - if (api == null || beatmap?.BeatmapInfo == null || !Leaderboard.IsPresent) return; - - getScoresRequest = new GetScoresRequest(beatmap.BeatmapInfo); - getScoresRequest.Success += r => Leaderboard.Scores = r.Scores; - api.Queue(getScoresRequest); - } } } diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index c52d0397ed..51ec6f7707 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -4,11 +4,12 @@ using System; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -22,15 +23,22 @@ namespace osu.Game.Screens.Select public Action OnFilter; //passed the selected tab and if mods is checked + private Bindable selectedTab; + private void invokeOnFilter() { - OnFilter?.Invoke(tabs.SelectedItem, modsCheckbox.State == CheckboxState.Checked); + OnFilter?.Invoke(tabs.Current, modsCheckbox.Current); } [BackgroundDependencyLoader] - private void load(OsuColour colour) + private void load(OsuColour colour, OsuConfigManager config) { modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight; + + selectedTab = config.GetBindable(OsuConfig.BeatmapDetailTab); + + tabs.Current.BindTo(selectedTab); + tabs.Current.TriggerChange(); } public BeatmapDetailAreaTabControl() @@ -61,10 +69,8 @@ namespace osu.Game.Screens.Select }, }; - tabs.SelectedItem.ValueChanged += item => invokeOnFilter(); - modsCheckbox.Action += (sender, e) => invokeOnFilter(); - - tabs.SelectedItem.Value = BeatmapDetailTab.Global; + tabs.Current.ValueChanged += item => invokeOnFilter(); + modsCheckbox.Current.ValueChanged += item => invokeOnFilter(); } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs new file mode 100644 index 0000000000..63fdfe3717 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -0,0 +1,488 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using System.Globalization; +using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Framework.Threading; + +namespace osu.Game.Screens.Select +{ + public class BeatmapDetails : Container + { + private readonly MetadataSegment description; + private readonly MetadataSegment source; + private readonly MetadataSegment tags; + + private readonly DifficultyRow circleSize; + private readonly DifficultyRow drainRate; + private readonly DifficultyRow overallDifficulty; + private readonly DifficultyRow approachRate; + private readonly DifficultyRow stars; + + private readonly Container ratingsContainer; + private readonly Bar ratingsBar; + private readonly OsuSpriteText negativeRatings; + private readonly OsuSpriteText positiveRatings; + private readonly BarGraph ratingsGraph; + + private readonly FillFlowContainer retryFailContainer; + private readonly BarGraph retryGraph; + private readonly BarGraph failGraph; + + private ScheduledDelegate pendingBeatmapSwitch; + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + beatmap = value; + + pendingBeatmapSwitch?.Cancel(); + pendingBeatmapSwitch = Schedule(updateStats); + } + } + + private void updateStats() + { + if (beatmap == null) return; + + description.Text = beatmap.Version; + source.Text = beatmap.Metadata.Source; + tags.Text = beatmap.Metadata.Tags; + + circleSize.Value = beatmap.Difficulty.CircleSize; + drainRate.Value = beatmap.Difficulty.DrainRate; + overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty; + approachRate.Value = beatmap.Difficulty.ApproachRate; + stars.Value = (float)beatmap.StarDifficulty; + + var requestedBeatmap = beatmap; + if (requestedBeatmap.Metrics == null) + { + var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); + lookup.Success += res => + { + if (beatmap != requestedBeatmap) + //the beatmap has been changed since we started the lookup. + return; + + requestedBeatmap.Metrics = res; + Schedule(() => updateMetrics(res)); + }; + lookup.Failure += e => updateMetrics(null); + + api.Queue(lookup); + } + + updateMetrics(requestedBeatmap.Metrics, false); + } + + /// + /// Update displayed metrics. + /// + /// New metrics to overwrite the existing display. Can be null. + /// Whether to hide the display on null or empty metrics. If false, we will dim as if waiting for further updates. + private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + { + var hasRatings = metrics?.Ratings.Any() ?? false; + var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any(); + + if (hasRatings) + { + var ratings = metrics.Ratings.ToList(); + ratingsContainer.Show(); + + negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString(); + positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString(); + ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum(); + + ratingsGraph.Values = ratings.Select(rating => (float)rating); + + ratingsContainer.FadeColour(Color4.White, 500, EasingTypes.Out); + } + else if (failOnMissing) + ratingsGraph.Values = new float[10]; + else + ratingsContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out); + + if (hasRetriesFails) + { + var retries = metrics.Retries; + var fails = metrics.Fails; + retryFailContainer.Show(); + + float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max(); + failGraph.MaxValue = maxValue; + retryGraph.MaxValue = maxValue; + + failGraph.Values = fails.Select(fail => (float)fail); + retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); + + retryFailContainer.FadeColour(Color4.White, 500, EasingTypes.Out); + } + else if (failOnMissing) + { + failGraph.Values = new float[100]; + retryGraph.Values = new float[100]; + } + else + retryFailContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out); + } + + public BeatmapDetails() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer() + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.4f, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = EasingTypes.OutQuint, + Children = new [] + { + description = new MetadataSegment("Description"), + source = new MetadataSegment("Source"), + tags = new MetadataSegment("Tags") + }, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.6f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 15), + Padding = new MarginPadding(10) { Top = 0 }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0,5), + Padding = new MarginPadding(10), + Children = new [] + { + circleSize = new DifficultyRow("Circle Size", 7), + drainRate = new DifficultyRow("HP Drain"), + overallDifficulty = new DifficultyRow("Accuracy"), + approachRate = new DifficultyRow("Approach Rate"), + stars = new DifficultyRow("Star Diffculty"), + }, + }, + }, + }, + ratingsContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + AlwaysPresent = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding + { + Top = 25, + Left = 15, + Right = 15, + }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "User Rating", + Font = @"Exo2.0-Medium", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + ratingsBar = new Bar + { + RelativeSizeAxes = Axes.X, + Height = 5, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] + { + negativeRatings = new OsuSpriteText + { + Font = @"Exo2.0-Regular", + Text = "0", + }, + positiveRatings = new OsuSpriteText + { + Font = @"Exo2.0-Regular", + Text = "0", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }, + }, + new OsuSpriteText + { + Text = "Rating Spread", + TextSize = 14, + Font = @"Exo2.0-Regular", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + ratingsGraph = new BarGraph + { + RelativeSizeAxes = Axes.X, + Height = 50, + }, + }, + }, + }, + }, + retryFailContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Points of Failure", + Font = @"Exo2.0-Regular", + }, + new Container + { + RelativeSizeAxes = Axes.X, + Size = new Vector2(1 / 0.6f, 50), + Children = new[] + { + retryGraph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + }, + failGraph = new BarGraph + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + } + }, + }, + } + }; + } + + private APIAccess api; + + [BackgroundDependencyLoader] + private void load(OsuColour colour, APIAccess api) + { + this.api = api; + + description.AccentColour = colour.GrayB; + source.AccentColour = colour.GrayB; + tags.AccentColour = colour.YellowLight; + + stars.AccentColour = colour.Yellow; + + ratingsBar.BackgroundColour = colour.Green; + ratingsBar.AccentColour = colour.YellowDark; + ratingsGraph.Colour = colour.BlueDark; + + failGraph.Colour = colour.YellowDarker; + retryGraph.Colour = colour.Yellow; + } + + private class DifficultyRow : Container, IHasAccentColour + { + private readonly OsuSpriteText name; + private readonly Bar bar; + private readonly OsuSpriteText valueText; + + private readonly float maxValue; + + private float difficultyValue; + public float Value + { + get + { + return difficultyValue; + } + set + { + difficultyValue = value; + bar.Length = value / maxValue; + valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture); + } + } + + public Color4 AccentColour + { + get + { + return bar.AccentColour; + } + set + { + bar.AccentColour = value; + } + } + + public DifficultyRow(string difficultyName, float maxValue = 10) + { + this.maxValue = maxValue; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + name = new OsuSpriteText + { + Font = @"Exo2.0-Regular", + Text = difficultyName, + }, + bar = new Bar + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1, 0.35f), + Padding = new MarginPadding { Left = 100, Right = 25 }, + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = @"Exo2.0-Regular", + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour) + { + name.Colour = colour.GrayB; + bar.BackgroundColour = colour.Gray7; + valueText.Colour = colour.GrayB; + } + } + + private class MetadataSegment : Container, IHasAccentColour + { + private readonly OsuSpriteText header; + private readonly FillFlowContainer content; + + public string Text + { + set + { + if (string.IsNullOrEmpty(value)) + Hide(); + else + { + Show(); + if (header.Text == "Tags") + content.Children = value.Split(' ').Select(text => new OsuSpriteText + { + Text = text, + Font = "Exo2.0-Regular", + }); + else + content.Children = new[] + { + new OsuSpriteText + { + Text = value, + Font = "Exo2.0-Regular", + } + }; + } + } + } + + public Color4 AccentColour + { + get + { + return content.Colour; + } + set + { + content.Colour = value; + } + } + + public MetadataSegment(string headerText) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Margin = new MarginPadding { Top = 10 }; + Children = new Drawable[] + { + header = new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = headerText, + }, + content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5,0), + Margin = new MarginPadding { Top = header.TextSize } + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 768cef4645..61c1f0cc33 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -18,9 +19,9 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Modes; -using osu.Game.Modes.Objects; -using osu.Game.Modes.Objects.Types; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Select { @@ -47,6 +48,8 @@ namespace osu.Game.Screens.Select protected override bool HideOnEscape => false; + protected override bool BlockPassThroughMouse => false; + protected override void PopIn() { MoveToX(0, 800, EasingTypes.OutQuint); @@ -100,7 +103,7 @@ namespace osu.Game.Screens.Select })); //get statistics fromt he current ruleset. - labels.AddRange(Ruleset.GetRuleset(beatmap.BeatmapInfo.Mode).GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); + labels.AddRange(beatmap.BeatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); } AlwaysPresent = true; @@ -146,11 +149,16 @@ namespace osu.Game.Screens.Select }, }, }, - // Text for beatmap info + new DifficultyColourBar(beatmap.BeatmapInfo) + { + RelativeSizeAxes = Axes.Y, + Width = 20, + }, new FillFlowContainer { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Name = "Top-aligned metadata", + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, Direction = FillDirection.Vertical, Margin = new MarginPadding { Top = 10, Left = 25, Right = 10, Bottom = 20 }, AutoSizeAxes = Axes.Both, @@ -159,16 +167,32 @@ namespace osu.Game.Screens.Select new OsuSpriteText { Font = @"Exo2.0-MediumItalic", - Text = metadata.Artist + " -- " + metadata.Title, + Text = beatmapInfo.Version, + TextSize = 24, + }, + } + }, + new FillFlowContainer + { + Name = "Bottom-aligned metadata", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 15, Left = 25, Right = 10, Bottom = 20 }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = @"Exo2.0-MediumItalic", + Text = !string.IsNullOrEmpty(metadata.Source) ? metadata.Source + " — " + metadata.Title : metadata.Title, TextSize = 28, - Shadow = true, }, new OsuSpriteText { Font = @"Exo2.0-MediumItalic", - Text = beatmapInfo.Version, + Text = metadata.Artist, TextSize = 17, - Shadow = true, }, new FillFlowContainer { @@ -182,20 +206,18 @@ namespace osu.Game.Screens.Select Font = @"Exo2.0-Medium", Text = "mapped by ", TextSize = 15, - Shadow = true, - }, + }, new OsuSpriteText { Font = @"Exo2.0-Bold", Text = metadata.Author, TextSize = 15, - Shadow = true, - }, + }, } }, new FillFlowContainer { - Margin = new MarginPadding { Top = 20 }, + Margin = new MarginPadding { Top = 20, Left = 10 }, Spacing = new Vector2(40, 0), AutoSizeAxes = Axes.Both, Children = labels @@ -254,5 +276,37 @@ namespace osu.Game.Screens.Select }; } } + + private class DifficultyColourBar : DifficultyColouredContainer + { + public DifficultyColourBar(BeatmapInfo beatmap) : base(beatmap) + { + } + + [BackgroundDependencyLoader] + private void load() + { + const float full_opacity_ratio = 0.7f; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = AccentColour, + Width = full_opacity_ratio, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = AccentColour, + Alpha = 0.5f, + X = full_opacity_ratio, + Width = 1 - full_opacity_ratio, + } + }; + } + } } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6d92b35993..e0b197e9ca 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -16,7 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Input; -using osu.Game.Modes; +using osu.Game.Database; namespace osu.Game.Screens.Select { @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select Group = group, Sort = sort, SearchText = searchTextBox.Text, - Mode = playMode + Ruleset = ruleset }; public Action Exit; @@ -93,11 +93,6 @@ namespace osu.Game.Screens.Select searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X, - OnChange = (sender, newText) => - { - if (newText) - FilterChanged?.Invoke(CreateCriteria()); - }, Exit = () => Exit?.Invoke(), }, new Box @@ -149,10 +144,12 @@ namespace osu.Game.Screens.Select } }; + searchTextBox.Current.ValueChanged += t => FilterChanged?.Invoke(CreateCriteria()); + groupTabs.PinItem(GroupMode.All); groupTabs.PinItem(GroupMode.RecentlyPlayed); - groupTabs.SelectedItem.ValueChanged += val => Group = val; - sortTabs.SelectedItem.ValueChanged += val => Sort = val; + groupTabs.Current.ValueChanged += val => Group = val; + sortTabs.Current.ValueChanged += val => Sort = val; } public void Deactivate() @@ -166,16 +163,17 @@ namespace osu.Game.Screens.Select searchTextBox.HoldFocus = true; } - private readonly Bindable playMode = new Bindable(); + private readonly Bindable ruleset = new Bindable(); [BackgroundDependencyLoader(permitNulls:true)] private void load(OsuColour colours, OsuGame osu) { sortTabs.AccentColour = colours.GreenLight; - if (osu != null) playMode.BindTo(osu.PlayMode); - playMode.ValueChanged += val => FilterChanged?.Invoke(CreateCriteria()); - playMode.TriggerChange(); + if (osu != null) + ruleset.BindTo(osu.Ruleset); + ruleset.ValueChanged += val => FilterChanged?.Invoke(CreateCriteria()); + ruleset.TriggerChange(); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index acf0954418..d49c7296ba 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.Drawables; -using osu.Game.Modes; +using osu.Game.Database; using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select public GroupMode Group; public SortMode Sort; public string SearchText; - public PlayMode Mode; + public RulesetInfo Ruleset; public void Filter(List groups) { @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Select { var set = g.BeatmapSet; - bool hasCurrentMode = set.Beatmaps.Any(bm => bm.Mode == Mode); + bool hasCurrentMode = set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0)); bool match = hasCurrentMode; @@ -31,7 +31,9 @@ namespace osu.Game.Screens.Select || (set.Metadata.Artist ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 || (set.Metadata.ArtistUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 || (set.Metadata.Title ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 - || (set.Metadata.TitleUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1; + || (set.Metadata.TitleUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.Tags ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.Source ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1; switch (g.State) { diff --git a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs index 1e9d6dc831..647398db9e 100644 --- a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs +++ b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Extensions; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Select.Leaderboards { diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 12ff096d16..02a412685c 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -10,7 +10,12 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using System; -using osu.Game.Modes.Scoring; +using osu.Framework.Allocation; +using osu.Framework.Threading; +using osu.Game.Database; +using osu.Game.Rulesets.Scoring; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; namespace osu.Game.Screens.Select.Leaderboards { @@ -19,6 +24,8 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly ScrollContainer scrollContainer; private readonly FillFlowContainer scrollFlow; + public Action ScoreSelected; + private IEnumerable scores; public IEnumerable Scores { @@ -26,6 +33,7 @@ namespace osu.Game.Screens.Select.Leaderboards set { scores = value; + getScoresRequest?.Cancel(); int i = 150; if (scores == null) @@ -47,6 +55,7 @@ namespace osu.Game.Screens.Select.Leaderboards var ls = new LeaderboardScore(s, 1 + i) { AlwaysPresent = true, + Action = () => ScoreSelected?.Invoke(s), State = Visibility.Hidden, }; scrollFlow.Add(ls); @@ -81,6 +90,46 @@ namespace osu.Game.Screens.Select.Leaderboards }; } + private APIAccess api; + + private BeatmapInfo beatmap; + + private ScheduledDelegate pendingBeatmapSwitch; + + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + beatmap = value; + Scores = null; + + pendingBeatmapSwitch?.Cancel(); + pendingBeatmapSwitch = Schedule(updateScores); + } + } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(APIAccess api) + { + this.api = api; + } + + private GetScoresRequest getScoresRequest; + private void updateScores() + { + if (!IsLoaded) return; + + Scores = null; + getScoresRequest?.Cancel(); + + if (api == null || Beatmap == null) return; + + getScoresRequest = new GetScoresRequest(Beatmap); + getScoresRequest.Success += r => Scores = r.Scores; + api.Queue(getScoresRequest); + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index 2bac387c5c..8df95f6913 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -10,14 +10,14 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; -using osu.Game.Modes.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Users; using osu.Framework; -using osu.Game.Modes.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Select.Leaderboards { - public class LeaderboardScore : Container, IStateful + public class LeaderboardScore : ClickableContainer, IStateful { public static readonly float HEIGHT = 60; @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Leaderboards Children = new Drawable[] { avatar = new DelayedLoadWrapper( - new Avatar(Score.User ?? new User { Id = Score.UserID }) + new Avatar(Score.User) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, @@ -169,7 +169,7 @@ namespace osu.Game.Screens.Select.Leaderboards { nameLabel = new OsuSpriteText { - Text = Score.User?.Username ?? Score.Username, + Text = Score.User.Username, Font = @"Exo2.0-BoldItalic", TextSize = 23, }, @@ -204,7 +204,7 @@ namespace osu.Game.Screens.Select.Leaderboards Children = new Drawable[] { maxCombo = new ScoreComponentLabel(FontAwesome.fa_link, Score.MaxCombo.ToString()), - accuracy = new ScoreComponentLabel(FontAwesome.fa_crosshairs, string.Format(Score.Accuracy % 1 == 0 ? @"{0:0}" : @"{0:0.00}", Score.Accuracy)), + accuracy = new ScoreComponentLabel(FontAwesome.fa_crosshairs, string.Format(Score.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", Score.Accuracy)), }, }, }, diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 78a8e4c177..c15900eb6d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Overlays.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Select { @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 10, Right = 5 }, }); + + beatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s)); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 3e8ddc0f64..182158fa5d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -20,7 +20,6 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Modes; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Select.Options; @@ -29,7 +28,7 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { - private readonly Bindable playMode = new Bindable(); + private readonly Bindable ruleset = new Bindable(); private BeatmapDatabase database; protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); @@ -170,8 +169,8 @@ namespace osu.Game.Screens.Select if (database == null) database = beatmaps; - playMode.ValueChanged += val => { if (Beatmap != null) Beatmap.PreferredPlayMode = val; }; - if (osu != null) playMode.BindTo(osu.PlayMode); + if (osu != null) + ruleset.BindTo(osu.Ruleset); database.BeatmapSetAdded += onBeatmapSetAdded; database.BeatmapSetRemoved += onBeatmapSetRemoved; @@ -200,7 +199,6 @@ namespace osu.Game.Screens.Select { if (Beatmap == null) return; - Beatmap.PreferredPlayMode = playMode.Value; OnSelected(); } diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs index b80f76d281..5783893b3d 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Tournament speedTo(0f, 2000); tracker.FadeIn(200); - delayedStateChangeDelegate = Delay(2300).Schedule(() => scrollState = ScrollState.Stopped); + delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); break; case ScrollState.Stopped: // Find closest to center @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Tournament st.Selected = true; OnSelected?.Invoke(st.Team); - delayedStateChangeDelegate = Delay(10000).Schedule(() => scrollState = ScrollState.Idle); + delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); break; case ScrollState.Idle: resetSelected(); @@ -295,11 +295,7 @@ namespace osu.Game.Screens.Tournament } } - private void speedTo(float value, double duration = 0, EasingTypes easing = EasingTypes.None) - { - DelayReset(); - TransformTo(speed, value, duration, easing, new TransformScrollSpeed()); - } + private void speedTo(float value, double duration = 0, EasingTypes easing = EasingTypes.None) => TransformTo(() => speed, value, duration, easing, new TransformScrollSpeed()); private enum ScrollState { diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 960b452682..729629bdb8 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.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 Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,6 +15,7 @@ namespace osu.Game.Users /// /// The name of this country. /// + [JsonProperty(@"name")] public string FullName; /// @@ -24,6 +26,7 @@ namespace osu.Game.Users /// /// Two-letter flag acronym (ISO 3166 standard) /// + [JsonProperty(@"code")] public string FlagName; } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 6e1de7e3ac..1361eefcff 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -13,11 +13,36 @@ namespace osu.Game.Users [JsonProperty(@"username")] public string Username; + [JsonProperty(@"country_code")] + public string CountryCode; + + [JsonProperty(@"country")] public Country Country; - public Team Team; + //public Team Team; - [JsonProperty(@"colour")] + [JsonProperty(@"profile_colour")] public string Colour; + + [JsonProperty(@"avatar_url")] + public string AvatarUrl; + + [JsonProperty(@"cover_url")] + public string CoverUrl; + + //[JsonProperty(@"cover")] + //public UserCover Cover; + + public class UserCover + { + [JsonProperty(@"custom_url")] + public string CustomUrl; + + [JsonProperty(@"url")] + public string Url; + + [JsonProperty(@"id")] + public int? Id; + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8de6f1201f..02551b6666 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,6 +24,7 @@ prompt 4 false + 6 pdbonly @@ -35,17 +36,14 @@ false - - $(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\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll - True + $(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1341\lib\net45\OpenTK.dll - - ..\packages\SharpCompress.0.15.1\lib\net45\SharpCompress.dll - True + + $(SolutionDir)\packages\SharpCompress.0.15.2\lib\net45\SharpCompress.dll $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll @@ -59,6 +57,7 @@ $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll True + @@ -72,73 +71,116 @@ + - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -147,9 +189,10 @@ + - - + + @@ -180,6 +223,11 @@ + + + + + @@ -202,8 +250,14 @@ - + + + + + + + @@ -213,17 +267,16 @@ - - + - - + + - - + + @@ -334,12 +387,15 @@ + + + - + - + @@ -358,7 +414,7 @@ - + @@ -389,7 +445,7 @@ - - - - + + + diff --git a/osu.sln b/osu.sln index c200d2e586..317cfe7da4 100644 --- a/osu.sln +++ b/osu.sln @@ -21,13 +21,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Desktop.VisualTests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests", "osu.Game.Tests\osu.Game.Tests.csproj", "{54377672-20B1-40AF-8087-5CF73BF3953A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Modes.Osu", "osu.Game.Modes.Osu\osu.Game.Modes.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Modes.Catch", "osu.Game.Modes.Catch\osu.Game.Modes.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Modes.Taiko", "osu.Game.Modes.Taiko\osu.Game.Modes.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Modes.Mania", "osu.Game.Modes.Mania\osu.Game.Modes.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Desktop.Tests", "osu.Desktop.Tests\osu.Desktop.Tests.csproj", "{230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D}" EndProject