diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml index be69e92748..1c988fe6fd 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml index da968b54b7..d7bb0f90f1 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml index f2b8155e37..997ac6b078 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml index 941455f8b4..b7a070174c 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml b/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml deleted file mode 100644 index 20a15f985f..0000000000 --- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml deleted file mode 100644 index 7196e486d2..0000000000 --- a/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index b9bb75d5bb..ed67fa92cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,63 +2,7 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Debug, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "VisualTests (Release, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Game.Tests/bin/Release/net471/osu.Game.Tests.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Release, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "osu! (Debug, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Desktop/bin/Debug/net471/osu!.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Debug, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "osu! (Release, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Desktop/bin/Release/net471/osu!.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Release, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "VisualTests (Debug, netcoreapp2.1)", + "name": "VisualTests (Debug)", "type": "coreclr", "request": "launch", "program": "dotnet", @@ -66,12 +10,12 @@ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build tests (Debug, dotnet)", + "preLaunchTask": "Build tests (Debug)", "env": {}, "console": "internalConsole" }, { - "name": "VisualTests (Release, netcoreapp2.1)", + "name": "VisualTests (Release)", "type": "coreclr", "request": "launch", "program": "dotnet", @@ -79,12 +23,12 @@ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build tests (Release, dotnet)", + "preLaunchTask": "Build tests (Release)", "env": {}, "console": "internalConsole" }, { - "name": "osu! (Debug, netcoreapp2.1)", + "name": "osu! (Debug)", "type": "coreclr", "request": "launch", "program": "dotnet", @@ -92,12 +36,12 @@ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll", ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build osu! (Debug, dotnet)", + "preLaunchTask": "Build osu! (Debug)", "env": {}, "console": "internalConsole" }, { - "name": "osu! (Release, netcoreapp2.1)", + "name": "osu! (Release)", "type": "coreclr", "request": "launch", "program": "dotnet", @@ -105,7 +49,7 @@ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll", ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build osu! (Release, dotnet)", + "preLaunchTask": "Build osu! (Release)", "env": {}, "console": "internalConsole" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bebad750ca..188f20b69f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,34 +4,7 @@ "version": "2.0.0", "tasks": [ { - "label": "Build (Debug, msbuild)", - "type": "shell", - "command": "msbuild", - "args": [ - "/p:TargetFramework=net471", - "/p:GenerateFullPaths=true", - "/m", - "/verbosity:m" - ], - "group": "build", - "problemMatcher": "$msCompile" - }, - { - "label": "Build (Release, msbuild)", - "type": "shell", - "command": "msbuild", - "args": [ - "/p:Configuration=Release", - "/p:TargetFramework=net471", - "/p:GenerateFullPaths=true", - "/m", - "/verbosity:m" - ], - "group": "build", - "problemMatcher": "$msCompile" - }, - { - "label": "Build osu! (Debug, dotnet)", + "label": "Build osu! (Debug)", "type": "shell", "command": "dotnet", "args": [ @@ -47,7 +20,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Build osu! (Release, dotnet)", + "label": "Build osu! (Release)", "type": "shell", "command": "dotnet", "args": [ @@ -64,7 +37,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Build tests (Debug, dotnet)", + "label": "Build tests (Debug)", "type": "shell", "command": "dotnet", "args": [ @@ -80,7 +53,7 @@ "problemMatcher": "$msCompile" }, { - "label": "Build tests (Release, dotnet)", + "label": "Build tests (Release)", "type": "shell", "command": "dotnet", "args": [ @@ -96,15 +69,6 @@ "group": "build", "problemMatcher": "$msCompile" }, - { - "label": "Restore (net471)", - "type": "shell", - "command": "nuget", - "args": [ - "restore" - ], - "problemMatcher": [] - }, { "label": "Restore (netcoreapp2.1)", "type": "shell", diff --git a/COMPILING.md b/COMPILING.md deleted file mode 100644 index bfcbf6bc2c..0000000000 --- a/COMPILING.md +++ /dev/null @@ -1,36 +0,0 @@ -# Linux -### 1. Requirements: -Mono >= 5.4.0 (>= 5.8.0 recommended) -Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release. -NuGet >= 4.4.0 -msbuild -git - -### 2. Cloning project -Clone the entire repository with submodules using -``` -git clone https://github.com/ppy/osu --recursive -``` -Then restore NuGet packages from the repository -``` -nuget restore -``` -### 3. Compiling -Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`. -### 4. Optimizing -If you want additional performance you can change build type to Release with -``` -msbuild -p:Configuration=Release -``` -Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running -``` -mono --aot ./osu\!.exe -``` -### 5. Troubleshooting -You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run -``` -nuget -sudo nuget update -self -``` -**Warning** NuGet creates few config files when it's run for the first time. -Do not run NuGet as root on the first run or you might run into very peculiar issues. diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index d061aa8423..26e80b3f48 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -118,7 +118,7 @@ namespace osu.Desktop.Overlays Icon = FontAwesome.fa_check_square; Activated = delegate { - Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}"); + Process.Start($"https://osu.ppy.sh/home/changelog/{version}"); return true; }; } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 766f36fa74..3cf95e9b3e 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,10 +26,10 @@ - + - + diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 097750d7e0..34e07170bd 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -53,10 +53,10 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + protected override Player CreatePlayer(Ruleset ruleset) { - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return base.CreatePlayer(beatmap, ruleset); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + return base.CreatePlayer(ruleset); } } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 2325a8cad9..d0180f1791 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -82,56 +82,25 @@ namespace osu.Game.Rulesets.Catch { new CatchModEasy(), new CatchModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new CatchModHalfTime(), - new CatchModDaycore(), - }, - }, + new MultiMod(new CatchModHalfTime(), new CatchModDaycore()) }; - case ModType.DifficultyIncrease: return new Mod[] { new CatchModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new CatchModSuddenDeath(), - new CatchModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new CatchModDoubleTime(), - new CatchModNightcore(), - }, - }, + new MultiMod(new CatchModSuddenDeath(), new CatchModPerfect()), + new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()), new CatchModHidden(), new CatchModFlashlight(), }; - case ModType.Special: return new Mod[] { new CatchModRelax(), null, null, - new MultiMod - { - Mods = new Mod[] - { - new CatchModAutoplay(), - new ModCinema(), - }, - }, + new MultiMod(new CatchModAutoplay(), new ModCinema()), }; - default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs index dff2b2d56a..b064d82a23 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs @@ -98,17 +98,12 @@ namespace osu.Game.Rulesets.Mania.Tests }); } - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - [BackgroundDependencyLoader] private void load(RulesetStore rulesets, SettingsStore settings) { maniaRuleset = rulesets.GetRuleset(3); - dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); + Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); } private ManiaPlayfield createPlayfield(int cols, bool inverted = false) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs index ea5f590bd1..d9e360081d 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { public class ManiaConfigManager : RulesetConfigManager { - public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) + public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) : base(settings, ruleset, variant) { } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 2517839355..ca2002b7c9 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; @@ -141,5 +142,22 @@ namespace osu.Game.Rulesets.Mania.Difficulty return difficulty; } + + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new ManiaModDoubleTime(), + new ManiaModHalfTime(), + new ManiaModEasy(), + new ManiaModHardRock(), + new ManiaModKey1(), + new ManiaModKey2(), + new ManiaModKey3(), + new ManiaModKey4(), + new ManiaModKey5(), + new ManiaModKey6(), + new ManiaModKey7(), + new ManiaModKey8(), + new ManiaModKey9(), + }; } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 02ecb3afda..e671a3fb14 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -15,8 +15,11 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Scoring; @@ -105,78 +108,34 @@ namespace osu.Game.Rulesets.Mania { new ManiaModEasy(), new ManiaModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new ManiaModHalfTime(), - new ManiaModDaycore(), - }, - }, + new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()), }; - case ModType.DifficultyIncrease: return new Mod[] { new ManiaModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new ManiaModSuddenDeath(), - new ManiaModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new ManiaModDoubleTime(), - new ManiaModNightcore(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new ManiaModFadeIn(), - new ManiaModHidden(), - } - }, + new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()), + new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()), + new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), }; - case ModType.Special: return new Mod[] { - new MultiMod - { - Mods = new Mod[] - { - new ManiaModKey4(), - new ManiaModKey5(), - new ManiaModKey6(), - new ManiaModKey7(), - new ManiaModKey8(), - new ManiaModKey9(), - new ManiaModKey1(), - new ManiaModKey2(), - new ManiaModKey3(), - }, - }, + new MultiMod(new ManiaModKey4(), + new ManiaModKey5(), + new ManiaModKey6(), + new ManiaModKey7(), + new ManiaModKey8(), + new ManiaModKey9(), + new ManiaModKey1(), + new ManiaModKey2(), + new ManiaModKey3()), new ManiaModRandom(), new ManiaModDualStages(), new ManiaModMirror(), - new MultiMod - { - Mods = new Mod[] - { - new ManiaModAutoplay(), - new ModCinema(), - }, - }, + new MultiMod(new ManiaModAutoplay(), new ModCinema()), }; - default: return new Mod[] { }; } @@ -194,6 +153,8 @@ namespace osu.Game.Rulesets.Mania public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo); + public ManiaRuleset(RulesetInfo rulesetInfo = null) : base(rulesetInfo) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index e02db68a28..6bfe295c3d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mods; @@ -24,5 +26,18 @@ namespace osu.Game.Rulesets.Mania.Mods mbc.TargetColumns = KeyCount; } + + public override Type[] IncompatibleMods => new[] + { + typeof(ManiaModKey1), + typeof(ManiaModKey2), + typeof(ManiaModKey3), + typeof(ManiaModKey4), + typeof(ManiaModKey5), + typeof(ManiaModKey6), + typeof(ManiaModKey7), + typeof(ManiaModKey8), + typeof(ManiaModKey9), + }.Except(new[] { GetType() }).ToArray(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 2147c5a761..d0fc6aa3d6 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -5,6 +5,7 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : base(barLine) { RelativeSizeAxes = Axes.X; - Height = 1; + Height = 2f; AddInternal(new Box { @@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, + Colour = new Color4(255, 204, 33, 255), }); bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index f7de503fb3..e008fa952e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using OpenTK.Graphics; @@ -23,9 +23,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly DrawableNote head; private readonly DrawableNote tail; - private readonly GlowPiece glowPiece; private readonly BodyPiece bodyPiece; - private readonly Container fullHeightContainer; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -37,25 +35,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private bool hasBroken; + private readonly Container tickContainer; + public DrawableHoldNote(HoldNote hitObject, ManiaAction action) : base(hitObject, action) { - Container tickContainer; RelativeSizeAxes = Axes.X; InternalChildren = new Drawable[] { - // The hit object itself cannot be used for various elements because the tail overshoots it - // So a specialized container that is updated to contain the tail height is used - fullHeightContainer = new Container - { - RelativeSizeAxes = Axes.X, - Child = glowPiece = new GlowPiece() - }, bodyPiece = new BodyPiece { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, }, tickContainer = new Container @@ -92,21 +82,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.AccentColour = value; - glowPiece.AccentColour = value; bodyPiece.AccentColour = value; head.AccentColour = value; tail.AccentColour = value; - } - } - - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - // Good enough for now, we just want them to have a lifetime end - this.Delay(2000).Expire(); - break; + tickContainer.ForEach(t => t.AccentColour = value); } } @@ -121,12 +100,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.Update(); // Make the body piece not lie under the head note - bodyPiece.Y = head.Height; - bodyPiece.Height = DrawHeight - head.Height; - - // Make the fullHeightContainer "contain" the height of the tail note, keeping in mind - // that the tail note overshoots the height of this hit object - fullHeightContainer.Height = DrawHeight + tail.Height; + bodyPiece.Y = head.Height / 2; + bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2; } public bool OnPressed(ManiaAction action) @@ -175,8 +150,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : base(holdNote.HitObject.Head, action) { this.holdNote = holdNote; - - GlowPiece.Alpha = 0; } public override bool OnPressed(ManiaAction action) @@ -194,11 +167,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return true; } - - protected override void UpdateState(ArmedState state) - { - // The holdnote keeps scrolling through for now, so having the head disappear looks weird - } } /// @@ -219,8 +187,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : base(holdNote.HitObject.Tail, action) { this.holdNote = holdNote; - - GlowPiece.Alpha = 0; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) @@ -253,11 +219,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - protected override void UpdateState(ArmedState state) - { - // The holdnote keeps scrolling through, so having the tail disappear looks weird - } - public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down public override bool OnReleased(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 74c17ad0fb..5df6079efa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; @@ -87,16 +86,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); } - protected override void UpdateState(ArmedState state) - { - switch (State.Value) - { - case ArmedState.Hit: - AccentColour = Color4.Green; - break; - } - } - protected override void Update() { if (AllJudged) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index db1fec40de..fbf1ee8460 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -27,5 +27,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != null) Action = action.Value; } + + protected override void UpdateState(ArmedState state) + { + switch (state) + { + case ArmedState.Miss: + this.FadeOut(150, Easing.In).Expire(); + break; + case ArmedState.Hit: + this.FadeOut(150, Easing.OutQuint).Expire(); + break; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 0340e6bba3..3de0a9c5cc 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -1,12 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Extensions.Color4Extensions; using OpenTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -16,9 +17,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { - protected readonly GlowPiece GlowPiece; - - private readonly LaneGlowPiece laneGlowPiece; private readonly NotePiece headPiece; public DrawableNote(Note hitObject, ManiaAction action) @@ -27,14 +25,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + CornerRadius = 5; + Masking = true; + InternalChildren = new Drawable[] { - laneGlowPiece = new LaneGlowPiece - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - GlowPiece = new GlowPiece(), headPiece = new NotePiece { Anchor = Anchor.TopCentre, @@ -49,9 +44,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables set { base.AccentColour = value; - laneGlowPiece.AccentColour = AccentColour; - GlowPiece.AccentColour = AccentColour; headPiece.AccentColour = AccentColour; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Lighten(1f).Opacity(0.6f), + Radius = 10, + }; } } @@ -71,17 +71,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddJudgement(new ManiaJudgement { Result = result }); } - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - case ArmedState.Miss: - this.FadeOut(100).Expire(); - break; - } - } - public virtual bool OnPressed(ManiaAction action) { if (action != Action) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 17644a78a5..4ab2da208a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -123,8 +123,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces if (!IsLoaded) return; - foreground.Colour = AccentColour.Opacity(0.4f); - background.Colour = AccentColour.Opacity(0.2f); + foreground.Colour = AccentColour.Opacity(0.9f); + background.Colour = AccentColour.Opacity(0.6f); subtractionCache.Invalidate(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs index e23b24508b..9ebeb91e0c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// internal class NotePiece : Container, IHasAccentColour { - private const float head_height = 10; + public const float NOTE_HEIGHT = 10; private const float head_colour_height = 6; private readonly Box colouredBox; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public NotePiece() { RelativeSizeAxes = Axes.X; - Height = head_height; + Height = NOTE_HEIGHT; Children = new[] { diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 28cd1b6b39..9c1830e642 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Mania.UI public ManiaAction Action; private readonly Box background; + private readonly Box backgroundOverlay; private readonly Container hitTargetBar; private readonly Container keyIcon; @@ -42,22 +43,32 @@ namespace osu.Game.Rulesets.Mania.UI protected override Container Content => content; private readonly Container content; - private const float opacity_released = 0.1f; - private const float opacity_pressed = 0.25f; - public Column() : base(ScrollingDirection.Up) { RelativeSizeAxes = Axes.Y; Width = column_width; + Masking = true; + CornerRadius = 5; + InternalChildren = new Drawable[] { background = new Box { Name = "Background", RelativeSizeAxes = Axes.Both, - Alpha = opacity_released + Alpha = 0.3f + }, + backgroundOverlay = new Box + { + Name = "Background Gradient Overlay", + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Blending = BlendingMode.Additive, + Alpha = 0 }, new Container { @@ -182,6 +193,7 @@ namespace osu.Game.Rulesets.Mania.UI accentColour = value; background.Colour = accentColour; + backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0)); hitTargetBar.EdgeEffect = new EdgeEffectParameters { @@ -223,8 +235,8 @@ namespace osu.Game.Rulesets.Mania.UI { if (action == Action) { - background.FadeTo(opacity_pressed, 50, Easing.OutQuint); - keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint); + backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); + keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); } return false; @@ -234,8 +246,8 @@ namespace osu.Game.Rulesets.Mania.UI { if (action == Action) { - background.FadeTo(opacity_released, 800, Easing.OutQuart); - keyIcon.ScaleTo(1f, 400, Easing.OutQuart); + backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); + keyIcon.ScaleTo(1f, 125, Easing.OutQuint); } return false; @@ -265,8 +277,13 @@ namespace osu.Game.Rulesets.Mania.UI if (action != Action) return false; - var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault(); - hitObject?.PlaySamples(); + var nextObject = + HitObjects.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? + // fallback to non-alive objects to find next off-screen object + HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? + HitObjects.Objects.LastOrDefault(); + + nextObject?.PlaySamples(); return true; } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index f01dfc0db1..f19c3a811b 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -1,19 +1,21 @@ // Copyright (c) 2007-2018 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.Shapes; +using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; namespace osu.Game.Rulesets.Mania.UI { internal class HitExplosion : CompositeDrawable { - private readonly Box inner; + private readonly CircularContainer circle; public HitExplosion(DrawableHitObject judgedObject) { @@ -22,33 +24,32 @@ namespace osu.Game.Rulesets.Mania.UI Anchor = Anchor.TopCentre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(isTick ? 0.5f : 1); - FillMode = FillMode.Fit; + RelativeSizeAxes = Axes.X; + Y = NotePiece.NOTE_HEIGHT / 2; + Height = NotePiece.NOTE_HEIGHT; - Blending = BlendingMode.Additive; + // scale roughly in-line with visual appearance of notes + Scale = new Vector2(isTick ? 0.4f : 0.8f); - Color4 accent = isTick ? Color4.White : judgedObject.AccentColour; - - InternalChild = new CircularContainer + InternalChild = circle = new CircularContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, - BorderThickness = 1, - BorderColour = accent, + // we want our size to be very small so the glow dominates it. + Size = new Vector2(0.1f), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = accent, - Radius = 10, - Hollow = true + Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour, Color4.White, 0, 1), + Radius = 100, }, - Child = inner = new Box + Child = new Box { + Alpha = 0, RelativeSizeAxes = Axes.Both, - Colour = accent, - Alpha = 1, - AlwaysPresent = true, + AlwaysPresent = true } }; } @@ -57,8 +58,8 @@ namespace osu.Game.Rulesets.Mania.UI { base.LoadComplete(); - this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500); - inner.FadeOut(250); + circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint); + this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint); Expire(true); } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 7123aab901..a3145d6035 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -10,12 +10,9 @@ using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; using osu.Game.Input.Handlers; -using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; @@ -103,7 +100,5 @@ namespace osu.Game.Rulesets.Mania.UI protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); - - protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 605794c795..cb93613c7d 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -9,7 +9,6 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -78,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, + CornerRadius = 5, Children = new Drawable[] { new Box @@ -183,15 +183,15 @@ namespace osu.Game.Rulesets.Mania.UI } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { normalColumnColours = new List { - colours.RedDark, - colours.GreenDark + new Color4(94, 0, 57, 255), + new Color4(6, 84, 0, 255) }; - specialColumnColour = colours.BlueDark; + specialColumnColour = new Color4(0, 48, 63, 255); // Set the special column + colour + key foreach (var column in Columns) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3ed072a275..94d2afbf45 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty @@ -71,5 +72,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty return starRating; } + + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new OsuModDoubleTime(), + new OsuModHalfTime(), + new OsuModEasy(), + new OsuModHardRock(), + }; } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index c455bb2af6..6ab75d008f 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -93,57 +93,26 @@ namespace osu.Game.Rulesets.Osu { new OsuModEasy(), new OsuModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new OsuModHalfTime(), - new OsuModDaycore(), - }, - }, + new MultiMod(new OsuModHalfTime(), new OsuModDaycore()), }; - case ModType.DifficultyIncrease: return new Mod[] { new OsuModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new OsuModSuddenDeath(), - new OsuModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModNightcore(), - }, - }, + new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()), + new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new OsuModFlashlight(), }; - case ModType.Special: return new Mod[] { new OsuModRelax(), new OsuModAutopilot(), new OsuModSpunOut(), - new MultiMod - { - Mods = new Mod[] - { - new OsuModAutoplay(), - new ModCinema(), - }, - }, + new MultiMod(new OsuModAutoplay(), new ModCinema()), new OsuModTarget(), }; - default: return new Mod[] { }; } @@ -161,7 +130,7 @@ namespace osu.Game.Rulesets.Osu public override string ShortName => "osu"; - public override SettingsSubsection CreateSettings() => new OsuSettings(); + public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this); public override int? LegacyID => 0; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index e7f17dd86b..240d8dc396 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private Bindable cursorScale; private Bindable autoCursorScale; - private Bindable beatmap; + private readonly IBindable beatmap = new Bindable(); public OsuCursor() { @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuGameBase game) + private void load(OsuConfigManager config, IBindableBeatmap beatmap) { Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer { @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both, }; - beatmap = game.Beatmap.GetBoundCopy(); + this.beatmap.BindTo(beatmap); beatmap.ValueChanged += v => calculateScale(); cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index b63f85623e..f2d5631e93 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -64,9 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI public override void PostProcess() { - connectionLayer.HitObjects = HitObjects.Objects - .Select(d => d.HitObject) - .OrderBy(h => h.StartTime).OfType(); + connectionLayer.HitObjects = HitObjects.Objects.Select(d => d.HitObject).OfType(); } private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuSettings.cs index 31ad6701fd..25c009b117 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettings.cs @@ -8,10 +8,15 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Osu.UI { - public class OsuSettings : SettingsSubsection + public class OsuSettings : RulesetSettingsSubsection { protected override string Header => "osu!"; + public OsuSettings(Ruleset ruleset) + : base(ruleset) + { + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 57e1e65064..bb666eb528 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty @@ -62,6 +63,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return starRating; } + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new TaikoModDoubleTime(), + new TaikoModHalfTime(), + new TaikoModEasy(), + new TaikoModHardRock(), + }; + private bool calculateStrainValues() { // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index abaa8db597..225461fd6f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -84,56 +84,25 @@ namespace osu.Game.Rulesets.Taiko { new TaikoModEasy(), new TaikoModNoFail(), - new MultiMod - { - Mods = new Mod[] - { - new TaikoModHalfTime(), - new TaikoModDaycore(), - }, - }, + new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()), }; - case ModType.DifficultyIncrease: return new Mod[] { new TaikoModHardRock(), - new MultiMod - { - Mods = new Mod[] - { - new TaikoModSuddenDeath(), - new TaikoModPerfect(), - }, - }, - new MultiMod - { - Mods = new Mod[] - { - new TaikoModDoubleTime(), - new TaikoModNightcore(), - }, - }, + new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()), + new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()), new TaikoModHidden(), new TaikoModFlashlight(), }; - case ModType.Special: return new Mod[] { new TaikoModRelax(), null, null, - new MultiMod - { - Mods = new Mod[] - { - new TaikoModAutoplay(), - new ModCinema(), - }, - }, + new MultiMod(new TaikoModAutoplay(), new ModCinema()), }; - default: return new Mod[] { }; } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 4985aa9365..1628423fe8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); - Assert.AreEqual(241526, metadata.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 489c38c420..b834be71f1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index c6863d1cb5..0039516c0c 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -48,17 +48,20 @@ namespace osu.Game.Tests.Beatmaps.IO { var reader = new ZipArchiveReader(osz); - BeatmapMetadata meta; - using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) - meta = Decoder.GetDecoder(stream).Decode(stream).Metadata; + Beatmap beatmap; - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + beatmap = Decoder.GetDecoder(stream).Decode(stream); + + var meta = beatmap.Metadata; + + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); Assert.AreEqual("Deif", meta.AuthorString); Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); - Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime); + Assert.AreEqual(164471, meta.PreviewTime); Assert.AreEqual(string.Empty, meta.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); Assert.AreEqual("Renatus", meta.Title); diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs new file mode 100644 index 0000000000..fd697ba3d3 --- /dev/null +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -0,0 +1,152 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class DifficultyAdjustmentModCombinationsTest + { + [Test] + public void TestNoMods() + { + var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(1, combinations.Length); + Assert.IsTrue(combinations[0] is NoModMod); + } + + [Test] + public void TestSingleMod() + { + var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(2, combinations.Length); + Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[1] is ModA); + } + + [Test] + public void TestDoubleMod() + { + var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(4, combinations.Length); + Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + Assert.IsTrue(combinations[3] is ModB); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + } + + [Test] + public void TestIncompatibleMods() + { + var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(3, combinations.Length); + Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is ModIncompatibleWithA); + } + + [Test] + public void TestDoubleIncompatibleMods() + { + var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(8, combinations.Length); + Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[1] is ModA); + Assert.IsTrue(combinations[2] is MultiMod); + Assert.IsTrue(combinations[3] is ModB); + Assert.IsTrue(combinations[4] is MultiMod); + Assert.IsTrue(combinations[5] is ModIncompatibleWithA); + Assert.IsTrue(combinations[6] is MultiMod); + Assert.IsTrue(combinations[7] is ModIncompatibleWithAAndB); + + Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA); + Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB); + Assert.IsTrue(((MultiMod)combinations[4]).Mods[0] is ModB); + Assert.IsTrue(((MultiMod)combinations[4]).Mods[1] is ModIncompatibleWithA); + Assert.IsTrue(((MultiMod)combinations[6]).Mods[0] is ModIncompatibleWithA); + Assert.IsTrue(((MultiMod)combinations[6]).Mods[1] is ModIncompatibleWithAAndB); + } + + [Test] + public void TestIncompatibleThroughBaseType() + { + var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); + + Assert.AreEqual(3, combinations.Length); + Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[1] is ModAofA); + Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); + } + + private class ModA : Mod + { + public override string Name => nameof(ModA); + public override string ShortenedName => nameof(ModA); + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; + } + + private class ModB : Mod + { + public override string Name => nameof(ModB); + public override string ShortenedName => nameof(ModB); + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) }; + } + + private class ModIncompatibleWithA : Mod + { + public override string Name => $"Incompatible With {nameof(ModA)}"; + public override string ShortenedName => $"Incompatible With {nameof(ModA)}"; + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => new[] { typeof(ModA) }; + } + + private class ModAofA : ModA + { + } + + private class ModIncompatibleWithAofA : ModIncompatibleWithA + { + // Incompatible through base type + } + + private class ModIncompatibleWithAAndB : Mod + { + public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override string ShortenedName => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; + } + + private class TestDifficultyCalculator : DifficultyCalculator + { + public TestDifficultyCalculator(params Mod[] mods) + : base(null) + { + DifficultyAdjustmentMods = mods; + } + + public override double Calculate(Dictionary categoryDifficulty = null) => throw new NotImplementedException(); + + protected override Mod[] DifficultyAdjustmentMods { get; } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index bf7aa725e5..4abfec4371 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; @@ -13,12 +12,11 @@ namespace osu.Game.Tests.Visual [Description("Player instantiated with an autoplay mod.")] public class TestCaseAutoplay : TestCasePlayer { - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + protected override Player CreatePlayer(Ruleset ruleset) { - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); return new ScoreAccessiblePlayer { - InitialBeatmap = beatmap, AllowPause = false, AllowLeadIn = false, AllowResults = false, diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 4679fca855..6d2b37d981 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -449,7 +449,6 @@ namespace osu.Game.Tests.Visual Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { - OnlineBeatmapSetID = id, // Create random metadata, then we can check if sorting works based on these Artist = $"peppy{id.ToString().PadLeft(6, '0')}", Title = $"test set #{id}!", @@ -503,7 +502,6 @@ namespace osu.Game.Tests.Visual Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { - OnlineBeatmapSetID = id, // Create random metadata, then we can check if sorting works based on these Artist = $"peppy{id.ToString().PadLeft(6, '0')}", Title = $"test set #{id}!", diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 996c3b8695..f52fecfd02 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using OpenTK; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -29,14 +28,11 @@ namespace osu.Game.Tests.Visual private RulesetStore rulesets; private TestBeatmapInfoWedge infoWedge; private readonly List beatmaps = new List(); - private readonly Bindable beatmap = new Bindable(); [BackgroundDependencyLoader] - private void load(OsuGameBase game, RulesetStore rulesets) + private void load(RulesetStore rulesets) { this.rulesets = rulesets; - - beatmap.BindTo(game.Beatmap); } protected override void LoadComplete() @@ -53,11 +49,11 @@ namespace osu.Game.Tests.Visual AddStep("show", () => { infoWedge.State = Visibility.Visible; - infoWedge.Beatmap = beatmap; + infoWedge.Beatmap = Beatmap; }); // select part is redundant, but wait for load isn't - selectBeatmap(beatmap.Value.Beatmap); + selectBeatmap(Beatmap.Value.Beatmap); AddWaitStep(3); @@ -120,8 +116,8 @@ namespace osu.Game.Tests.Visual { selectNullBeatmap(); AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); - AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title); - AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist); AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); } @@ -133,7 +129,7 @@ namespace osu.Game.Tests.Visual AddStep($"select {b.Metadata.Title} beatmap", () => { infoBefore = infoWedge.Info; - infoWedge.Beatmap = beatmap.Value = new TestWorkingBeatmap(b); + infoWedge.Beatmap = Beatmap.Value = new TestWorkingBeatmap(b); }); AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); @@ -143,8 +139,8 @@ namespace osu.Game.Tests.Visual { AddStep("select null beatmap", () => { - beatmap.Value = beatmap.Default; - infoWedge.Beatmap = beatmap; + Beatmap.Value = Beatmap.Default; + infoWedge.Beatmap = Beatmap; }); } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index 5be7386238..d3098864f4 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Graphics; -using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -15,6 +14,7 @@ using osu.Game.Users; using System.Collections.Generic; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; namespace osu.Game.Tests.Visual @@ -22,9 +22,9 @@ namespace osu.Game.Tests.Visual [System.ComponentModel.Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase { - private readonly IEnumerable scores; - private readonly IEnumerable anotherScores; - private readonly OnlineScore topScore; + private readonly IEnumerable scores; + private readonly IEnumerable anotherScores; + private readonly APIScore topScore; private readonly Box background; public TestCaseBeatmapScoresContainer() @@ -52,12 +52,12 @@ namespace osu.Game.Tests.Visual AddStep("remove scores", () => scoresContainer.Scores = null); AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); - AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapSetID = 1, OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo }); + AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo }); scores = new[] { - new OnlineScore + new APIScore { User = new User { @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new OnlineScore + new APIScore { User = new User { @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new OnlineScore + new APIScore { User = new User { @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new OnlineScore + new APIScore { User = new User { @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567, Accuracy = 0.8765, }, - new OnlineScore + new APIScore { User = new User { @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual anotherScores = new[] { - new OnlineScore + new APIScore { User = new User { @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new OnlineScore + new APIScore { User = new User { @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new OnlineScore + new APIScore { User = new User { @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual TotalScore = 123456, Accuracy = 0.6543, }, - new OnlineScore + new APIScore { User = new User { @@ -251,7 +251,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new OnlineScore + new APIScore { User = new User { @@ -279,7 +279,7 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - topScore = new OnlineScore + topScore = new APIScore { User = new User { diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 89b1c52010..12a7ee9c12 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -35,9 +35,6 @@ namespace osu.Game.Tests.Visual typeof(MessageFormatter) }; - private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - public TestCaseChatLink() { Add(textContainer = new TestChatLineContainer @@ -53,7 +50,7 @@ namespace osu.Game.Tests.Visual private void load(OsuColour colours) { linkColour = colours.Blue; - dependencies.Cache(new ChatOverlay + Dependencies.Cache(new ChatOverlay { AvailableChannels = { diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index 96a754a5ce..e7bcfbf500 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -17,14 +17,10 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) }; [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - - var compose = new Compose(); - compose.Beatmap.BindTo(osuGame.Beatmap); - - Child = compose; + Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + Child = new Compose(); } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs index a5053bafe8..6c74876e81 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; using OpenTK; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; @@ -16,9 +15,7 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseEditorComposeTimeline : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) }; - - private readonly ScrollableTimeline timeline; + public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) }; public TestCaseEditorComposeTimeline() { @@ -30,19 +27,14 @@ namespace osu.Game.Tests.Visual Origin = Anchor.TopCentre, State = Visibility.Visible }, - timeline = new ScrollableTimeline + new TimelineArea { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(1000, 100) + RelativeSizeAxes = Axes.X, + Size = new Vector2(0.8f, 100) } }; } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - timeline.Beatmap.BindTo(osuGame.Beatmap); - } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs index f037d70493..94b99d483c 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { var testBeatmap = new Beatmap { @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual } }; - osuGame.Beatmap.Value = new TestWorkingBeatmap(testBeatmap); + Beatmap.Value = new TestWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index d01c2d2b92..cafd1b6f1a 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -19,19 +19,16 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - SummaryTimeline summaryTimeline; - Add(summaryTimeline = new SummaryTimeline + Add(new SummaryTimeline { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 50) }); - - summaryTimeline.Beatmap.BindTo(osuGame.Beatmap); } } } diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index d0c46ecdd7..5df371dd09 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -32,15 +32,10 @@ namespace osu.Game.Tests.Visual typeof(NotNullAttribute) }; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap + Beatmap.Value = new TestWorkingBeatmap(new Beatmap { HitObjects = new List { @@ -63,8 +58,8 @@ namespace osu.Game.Tests.Visual }); var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); Child = new OsuHitObjectComposer(new OsuRuleset()); } diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs index 10c813b2f8..5ba0167f12 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/TestCaseMusicController.cs @@ -2,12 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Game.Beatmaps; using osu.Game.Overlays; namespace osu.Game.Tests.Visual @@ -15,8 +12,6 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseMusicController : OsuTestCase { - private readonly Bindable beatmapBacking = new Bindable(); - public TestCaseMusicController() { Clock = new FramedClock(); @@ -30,13 +25,7 @@ namespace osu.Game.Tests.Visual AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); AddStep(@"show", () => mc.State = Visibility.Visible); - AddToggleStep(@"toggle beatmap lock", state => beatmapBacking.Disabled = state); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - beatmapBacking.BindTo(game.Beatmap); + AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); } } } diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 8c52360db8..dab7f7e037 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual private RulesetStore rulesets; - private DependencyContainer dependencies; private WorkingBeatmap defaultBeatmap; public override IReadOnlyList RequiredTypes => new[] @@ -48,8 +47,6 @@ namespace osu.Game.Tests.Visual typeof(DrawableCarouselBeatmapSet), }; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - private class TestSongSelect : PlaySongSelect { public WorkingBeatmap CurrentBeatmap => Beatmap.Value; @@ -58,7 +55,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load() { TestSongSelect songSelect = null; @@ -67,10 +64,10 @@ namespace osu.Game.Tests.Visual // this is by no means clean. should be replacing inside of OsuGameBase somehow. IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext()); - dependencies.Cache(rulesets = new RulesetStore(factory)); - dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) + Dependencies.Cache(rulesets = new RulesetStore(factory)); + Dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) { - DefaultBeatmap = defaultBeatmap = game.Beatmap.Default + DefaultBeatmap = defaultBeatmap = Beatmap.Default }); void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => @@ -78,7 +75,7 @@ namespace osu.Game.Tests.Visual if (deleteMaps) { manager.Delete(manager.GetAllUsableBeatmapSets()); - game.Beatmap.SetDefault(); + Beatmap.SetDefault(); } if (songSelect != null) @@ -125,7 +122,6 @@ namespace osu.Game.Tests.Visual Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { - OnlineBeatmapSetID = 1234 + i, // Create random metadata, then we can check if sorting works based on these Artist = "MONACA " + RNG.Next(0, 9), Title = "Black Song " + RNG.Next(0, 9), diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs index 24ebb534c1..36fb1bcedd 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs @@ -15,17 +15,12 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCasePlaybackControl : OsuTestCase { - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - [BackgroundDependencyLoader] private void load() { var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); var playback = new PlaybackControl { @@ -34,7 +29,7 @@ namespace osu.Game.Tests.Visual Size = new Vector2(200,100) }; - playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); + Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); Child = playback; } diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index 1e7618232d..52a9db080d 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -12,9 +12,10 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(OsuGameBase game) { + Beatmap.Value = new DummyWorkingBeatmap(game); + AddStep("load dummy beatmap", () => Add(new PlayerLoader(new Player { - InitialBeatmap = new DummyWorkingBeatmap(game), AllowPause = false, AllowLeadIn = false, AllowResults = false, diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 5bc16fe420..1f2d99a7d8 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -13,23 +12,20 @@ namespace osu.Game.Tests.Visual [Description("Player instantiated with a replay.")] public class TestCaseReplay : TestCasePlayer { - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + protected override Player CreatePlayer(Ruleset ruleset) { // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here // to simulate setting a replay rather than having the replay already set for us - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value); // We have the replay var replay = dummyRulesetContainer.Replay; // Reset the mods - beatmap.Mods.Value = beatmap.Mods.Value.Where(m => !(m is ModAutoplay)); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - return new ReplayPlayer(replay) - { - InitialBeatmap = beatmap - }; + return new ReplayPlayer(replay); } } } diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index 35e1db7c9e..ee36fb0afc 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -32,18 +32,13 @@ namespace osu.Game.Tests.Visual this.beatmaps = beatmaps; } - private WorkingBeatmap beatmap; - protected override void LoadComplete() { base.LoadComplete(); - if (beatmap == null) - { - var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); - if (beatmapInfo != null) - beatmap = beatmaps.GetWorkingBeatmap(beatmapInfo); - } + var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); + if (beatmapInfo != null) + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); Add(new Results(new Score { @@ -63,10 +58,7 @@ namespace osu.Game.Tests.Visual { Username = "peppy", } - }) - { - InitialBeatmap = beatmap - }); + })); } } } diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 5dad48c6d7..c942342168 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -14,10 +14,6 @@ namespace osu.Game.Tests.Visual private readonly SettingsOverlay settings; private readonly DialogOverlay dialogOverlay; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - public TestCaseSettings() { settings = new MainSettings @@ -33,7 +29,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - dependencies.Cache(dialogOverlay); + Dependencies.Cache(dialogOverlay); Add(settings); } diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs index e721c5ced0..b63881ffa7 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseStoryboard : OsuTestCase { - private readonly Bindable beatmapBacking = new Bindable(); - private readonly Container storyboardContainer; private DrawableStoryboard storyboard; @@ -43,6 +40,7 @@ namespace osu.Game.Tests.Visual }, }, }); + Add(new MusicController { Origin = Anchor.TopRight, @@ -55,10 +53,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load() { - beatmapBacking.BindTo(game.Beatmap); - beatmapBacking.ValueChanged += beatmapChanged; + Beatmap.ValueChanged += beatmapChanged; } private void beatmapChanged(WorkingBeatmap working) @@ -66,10 +63,10 @@ namespace osu.Game.Tests.Visual private void restart() { - var track = beatmapBacking.Value.Track; + var track = Beatmap.Value.Track; track.Reset(); - loadStoryboard(beatmapBacking.Value); + loadStoryboard(Beatmap); track.Start(); } @@ -81,7 +78,7 @@ namespace osu.Game.Tests.Visual var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Storyboard.CreateDrawable(beatmapBacking); + storyboard = working.Storyboard.CreateDrawable(Beatmap); storyboard.Passing = false; storyboardContainer.Add(storyboard); diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs index f625bc0150..b14606780d 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs @@ -12,6 +12,7 @@ using osu.Game.Overlays.Profile.Sections.Recent; using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Tests.Visual { @@ -49,15 +50,15 @@ namespace osu.Game.Tests.Visual }; } - private IEnumerable createDummyActivities() + private IEnumerable createDummyActivities() { - var dummyBeatmap = new RecentActivity.RecentActivityBeatmap + var dummyBeatmap = new APIRecentActivity.RecentActivityBeatmap { Title = @"Dummy beatmap", Url = "/b/1337", }; - var dummyUser = new RecentActivity.RecentActivityUser + var dummyUser = new APIRecentActivity.RecentActivityUser { Username = "DummyReborn", Url = "/u/666", @@ -66,61 +67,61 @@ namespace osu.Game.Tests.Visual return new[] { - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.Achievement, - Achievement = new RecentActivity.RecentActivityAchievement + Achievement = new APIRecentActivity.RecentActivityAchievement { Name = @"Feelin' It", Slug = @"all-secret-feelinit", }, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapPlaycount, Count = 1337, Beatmap = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapsetApprove, Approval = BeatmapApproval.Qualified, Beatmapset = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapsetDelete, Beatmapset = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapsetRevive, Beatmapset = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapsetRevive, Beatmapset = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapsetUpdate, Beatmapset = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.BeatmapsetUpload, Beatmapset = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.Rank, @@ -128,29 +129,29 @@ namespace osu.Game.Tests.Visual Mode = "osu!", Beatmap = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.RankLost, Mode = "osu!", Beatmap = dummyBeatmap, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.UsernameChange, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.UserSupportAgain, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.UserSupportFirst, }, - new RecentActivity + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.UserSupportGift, diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index 776adab0d1..983b98016e 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -5,23 +5,20 @@ using NUnit.Framework; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Tests.Visual { [TestFixture] public class TestCaseWaveform : OsuTestCase { - private readonly Bindable beatmapBacking = new Bindable(); - - public TestCaseWaveform() + [BackgroundDependencyLoader] + private void load() { FillFlowContainer flow; Child = flow = new FillFlowContainer @@ -43,13 +40,13 @@ namespace osu.Game.Tests.Visual for (int i = 1; i <= 16; i *= 2) { - var newDisplay = new BeatmapWaveformGraph + var newDisplay = new WaveformGraph { RelativeSizeAxes = Axes.Both, - Resolution = 1f / i + Resolution = 1f / i, }; - newDisplay.Beatmap.BindTo(beatmapBacking); + Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform; flow.Add(new Container { @@ -83,8 +80,5 @@ namespace osu.Game.Tests.Visual }); } } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); } } diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs new file mode 100644 index 0000000000..70dd67cdbd --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs @@ -0,0 +1,142 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; +using osu.Game.Screens.Edit.Screens.Compose.Timeline; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase + { + private readonly ZoomableScrollContainer scrollContainer; + private readonly Drawable innerBox; + + public TestCaseZoomableScrollContainer() + { + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 250, + Width = 0.75f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(30) + }, + scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + } + }, + new MenuCursor() + }; + + scrollContainer.Add(innerBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + }); + } + + [Test] + public void TestZoom0() + { + reset(); + AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); + AddAssert("Box width = 1x", () => Precision.AlmostEquals(boxQuad.Size, scrollQuad.Size)); + } + + [Test] + public void TestZoom10() + { + reset(); + AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10); + AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre)); + AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X)); + } + + [Test] + public void TestMouseZoomInOnceOutOnce() + { + reset(); + + // Scroll in at 0.25 + AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); + AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0))); + AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); + AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X)); + + // Scroll out at 0.25 + AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0))); + AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); + AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X)); + } + + [Test] + public void TestMouseZoomInTwiceOutTwice() + { + reset(); + + // Scroll in at 0.25 + AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); + AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0))); + AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + + // Scroll in at 0.6 + AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y))); + AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0))); + AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); + + // Very hard to determine actual position, so approximate + AddAssert("Box at correct position (1)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X)); + AddAssert("Box at correct position (2)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.3f * boxQuad.Size.X)); + AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X)); + + // Scroll out at 0.6 + AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0))); + AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + + // Scroll out at 0.25 + AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); + AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); + AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0))); + AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl)); + AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); + } + + private void reset() + { + AddStep("Reset", () => + { + scrollContainer.Zoom = 0; + scrollContainer.ScrollTo(0, false); + }); + } + + private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad; + private Quad boxQuad => innerBox.ScreenSpaceDrawQuad; + } +} diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 40d62103a8..3afc3c4d32 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -23,7 +23,6 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; private int? onlineBeatmapID; - private int? onlineBeatmapSetID; [JsonProperty("id")] public int? OnlineBeatmapID @@ -32,19 +31,10 @@ namespace osu.Game.Beatmaps set { onlineBeatmapID = value > 0 ? value : null; } } - [JsonProperty("beatmapset_id")] - [NotMapped] - public int? OnlineBeatmapSetID - { - get { return onlineBeatmapSetID; } - set { onlineBeatmapSetID = value > 0 ? value : null; } - } - [JsonIgnore] public int BeatmapSetInfoID { get; set; } [Required] - [JsonIgnore] public BeatmapSetInfo BeatmapSet { get; set; } public BeatmapMetadata Metadata { get; set; } @@ -141,8 +131,8 @@ namespace osu.Game.Beatmaps (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; + BeatmapSet.Hash == other.BeatmapSet.Hash && + (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; /// /// Returns a shallow-clone of this . diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 806bcc4132..895b47d62b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -81,12 +81,31 @@ namespace osu.Game.Beatmaps protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) { - model.Beatmaps = createBeatmapDifficulties(model, archive); + model.Beatmaps = createBeatmapDifficulties(archive); - // remove metadata from difficulties where it matches the set foreach (BeatmapInfo b in model.Beatmaps) + { + // remove metadata from difficulties where it matches the set if (model.Metadata.Equals(b.Metadata)) b.Metadata = null; + + // by setting the model here, we can update the noline set id below. + b.BeatmapSet = model; + + fetchAndPopulateOnlineIDs(b); + } + + // check if a set already exists with the same online id, delete if it does. + if (model.OnlineBeatmapSetID != null) + { + var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); + if (existingOnlineId != null) + { + Delete(existingOnlineId); + beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); + Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); + } + } } protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) @@ -99,18 +118,6 @@ namespace osu.Game.Beatmaps return existingHashMatch; } - // check if a set already exists with the same online id - if (model.OnlineBeatmapSetID != null) - { - var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); - if (existingOnlineId != null) - { - Delete(existingOnlineId); - beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); - } - } - return null; } @@ -306,29 +313,29 @@ namespace osu.Game.Beatmaps return hashable.ComputeSHA2Hash(); } - protected override BeatmapSetInfo CreateModel(ArchiveReader reader) + protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive."); - BeatmapMetadata metadata; + Beatmap beatmap; using (var stream = new StreamReader(reader.GetStream(mapName))) - metadata = Decoder.GetDecoder(stream).Decode(stream).Metadata; + beatmap = Decoder.GetDecoder(stream).Decode(stream); return new BeatmapSetInfo { - OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, + OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), Hash = computeBeatmapSetHash(reader), - Metadata = metadata + Metadata = beatmap.Metadata }; } /// /// Create all required s for the provided archive. /// - private List createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader) + private List createBeatmapDifficulties(ArchiveReader reader) { var beatmapInfos = new List(); @@ -348,10 +355,6 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); - // ensure we have the same online set ID as the set itself. - beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID; - beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID; - // check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence. if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null) beatmap.BeatmapInfo.OnlineBeatmapID = null; @@ -376,6 +379,40 @@ namespace osu.Game.Beatmaps return beatmapInfos; } + /// + /// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties. + /// + /// The beatmap to populate. + /// Whether to re-query if the provided beatmap already has populated values. + /// True if population was successful. + private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, bool force = false) + { + if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null) + return true; + + Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database); + + try + { + var req = new GetBeatmapRequest(beatmap); + + req.Perform(api); + + var res = req.Result; + + Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database); + + beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; + beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + return true; + } + catch (Exception e) + { + Logger.Log($"Failed ({e})", LoggingTarget.Database); + return false; + } + } + /// /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. /// diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 34147c18d2..6c1bcd0531 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -17,16 +17,6 @@ namespace osu.Game.Beatmaps [JsonIgnore] public int ID { get; set; } - private int? onlineBeatmapSetID; - - [NotMapped] - [JsonProperty(@"id")] - public int? OnlineBeatmapSetID - { - get { return onlineBeatmapSetID; } - set { onlineBeatmapSetID = value > 0 ? value : null; } - } - public string Title { get; set; } public string TitleUnicode { get; set; } public string Artist { get; set; } @@ -82,8 +72,7 @@ namespace osu.Game.Beatmaps if (other == null) return false; - return onlineBeatmapSetID == other.onlineBeatmapSetID - && Title == other.Title + return Title == other.Title && TitleUnicode == other.TitleUnicode && Artist == other.Artist && ArtistUnicode == other.ArtistUnicode diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index fa08c6cb68..ed8fbdbb26 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -22,18 +22,18 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapSetOnlineInfo OnlineInfo { get; set; } - public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); + public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; [NotMapped] public bool DeletePending { get; set; } public string Hash { get; set; } - public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; + public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; public List Files { get; set; } - public override string ToString() => Metadata.ToString(); + public override string ToString() => Metadata?.ToString() ?? base.ToString(); public bool Protected { get; set; } } diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs new file mode 100644 index 0000000000..34d9cd3d25 --- /dev/null +++ b/osu.Game/Beatmaps/BindableBeatmap.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Diagnostics; +using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; + +namespace osu.Game.Beatmaps +{ + /// + /// A for the beatmap. + /// This should be used sparingly in-favour of . + /// + public abstract class BindableBeatmap : NonNullableBindable, IBindableBeatmap + { + private AudioManager audioManager; + private WorkingBeatmap lastBeatmap; + + protected BindableBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + + /// + /// Registers an for s to be added to. + /// + /// The to register. + protected void RegisterAudioManager([NotNull] AudioManager audioManager) + { + if (this.audioManager != null) throw new InvalidOperationException($"Cannot register multiple {nameof(AudioManager)}s."); + + this.audioManager = audioManager; + + ValueChanged += registerAudioTrack; + + // If the track has changed prior to this being called, let's register it + if (Value != Default) + registerAudioTrack(Value); + } + + private void registerAudioTrack(WorkingBeatmap beatmap) + { + var trackLoaded = lastBeatmap?.TrackLoaded ?? false; + + // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) + if (!trackLoaded || lastBeatmap?.Track != beatmap.Track) + { + if (trackLoaded) + { + Debug.Assert(lastBeatmap != null); + Debug.Assert(lastBeatmap.Track != null); + + lastBeatmap.RecycleTrack(); + } + + audioManager.Track.AddItem(beatmap.Track); + } + + lastBeatmap = beatmap; + } + + [NotNull] + IBindableBeatmap IBindableBeatmap.GetBoundCopy() => GetBoundCopy(); + + /// + /// Retrieve a new instance weakly bound to this . + /// If you are further binding to events of the retrieved , ensure a local reference is held. + /// + [NotNull] + public abstract BindableBeatmap GetBoundCopy(); + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs new file mode 100644 index 0000000000..6acb58e165 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// A component to allow downloading of a beatmap set. Automatically handles state syncing between other instances. + /// + public class BeatmapSetDownloader : Component + { + private readonly BeatmapSetInfo set; + private readonly bool noVideo; + + private BeatmapManager beatmaps; + + /// + /// Whether the associated beatmap set has been downloading (by this instance or any other instance). + /// + public readonly BindableBool Downloaded = new BindableBool(); + + public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false) + { + this.set = set; + this.noVideo = noVideo; + } + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + + beatmaps.ItemAdded += setAdded; + beatmaps.ItemRemoved += setRemoved; + + // initial value + if (set.OnlineBeatmapSetID != null) + Downloaded.Value = beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + { + beatmaps.ItemAdded -= setAdded; + beatmaps.ItemRemoved -= setRemoved; + } + } + + /// + /// Begin downloading the associated beatmap set. + /// + /// True if downloading began. False if an existing download is active or completed. + public bool Download() + { + if (Downloaded.Value) + return false; + + if (beatmaps.GetExistingDownload(set) != null) + return false; + + beatmaps.Download(set, noVideo); + return true; + } + + private void setAdded(BeatmapSetInfo s) + { + if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) + Downloaded.Value = true; + } + + private void setRemoved(BeatmapSetInfo s) + { + if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) + Downloaded.Value = false; + } + } +} diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 8094abe5ed..ee1fc6aec3 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps { private readonly OsuGameBase game; - public DummyWorkingBeatmap(OsuGameBase game) + public DummyWorkingBeatmap(OsuGameBase game = null) : base(new BeatmapInfo { Metadata = new BeatmapMetadata @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => new Beatmap(); - protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4"); + protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4"); protected override Track GetTrack() => new TrackVirtual(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 2aee419d20..581207607a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -8,7 +8,6 @@ using System.Linq; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; -using osu.Framework; namespace osu.Game.Beatmaps.Formats { @@ -28,23 +27,18 @@ namespace osu.Game.Beatmaps.Formats AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); } - /// - /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. - /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - /// - public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0; - /// /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. /// public bool ApplyOffsets = true; - private readonly int offset = UniversalOffset; + private readonly int offset; - public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) + public LegacyBeatmapDecoder(int version = LATEST_VERSION) + : base(version) { // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) - offset += FormatVersion < 5 ? 24 : 0; + offset = FormatVersion < 5 ? 24 : 0; } protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) @@ -142,6 +136,7 @@ namespace osu.Game.Beatmaps.Formats parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(); break; } + break; case @"LetterboxInBreaks": beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; @@ -214,8 +209,7 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); - metadata.OnlineBeatmapSetID = int.Parse(pair.Value); + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) }; break; } } diff --git a/osu.Game/Beatmaps/IBindableBeatmap.cs b/osu.Game/Beatmaps/IBindableBeatmap.cs new file mode 100644 index 0000000000..329c0b6a3c --- /dev/null +++ b/osu.Game/Beatmaps/IBindableBeatmap.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; + +namespace osu.Game.Beatmaps +{ + /// + /// Read-only interface for the beatmap. + /// + public interface IBindableBeatmap : IBindable + { + /// + /// Retrieve a new instance weakly bound to this . + /// If you are further binding to events of the retrieved , ensure a local reference is held. + /// + IBindableBeatmap GetBoundCopy(); + } +} diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 0ef0589dff..0ede6de0f2 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -13,13 +13,13 @@ namespace osu.Game.Configuration { private readonly SettingsStore settings; - private readonly int variant; + private readonly int? variant; private readonly List databasedSettings; private readonly RulesetInfo ruleset; - protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0) + protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null) { this.settings = settings; this.ruleset = ruleset; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 597960c352..f7fe424aa9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -5,6 +5,7 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Platform; using osu.Game.Overlays; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; namespace osu.Game.Configuration @@ -80,6 +81,8 @@ namespace osu.Game.Configuration Set(OsuSetting.FloatingComments, false); + Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); + Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential); Set(OsuSetting.IncreaseFirstObjectVisibility, true); @@ -147,6 +150,7 @@ namespace osu.Game.Configuration SongSelectRightMouseScroll, BeatmapSkins, BeatmapHitsounds, - IncreaseFirstObjectVisibility + IncreaseFirstObjectVisibility, + ScoreDisplayMode } } diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index bf16af4706..f0d49af988 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Containers { public class BeatSyncedContainer : Container { - protected readonly Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); private int lastBeat; private TimingControlPoint lastTimingPoint; @@ -74,9 +74,9 @@ namespace osu.Game.Graphics.Containers } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load(IBindableBeatmap beatmap) { - Beatmap.BindTo(game.Beatmap); + Beatmap.BindTo(beatmap); } protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index dfd181b98a..adbedb2aac 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -14,16 +14,19 @@ namespace osu.Game.Online.API { protected override WebRequest CreateWebRequest() => new JsonWebRequest(Uri); + public T Result => ((JsonWebRequest)WebRequest).ResponseObject; + protected APIRequest() { base.Success += onSuccess; } - private void onSuccess() - { - Success?.Invoke(((JsonWebRequest)WebRequest).ResponseObject); - } + private void onSuccess() => Success?.Invoke(Result); + /// + /// Invoked on successful completion of an API request. + /// This will be scheduled to the API's internal scheduler (run on update thread automatically). + /// public new event APISuccessHandler Success; } @@ -52,7 +55,16 @@ namespace osu.Game.Online.API protected APIAccess API; protected WebRequest WebRequest; + /// + /// Invoked on successful completion of an API request. + /// This will be scheduled to the API's internal scheduler (run on update thread automatically). + /// public event APISuccessHandler Success; + + /// + /// Invoked on failure to complete an API request. + /// This will be scheduled to the API's internal scheduler (run on update thread automatically). + /// public event APIFailureHandler Failure; private bool cancelled; diff --git a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs deleted file mode 100644 index 44c1216959..0000000000 --- a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using System; - -namespace osu.Game.Online.API.Requests -{ - public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong... - { - [JsonProperty(@"covers")] - private BeatmapSetOnlineCovers covers { get; set; } - - [JsonProperty(@"preview_url")] - private string preview { get; set; } - - [JsonProperty(@"play_count")] - private int playCount { get; set; } - - [JsonProperty(@"favourite_count")] - private int favouriteCount { get; set; } - - [JsonProperty(@"bpm")] - private double bpm { get; set; } - - [JsonProperty(@"video")] - private bool hasVideo { get; set; } - - [JsonProperty(@"storyboard")] - private bool hasStoryboard { get; set; } - - [JsonProperty(@"status")] - private BeatmapSetOnlineStatus status { get; set; } - - [JsonProperty(@"submitted_date")] - private DateTimeOffset submitted { get; set; } - - [JsonProperty(@"ranked_date")] - private DateTimeOffset ranked { get; set; } - - [JsonProperty(@"last_updated")] - private DateTimeOffset lastUpdated { get; set; } - - [JsonProperty(@"user_id")] - private long creatorId { - set { Author.Id = value; } - } - - [JsonProperty(@"beatmaps")] - private IEnumerable beatmaps { get; set; } - - public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) - { - return new BeatmapSetInfo - { - OnlineBeatmapSetID = OnlineBeatmapSetID, - Metadata = this, - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = covers, - Preview = preview, - PlayCount = playCount, - FavouriteCount = favouriteCount, - BPM = bpm, - Status = status, - HasVideo = hasVideo, - HasStoryboard = hasStoryboard, - Submitted = submitted, - Ranked = ranked, - LastUpdated = lastUpdated, - }, - Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), - }; - } - - private class APIResponseBeatmap : BeatmapMetadata - { - [JsonProperty(@"id")] - private int onlineBeatmapID { get; set; } - - [JsonProperty(@"playcount")] - private int playCount { get; set; } - - [JsonProperty(@"passcount")] - private int passCount { get; set; } - - [JsonProperty(@"mode_int")] - private int ruleset { get; set; } - - [JsonProperty(@"difficulty_rating")] - private double starDifficulty { get; set; } - - [JsonProperty(@"drain")] - private float drainRate { get; set; } - - [JsonProperty(@"cs")] - private float circleSize { get; set; } - - [JsonProperty(@"ar")] - private float approachRate { get; set; } - - [JsonProperty(@"accuracy")] - private float overallDifficulty { get; set; } - - [JsonProperty(@"total_length")] - private double length { get; set; } - - [JsonProperty(@"count_circles")] - private int circleCount { get; set; } - - [JsonProperty(@"count_sliders")] - private int sliderCount { get; set; } - - [JsonProperty(@"version")] - private string version { get; set; } - - public BeatmapInfo ToBeatmap(RulesetStore rulesets) - { - return new BeatmapInfo - { - Metadata = this, - Ruleset = rulesets.GetRuleset(ruleset), - StarDifficulty = starDifficulty, - OnlineBeatmapID = onlineBeatmapID, - Version = version, - BaseDifficulty = new BeatmapDifficulty - { - DrainRate = drainRate, - CircleSize = circleSize, - ApproachRate = approachRate, - OverallDifficulty = overallDifficulty, - }, - OnlineInfo = new BeatmapOnlineInfo - { - PlayCount = playCount, - PassCount = passCount, - Length = length, - CircleCount = circleCount, - SliderCount = sliderCount, - }, - }; - } - } - } -} diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs index b853da7e76..e3865be5fb 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs @@ -1,46 +1,20 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using Newtonsoft.Json; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetBeatmapDetailsRequest : APIRequest + public class GetBeatmapDetailsRequest : APIRequest { private readonly BeatmapInfo beatmap; - private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; - public GetBeatmapDetailsRequest(BeatmapInfo beatmap) { this.beatmap = beatmap; } - protected override string Target => $@"beatmaps/{lookupString}"; - } - - public class GetBeatmapDetailsResponse : 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; - } - } + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}"; } } diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs new file mode 100644 index 0000000000..9d254ce29d --- /dev/null +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapRequest : APIRequest + { + private readonly BeatmapInfo beatmap; + + private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; + + public GetBeatmapRequest(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + protected override string Target => $@"beatmaps/{lookupString}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs index 37dd77af46..d04e069cd6 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs @@ -1,9 +1,11 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Online.API.Requests.Responses; + namespace osu.Game.Online.API.Requests { - public class GetBeatmapSetRequest : APIRequest + public class GetBeatmapSetRequest : APIRequest { private readonly int id; private readonly BeatmapSetLookupType type; diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index cff6fd4ea5..3be5b91a0d 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -2,20 +2,15 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Users; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Framework.IO.Network; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; private readonly LeaderboardScope scope; @@ -36,9 +31,9 @@ namespace osu.Game.Online.API.Requests Success += onSuccess; } - private void onSuccess(GetScoresResponse r) + private void onSuccess(APIScores r) { - foreach (OnlineScore score in r.Scores) + foreach (APIScore score in r.Scores) score.ApplyBeatmap(beatmap); } @@ -55,112 +50,4 @@ namespace osu.Game.Online.API.Requests protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; } - - public class GetScoresResponse - { - [JsonProperty(@"scores")] - public IEnumerable Scores; - } - - public class OnlineScore : Score - { - [JsonProperty(@"score")] - private double totalScore - { - set { TotalScore = value; } - } - - [JsonProperty(@"max_combo")] - private int maxCombo - { - set { MaxCombo = value; } - } - - [JsonProperty(@"user")] - private User user - { - set { User = value; } - } - - [JsonProperty(@"replay_data")] - private Replay replay - { - set { Replay = value; } - } - - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - - [JsonProperty(@"score_id")] - private long onlineScoreID - { - set { OnlineScoreID = value; } - } - - [JsonProperty(@"created_at")] - private DateTimeOffset date - { - set { Date = value; } - } - - [JsonProperty(@"beatmap")] - private BeatmapInfo beatmap - { - set { Beatmap = value; } - } - - [JsonProperty(@"beatmapset")] - private BeatmapMetadata metadata - { - set { Beatmap.Metadata = value; } - } - - [JsonProperty(@"statistics")] - private Dictionary jsonStats - { - set - { - foreach (var kvp in value) - { - HitResult newKey; - switch (kvp.Key) - { - case @"count_300": - newKey = HitResult.Great; - break; - case @"count_100": - newKey = HitResult.Good; - break; - case @"count_50": - newKey = HitResult.Meh; - break; - case @"count_miss": - newKey = HitResult.Miss; - break; - default: - continue; - } - - Statistics.Add(newKey, kvp.Value); - } - } - } - - [JsonProperty(@"mods")] - private string[] modStrings { get; set; } - - public void ApplyBeatmap(BeatmapInfo beatmap) - { - Beatmap = beatmap; - ApplyRuleset(beatmap.Ruleset); - } - - public void ApplyRuleset(RulesetInfo ruleset) - { - Ruleset = ruleset; - - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); - } - } } diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 48e9babd97..b847a954f6 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -3,10 +3,11 @@ using Humanizer; using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserBeatmapsRequest : APIRequest> + public class GetUserBeatmapsRequest : APIRequest> { private readonly long userId; private readonly int offset; diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs index 106f3d4fb1..9a54aafa82 100644 --- a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -1,14 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserMostPlayedBeatmapsRequest : APIRequest> + public class GetUserMostPlayedBeatmapsRequest : APIRequest> { private readonly long userId; private readonly int offset; @@ -21,28 +19,4 @@ namespace osu.Game.Online.API.Requests protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}"; } - - public class MostPlayedBeatmap - { - [JsonProperty("beatmap_id")] - public int BeatmapID; - - [JsonProperty("count")] - public int PlayCount; - - [JsonProperty] - private BeatmapInfo beatmap; - - [JsonProperty] - private APIResponseBeatmapSet beatmapSet; - - public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) - { - BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); - beatmap.BeatmapSet = setInfo; - beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID; - beatmap.Metadata = setInfo.Metadata; - return beatmap; - } - } } diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index ded0e4f80d..e1cad1a532 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -1,15 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using Newtonsoft.Json; -using osu.Game.Rulesets.Scoring; -using Humanizer; -using System; using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserRecentActivitiesRequest : APIRequest> + public class GetUserRecentActivitiesRequest : APIRequest> { private readonly long userId; private readonly int offset; @@ -23,86 +20,6 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/recent_activity?offset={offset}"; } - public class RecentActivity - { - [JsonProperty("id")] - public int ID; - - [JsonProperty("createdAt")] - public DateTimeOffset CreatedAt; - - [JsonProperty] - private string type - { - set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize()); - } - - public RecentActivityType Type; - - [JsonProperty] - private string scoreRank - { - set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value); - } - - public ScoreRank ScoreRank; - - [JsonProperty("rank")] - public int Rank; - - [JsonProperty("approval")] - public BeatmapApproval Approval; - - [JsonProperty("count")] - public int Count; - - [JsonProperty("mode")] - public string Mode; - - [JsonProperty("beatmap")] - public RecentActivityBeatmap Beatmap; - - [JsonProperty("beatmapset")] - public RecentActivityBeatmap Beatmapset; - - [JsonProperty("user")] - public RecentActivityUser User; - - [JsonProperty("achievement")] - public RecentActivityAchievement Achievement; - - public class RecentActivityBeatmap - { - [JsonProperty("title")] - public string Title; - - [JsonProperty("url")] - public string Url; - } - - public class RecentActivityUser - { - [JsonProperty("username")] - public string Username; - - [JsonProperty("url")] - public string Url; - - [JsonProperty("previousUsername")] - public string PreviousUsername; - } - - public class RecentActivityAchievement - { - [JsonProperty("slug")] - public string Slug; - - [JsonProperty("name")] - public string Name; - } - - } - public enum RecentActivityType { Achievement, diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 7757f529e0..ea14135a61 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -2,10 +2,11 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : APIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/GetUsersRequest.cs b/osu.Game/Online/API/Requests/GetUsersRequest.cs index 575d0464aa..17dd9263c9 100644 --- a/osu.Game/Online/API/Requests/GetUsersRequest.cs +++ b/osu.Game/Online/API/Requests/GetUsersRequest.cs @@ -2,19 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using Newtonsoft.Json; -using osu.Game.Users; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUsersRequest : APIRequest> + public class GetUsersRequest : APIRequest> { protected override string Target => @"rankings/osu/performance"; } - - public class RankingEntry - { - [JsonProperty] - public User User; - } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs new file mode 100644 index 0000000000..99e4392374 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -0,0 +1,85 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIBeatmap : BeatmapMetadata + { + [JsonProperty(@"id")] + public int OnlineBeatmapID { get; set; } + + [JsonProperty(@"beatmapset_id")] + public int OnlineBeatmapSetID { get; set; } + + [JsonProperty(@"playcount")] + private int playCount { get; set; } + + [JsonProperty(@"passcount")] + private int passCount { get; set; } + + [JsonProperty(@"mode_int")] + private int ruleset { get; set; } + + [JsonProperty(@"difficulty_rating")] + private double starDifficulty { get; set; } + + [JsonProperty(@"drain")] + private float drainRate { get; set; } + + [JsonProperty(@"cs")] + private float circleSize { get; set; } + + [JsonProperty(@"ar")] + private float approachRate { get; set; } + + [JsonProperty(@"accuracy")] + private float overallDifficulty { get; set; } + + [JsonProperty(@"total_length")] + private double length { get; set; } + + [JsonProperty(@"count_circles")] + private int circleCount { get; set; } + + [JsonProperty(@"count_sliders")] + private int sliderCount { get; set; } + + [JsonProperty(@"version")] + private string version { get; set; } + + public BeatmapInfo ToBeatmap(RulesetStore rulesets) + { + return new BeatmapInfo + { + Metadata = this, + Ruleset = rulesets.GetRuleset(ruleset), + StarDifficulty = starDifficulty, + OnlineBeatmapID = OnlineBeatmapID, + BeatmapSet = new BeatmapSetInfo + { + OnlineBeatmapSetID = OnlineBeatmapSetID, + }, + Version = version, + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = drainRate, + CircleSize = circleSize, + ApproachRate = approachRate, + OverallDifficulty = overallDifficulty, + }, + OnlineInfo = new BeatmapOnlineInfo + { + PlayCount = playCount, + PassCount = passCount, + Length = length, + CircleCount = circleCount, + SliderCount = sliderCount, + }, + }; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapMetrics.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapMetrics.cs new file mode 100644 index 0000000000..e67c60cb91 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapMetrics.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Beatmaps; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIBeatmapMetrics : 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/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs new file mode 100644 index 0000000000..3b6bb565b0 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2007-2018 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 Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIBeatmapSet : BeatmapMetadata // todo: this is a bit wrong... + { + [JsonProperty(@"covers")] + private BeatmapSetOnlineCovers covers { get; set; } + + private int? onlineBeatmapSetID; + + [JsonProperty(@"id")] + public int? OnlineBeatmapSetID + { + get { return onlineBeatmapSetID; } + set { onlineBeatmapSetID = value > 0 ? value : null; } + } + + [JsonProperty(@"preview_url")] + private string preview { get; set; } + + [JsonProperty(@"play_count")] + private int playCount { get; set; } + + [JsonProperty(@"favourite_count")] + private int favouriteCount { get; set; } + + [JsonProperty(@"bpm")] + private double bpm { get; set; } + + [JsonProperty(@"video")] + private bool hasVideo { get; set; } + + [JsonProperty(@"storyboard")] + private bool hasStoryboard { get; set; } + + [JsonProperty(@"status")] + private BeatmapSetOnlineStatus status { get; set; } + + [JsonProperty(@"submitted_date")] + private DateTimeOffset submitted { get; set; } + + [JsonProperty(@"ranked_date")] + private DateTimeOffset ranked { get; set; } + + [JsonProperty(@"last_updated")] + private DateTimeOffset lastUpdated { get; set; } + + [JsonProperty(@"user_id")] + private long creatorId + { + set { Author.Id = value; } + } + + [JsonProperty(@"beatmaps")] + private IEnumerable beatmaps { get; set; } + + public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) + { + return new BeatmapSetInfo + { + OnlineBeatmapSetID = OnlineBeatmapSetID, + Metadata = this, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = covers, + Preview = preview, + PlayCount = playCount, + FavouriteCount = favouriteCount, + BPM = bpm, + Status = status, + HasVideo = hasVideo, + HasStoryboard = hasStoryboard, + Submitted = submitted, + Ranked = ranked, + LastUpdated = lastUpdated, + }, + Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), + }; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs new file mode 100644 index 0000000000..2c65a37cf8 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Humanizer; +using Newtonsoft.Json; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIRecentActivity + { + [JsonProperty("id")] + public int ID; + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt; + + [JsonProperty] + private string type + { + set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize()); + } + + public RecentActivityType Type; + + [JsonProperty] + private string scoreRank + { + set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value); + } + + public ScoreRank ScoreRank; + + [JsonProperty("rank")] + public int Rank; + + [JsonProperty("approval")] + public BeatmapApproval Approval; + + [JsonProperty("count")] + public int Count; + + [JsonProperty("mode")] + public string Mode; + + [JsonProperty("beatmap")] + public RecentActivityBeatmap Beatmap; + + [JsonProperty("beatmapset")] + public RecentActivityBeatmap Beatmapset; + + [JsonProperty("user")] + public RecentActivityUser User; + + [JsonProperty("achievement")] + public RecentActivityAchievement Achievement; + + public class RecentActivityBeatmap + { + [JsonProperty("title")] + public string Title; + + [JsonProperty("url")] + public string Url; + } + + public class RecentActivityUser + { + [JsonProperty("username")] + public string Username; + + [JsonProperty("url")] + public string Url; + + [JsonProperty("previousUsername")] + public string PreviousUsername; + } + + public class RecentActivityAchievement + { + [JsonProperty("slug")] + public string Slug; + + [JsonProperty("name")] + public string Name; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs new file mode 100644 index 0000000000..a398bf46ee --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -0,0 +1,117 @@ +// Copyright (c) 2007-2018 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 Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIScore : Score + { + [JsonProperty(@"score")] + private double totalScore + { + set => TotalScore = value; + } + + [JsonProperty(@"max_combo")] + private int maxCombo + { + set => MaxCombo = value; + } + + [JsonProperty(@"user")] + private User user + { + set => User = value; + } + + [JsonProperty(@"replay_data")] + private Replay replay + { + set => Replay = value; + } + + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + + [JsonProperty(@"score_id")] + private long onlineScoreID + { + set => OnlineScoreID = value; + } + + [JsonProperty(@"created_at")] + private DateTimeOffset date + { + set => Date = value; + } + + [JsonProperty(@"beatmap")] + private BeatmapInfo beatmap + { + set => Beatmap = value; + } + + [JsonProperty(@"beatmapset")] + private BeatmapMetadata metadata + { + set => Beatmap.Metadata = value; + } + + [JsonProperty(@"statistics")] + private Dictionary jsonStats + { + set + { + foreach (var kvp in value) + { + HitResult newKey; + switch (kvp.Key) + { + case @"count_300": + newKey = HitResult.Great; + break; + case @"count_100": + newKey = HitResult.Good; + break; + case @"count_50": + newKey = HitResult.Meh; + break; + case @"count_miss": + newKey = HitResult.Miss; + break; + default: + continue; + } + + Statistics.Add(newKey, kvp.Value); + } + } + } + + [JsonProperty(@"mods")] + private string[] modStrings { get; set; } + + public void ApplyBeatmap(BeatmapInfo beatmap) + { + Beatmap = beatmap; + ApplyRuleset(beatmap.Ruleset); + } + + public void ApplyRuleset(RulesetInfo ruleset) + { + Ruleset = ruleset; + + // Evaluate the mod string + Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIScores.cs b/osu.Game/Online/API/Requests/Responses/APIScores.cs new file mode 100644 index 0000000000..b4213db253 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScores.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2007-2018 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.Online.API.Requests.Responses +{ + public class APIScores + { + [JsonProperty(@"scores")] + public IEnumerable Scores; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs new file mode 100644 index 0000000000..54c8451456 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUser + { + [JsonProperty] + public User User; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs new file mode 100644 index 0000000000..8a5aea9e97 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUserMostPlayedBeatmap + { + [JsonProperty("beatmap_id")] + public int BeatmapID; + + [JsonProperty("count")] + public int PlayCount; + + [JsonProperty] + private BeatmapInfo beatmap; + + [JsonProperty] + private APIBeatmapSet beatmapSet; + + public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) + { + BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); + beatmap.BeatmapSet = setInfo; + beatmap.Metadata = setInfo.Metadata; + return beatmap; + } + } +} diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index d68314f8fc..2a154d1230 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.ComponentModel; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class SearchBeatmapSetsRequest : APIRequest> + public class SearchBeatmapSetsRequest : APIRequest> { private readonly string query; private readonly RulesetInfo ruleset; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6269fcfa76..a5779a2293 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -57,13 +56,16 @@ namespace osu.Game protected SettingsStore SettingsStore; + protected RulesetConfigCache RulesetConfigCache; + protected MenuCursorContainer MenuCursorContainer; private Container content; protected override Container Content => content; - public Bindable Beatmap { get; private set; } + private OsuBindableBeatmap beatmap; + protected BindableBeatmap Beatmap => beatmap; private Bindable fpsDisplayVisible; @@ -123,6 +125,7 @@ namespace osu.Game dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); + dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new OsuColour()); fileImporters.Add(BeatmapManager); @@ -157,33 +160,15 @@ namespace osu.Game Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); var defaultBeatmap = new DummyWorkingBeatmap(this); - Beatmap = new NonNullableBindable(defaultBeatmap); + beatmap = new OsuBindableBeatmap(defaultBeatmap, Audio); BeatmapManager.DefaultBeatmap = defaultBeatmap; // tracks play so loud our samples can't keep up. // this adds a global reduction of track volume for the time being. Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); - Beatmap.ValueChanged += b => - { - var trackLoaded = lastBeatmap?.TrackLoaded ?? false; - - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (!trackLoaded || lastBeatmap?.Track != b.Track) - { - if (trackLoaded) - { - Debug.Assert(lastBeatmap != null); - Debug.Assert(lastBeatmap.Track != null); - - lastBeatmap.RecycleTrack(); - } - - Audio.Track.AddItem(b.Track); - } - - lastBeatmap = b; - }; + dependencies.CacheAs(beatmap); + dependencies.CacheAs(beatmap); FileStore.Cleanup(); @@ -204,6 +189,17 @@ namespace osu.Game dependencies.Cache(globalBinding); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // TODO: This is temporary until we reimplement the local FPS display. + // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. + fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); + fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; + fpsDisplayVisible.TriggerChange(); + } + private void runMigrations() { try @@ -227,19 +223,6 @@ namespace osu.Game } } - private WorkingBeatmap lastBeatmap; - - protected override void LoadComplete() - { - base.LoadComplete(); - - // TODO: This is temporary until we reimplement the local FPS display. - // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. - fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; - fpsDisplayVisible.TriggerChange(); - } - public override void SetHost(GameHost host) { if (LocalConfig == null) @@ -258,5 +241,26 @@ namespace osu.Game } public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); + + private class OsuBindableBeatmap : BindableBeatmap + { + public OsuBindableBeatmap(WorkingBeatmap defaultValue, AudioManager audioManager) + : this(defaultValue) + { + RegisterAudioManager(audioManager); + } + + private OsuBindableBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + + public override BindableBeatmap GetBoundCopy() + { + var copy = new OsuBindableBeatmap(Default); + copy.BindTo(this); + return copy; + } + } } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index c699ae2328..4fce6a49fb 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -3,6 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; @@ -11,10 +13,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class DownloadButton : HeaderButton { - public DownloadButton(string title, string subtitle) + public DownloadButton(BeatmapSetInfo set, bool noVideo = false) { Width = 120; + BeatmapSetDownloader downloader; Add(new Container { Depth = -1, @@ -22,6 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { + downloader = new BeatmapSetDownloader(set, noVideo), new FillFlowContainer { Anchor = Anchor.CentreLeft, @@ -32,13 +36,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { new OsuSpriteText { - Text = title, + Text = "Download", TextSize = 13, Font = @"Exo2.0-Bold", }, new OsuSpriteText { - Text = subtitle, + Text = set.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty, TextSize = 11, Font = @"Exo2.0-Bold", }, @@ -54,6 +58,25 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }, }); + + Action = () => + { + if (!downloader.Download()) + { + Content.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine); + } + }; + + downloader.Downloaded.ValueChanged += d => + { + if (d) + this.FadeOut(200); + else + this.FadeIn(200); + }; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 89c141ef17..afba99f928 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -35,8 +35,6 @@ namespace osu.Game.Overlays.BeatmapSet private readonly BeatmapSetOnlineStatusPill onlineStatusPill; public Details Details; - private BeatmapManager beatmaps; - public readonly BeatmapPicker Picker; private BeatmapSetInfo beatmapSet; @@ -68,8 +66,24 @@ namespace osu.Game.Overlays.BeatmapSet downloadButtonsContainer.FadeIn(transition_duration); favouriteButton.FadeIn(transition_duration); - noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration); - videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration); + if (BeatmapSet.OnlineInfo.HasVideo) + { + videoButtons.Children = new[] + { + new DownloadButton(BeatmapSet), + new DownloadButton(BeatmapSet, true), + }; + + videoButtons.FadeIn(transition_duration); + noVideoButtons.FadeOut(transition_duration); + } + else + { + noVideoButtons.Child = new DownloadButton(BeatmapSet); + + noVideoButtons.FadeIn(transition_duration); + videoButtons.FadeOut(transition_duration); + } } else { @@ -192,27 +206,12 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.Both, Alpha = 0f, - Child = new DownloadButton("Download", @"") - { - Action = () => download(false), - }, }, videoButtons = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Spacing = new Vector2(buttons_spacing), Alpha = 0f, - Children = new[] - { - new DownloadButton("Download", "with Video") - { - Action = () => download(false), - }, - new DownloadButton("Download", "without Video") - { - Action = () => download(true), - }, - }, }, }, }, @@ -248,41 +247,10 @@ namespace osu.Game.Overlays.BeatmapSet } [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapManager beatmaps) + private void load(OsuColour colours) { tabsBg.Colour = colours.Gray3; - this.beatmaps = beatmaps; - - beatmaps.ItemAdded += handleBeatmapAdd; - updateDisplay(); } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd; - } - - private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() => - { - if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID) - downloadButtonsContainer.FadeOut(transition_duration); - }); - - private void download(bool noVideo) - { - if (beatmaps.GetExistingDownload(BeatmapSet) != null) - { - downloadButtonsContainer.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - - return; - } - - beatmaps.Download(BeatmapSet, noVideo); - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 10e689698d..fc8b3a6800 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; - public DrawableScore(int index, OnlineScore score) + public DrawableScore(int index, APIScore score) { ScoreModsContainer modsContainer; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 8ac79eabb4..b3ceffd35e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -42,8 +42,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly InfoColumn statistics; private readonly ScoreModsContainer modsContainer; - private OnlineScore score; - public OnlineScore Score + private APIScore score; + public APIScore Score { get { return score; } set diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 185282bec9..626de14c98 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -11,6 +11,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -28,10 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; + private IEnumerable scores; private BeatmapInfo beatmap; - public IEnumerable Scores + public IEnumerable Scores { get { return scores; } set diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index ed4630a8e7..723e9e8b35 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -166,14 +166,13 @@ namespace osu.Game.Overlays.Direct }, }, }, - new DownloadButton + new DownloadButton(SetInfo) { Size = new Vector2(30), Margin = new MarginPadding(horizontal_padding), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Colour = colours.Gray5, - Action = StartDownload }, }, }, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 13398a4a32..6e3483604b 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -12,28 +12,31 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Game.Beatmaps; namespace osu.Game.Overlays.Direct { public class DirectListPanel : DirectPanel { + private const float transition_duration = 120; private const float horizontal_padding = 10; private const float vertical_padding = 5; private const float height = 70; + private PlayButton playButton; + private Box progressBar; + private Container downloadContainer; + + protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; + public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap) { RelativeSizeAxes = Axes.X; Height = height; } - private PlayButton playButton; - private Box progressBar; - - protected override PlayButton PlayButton => playButton; - protected override Box PreviewBar => progressBar; - [BackgroundDependencyLoader] private void load(LocalisationEngine localisation, OsuColour colours) { @@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Direct AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, LayoutEasing = Easing.OutQuint, - LayoutDuration = 120, + LayoutDuration = transition_duration, Spacing = new Vector2(10, 0), Children = new Drawable[] { @@ -104,53 +107,69 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding }, + Direction = FillDirection.Horizontal, + LayoutEasing = Easing.OutQuint, + LayoutDuration = transition_duration, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) + downloadContainer = new Container { - Margin = new MarginPadding { Right = 1 }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Alpha = 0, + Child = new DownloadButton(SetInfo) + { + Size = new Vector2(height - vertical_padding * 2), + Margin = new MarginPadding { Left = vertical_padding }, + }, }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] + Direction = FillDirection.Vertical, + Children = new Drawable[] { - new OsuSpriteText + new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) { - Text = "mapped by ", - TextSize = 14, + Margin = new MarginPadding { Right = 1 }, + }, + new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = "mapped by ", + TextSize = 14, + }, + new OsuSpriteText + { + Text = SetInfo.Metadata.Author.Username, + TextSize = 14, + Font = @"Exo2.0-SemiBoldItalic", + }, + }, }, new OsuSpriteText { - Text = SetInfo.Metadata.Author.Username, + Text = $"from {SetInfo.Metadata.Source}", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, TextSize = 14, - Font = @"Exo2.0-SemiBoldItalic", + Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, }, }, }, - new OsuSpriteText - { - Text = $"from {SetInfo.Metadata.Source}", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - TextSize = 14, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, - }, }, }, - new DownloadButton - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Size = new Vector2(height - vertical_padding * 2), - Action = StartDownload - }, }, }, progressBar = new Box @@ -165,5 +184,17 @@ namespace osu.Game.Overlays.Direct }, }); } + + protected override bool OnHover(InputState state) + { + downloadContainer.FadeIn(transition_duration, Easing.InOutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + downloadContainer.FadeOut(transition_duration, Easing.InOutQuint); + base.OnHoverLost(state); + } } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index df784252ce..e767f6ec83 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -147,22 +147,6 @@ namespace osu.Game.Overlays.Direct protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo); - protected void StartDownload() - { - if (beatmaps.GetExistingDownload(SetInfo) != null) - { - // we already have an active download running. - content.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - - return; - } - - beatmaps.Download(SetInfo); - } - private void attachDownload(DownloadBeatmapSetRequest request) { if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID) diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index f01c9dac59..1ffa8dbd35 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -3,6 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using OpenTK; @@ -13,10 +15,12 @@ namespace osu.Game.Overlays.Direct { private readonly SpriteIcon icon; - public DownloadButton() + public DownloadButton(BeatmapSetInfo set, bool noVideo = false) { + BeatmapSetDownloader downloader; Children = new Drawable[] { + downloader = new BeatmapSetDownloader(set, noVideo), icon = new SpriteIcon { Anchor = Anchor.Centre, @@ -25,6 +29,25 @@ namespace osu.Game.Overlays.Direct Icon = FontAwesome.fa_osu_chevron_down_o, }, }; + + Action = () => + { + if (!downloader.Download()) + { + Content.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine); + } + }; + + downloader.Downloaded.ValueChanged += d => + { + if (d) + this.FadeOut(200); + else + this.FadeIn(200); + }; } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index f437546888..b33f271986 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -28,7 +28,6 @@ namespace osu.Game.Overlays private APIAccess api; private RulesetStore rulesets; - private BeatmapManager beatmaps; private readonly FillFlowContainer resultCountsContainer; private readonly OsuSpriteText resultCountsText; @@ -177,24 +176,14 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps) + private void load(OsuColour colours, APIAccess api, RulesetStore rulesets) { this.api = api; this.rulesets = rulesets; - this.beatmaps = beatmaps; resultCountsContainer.Colour = colours.Yellow; - - beatmaps.ItemAdded += setAdded; } - private void setAdded(BeatmapSetInfo set) => Schedule(() => - { - // if a new map was imported, we should remove it from search results (download completed etc.) - panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire(); - BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID); - }); - private void updateResultCounts() { resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint); @@ -297,9 +286,7 @@ namespace osu.Game.Overlays { Task.Run(() => { - var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList(); - var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList(); - var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList(); + var sets = response.Select(r => r.ToBeatmapSet(rulesets)).ToList(); // may not need scheduling; loads async internally. Schedule(() => @@ -323,14 +310,6 @@ namespace osu.Game.Overlays private int distinctCount(List list) => list.Distinct().ToArray().Length; - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - beatmaps.ItemAdded -= setAdded; - } - public class ResultCounts { public readonly int Artists; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index d4bc8c5b27..19ce0cf932 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -73,13 +73,13 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, OsuGameBase osuGame) + private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap) { beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); beatmaps.ItemAdded += addBeatmapSet; beatmaps.ItemRemoved += removeBeatmapSet; - beatmapBacking.BindTo(osuGame.Beatmap); + beatmapBacking.BindTo(beatmap); beatmapBacking.ValueChanged += _ => updateSelectedSet(); } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 085a44ecba..b74e7e1178 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -21,17 +21,22 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; + /// + /// Invoked when the order of an item in the list has changed. + /// The second parameter indicates the new index of the item. + /// public Action OrderChanged; + private readonly Bindable beatmap = new Bindable(); private BeatmapManager beatmaps; + private FilterControl filter; private PlaylistList list; - private readonly Bindable beatmapBacking = new Bindable(); - [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) + private void load(OsuColour colours, BindableBeatmap beatmap, BeatmapManager beatmaps) { + this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; Children = new Drawable[] @@ -73,15 +78,13 @@ namespace osu.Game.Overlays.Music }, }; - beatmapBacking.BindTo(game.Beatmap); - filter.Search.OnCommit = (sender, newText) => { - BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); - if (beatmap != null) + BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (toSelect != null) { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap); - beatmapBacking.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); + beatmap.Value.Track.Restart(); } }; } @@ -105,14 +108,14 @@ namespace osu.Game.Overlays.Music private void itemSelected(BeatmapSetInfo set) { - if (set.ID == (beatmapBacking.Value?.BeatmapSetInfo?.ID ?? -1)) + if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - beatmapBacking.Value?.Track?.Seek(0); + beatmap.Value?.Track?.Seek(0); return; } - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); - beatmapBacking.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmap.Value.Track.Restart(); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index fb4e278b0c..d96bb40165 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,11 +30,8 @@ namespace osu.Game.Overlays public class MusicController : OsuFocusedOverlayContainer { private const float player_height = 130; - private const float transition_length = 800; - private const float progress_height = 10; - private const float bottom_black_area_height = 55; private Drawable background; @@ -49,16 +46,17 @@ namespace osu.Game.Overlays private PlaylistOverlay playlist; + private BeatmapManager beatmaps; private LocalisationEngine localisation; - private BeatmapManager beatmaps; - private readonly Bindable beatmapBacking = new Bindable(); private List beatmapSets; private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; + private readonly Bindable beatmap = new Bindable(); + public MusicController() { Width = 400; @@ -97,8 +95,9 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) + private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) { + this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; this.localisation = localisation; @@ -224,8 +223,6 @@ namespace osu.Game.Overlays beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; - beatmapBacking.BindTo(game.Beatmap); - playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } @@ -240,10 +237,8 @@ namespace osu.Game.Overlays protected override void LoadComplete() { - beatmapBacking.ValueChanged += beatmapChanged; - beatmapBacking.DisabledChanged += beatmapDisabledChanged; - beatmapBacking.TriggerChange(); - + beatmap.BindValueChanged(beatmapChanged, true); + beatmap.BindDisabledChanged(beatmapDisabledChanged, true); base.LoadComplete(); } @@ -276,7 +271,7 @@ namespace osu.Game.Overlays playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && beatmapSets.Any()) + if (track.HasCompleted && !track.Looping && !beatmap.Disabled && beatmapSets.Any()) next(); } else @@ -289,7 +284,7 @@ namespace osu.Game.Overlays if (track == null) { - if (!beatmapBacking.Disabled) + if (!beatmap.Disabled) next(true); return; } @@ -307,8 +302,8 @@ namespace osu.Game.Overlays var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); if (playable != null) { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking); - beatmapBacking.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); + beatmap.Value.Track.Restart(); } } @@ -320,8 +315,8 @@ namespace osu.Game.Overlays var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); if (playable != null) { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking); - beatmapBacking.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); + beatmap.Value.Track.Restart(); } } diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs index 36a9a9b01a..f968f94187 100644 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs @@ -107,13 +107,20 @@ namespace osu.Game.Overlays.Profile.Header visibleBadge = 0; badgeFlowContainer.Clear(); - foreach (var badge in badges) + for (var index = 0; index < badges.Length; index++) { - LoadComponentAsync(new DrawableBadge(badge) + int displayIndex = index; + LoadComponentAsync(new DrawableBadge(badges[index]) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - }, badgeFlowContainer.Add); + }, asyncBadge => + { + badgeFlowContainer.Add(asyncBadge); + + // load in stable order regardless of async load order. + badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + }); } } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 97079c77f3..359bfc7564 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Sections { Action = () => { - if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value); + if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value); }; Child = new FillFlowContainer diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 87df84e2e5..707de0a10f 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -8,6 +8,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Users; using System; using System.Linq; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -49,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks MissingText.Hide(); - foreach (OnlineScore score in scores) + foreach (APIScore score in scores) { DrawableProfileScore drawableScore; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 721cc30b61..046c1b1a33 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Screens.Select.Leaderboards; @@ -17,11 +18,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { private APIAccess api; - private readonly RecentActivity activity; + private readonly APIRecentActivity activity; private LinkFlowContainer content; - public DrawableRecentActivity(RecentActivity activity) + public DrawableRecentActivity(APIRecentActivity activity) { this.activity = activity; } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 8c1108b115..ee2f2f5973 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; using System.Linq; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Profile.Sections.Recent { @@ -36,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent MissingText.Hide(); - foreach (RecentActivity activity in activities) + foreach (APIRecentActivity activity in activities) { ItemsContainer.Add(new DrawableRecentActivity(activity)); } diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs new file mode 100644 index 0000000000..05104018cd --- /dev/null +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Rulesets; + +namespace osu.Game.Overlays.Settings +{ + /// + /// A which provides subclasses with the + /// from the 's . + /// + public abstract class RulesetSettingsSubsection : SettingsSubsection + { + private readonly Ruleset ruleset; + + protected RulesetSettingsSubsection(Ruleset ruleset) + { + this.ruleset = ruleset; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + var config = dependencies.Get().GetConfigFor(ruleset); + if (config != null) + dependencies.Cache(config); + + return dependencies; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 647395cf69..9c8f5e2643 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Overlays.Settings.Sections.Gameplay { @@ -38,6 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) }, + new SettingsEnumDropdown + { + LabelText = "Score display mode", + Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) + } }; } } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 04a04bc86e..451809867d 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -168,9 +168,25 @@ namespace osu.Game.Overlays.Volume private set => Bindable.Value = value; } - public void Increase() => Volume += 0.05f; + private const float adjust_step = 0.05f; - public void Decrease() => Volume -= 0.05f; + public void Increase() => adjust(1); + public void Decrease() => adjust(-1); + + private void adjust(int direction) + { + float amount = adjust_step * direction; + + var mouse = GetContainingInputManager().CurrentState.Mouse; + if (mouse.HasPreciseScroll) + { + float scrollDelta = mouse.ScrollDelta.Y; + if (scrollDelta != 0) + amount *= Math.Abs(scrollDelta / 10); + } + + Volume += amount; + } public bool OnPressed(GlobalAction action) { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 4ecf1eefb2..74cece5154 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Configuration public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager where T : struct { - protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant) + protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) { } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 070bc7ddb0..31cd9dc6f5 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 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.Extensions.IEnumerableExtensions; @@ -36,6 +37,44 @@ namespace osu.Game.Rulesets.Difficulty { } + /// + /// Creates all combinations which adjust the difficulty. + /// + public Mod[] CreateDifficultyAdjustmentModCombinations() + { + return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); + + IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) + { + // Initial-case: Empty current set + if (currentSetCount == 0) + yield return new NoModMod(); + + if (currentSetCount == 1) + yield return currentSet.Single(); + + if (currentSetCount > 1) + yield return new MultiMod(currentSet.ToArray()); + + // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod + // combinations in further recursions, so a moving subset is used to eliminate this effect + for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) + { + var adjustmentMod = adjustmentSet[i]; + if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) + continue; + + foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) + yield return combo; + } + } + } + + /// + /// Retrieves all s which adjust the difficulty. + /// + protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); + public abstract double Calculate(Dictionary categoryDifficulty = null); } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5f1b9a6bad..0c91c9f548 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit private RulesetContainer rulesetContainer; private readonly List layerContainers = new List(); - private readonly Bindable beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); protected HitObjectComposer(Ruleset ruleset) { @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Edit } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, IFrameBasedClock framedClock) + private void load(IBindableBeatmap beatmap, IFrameBasedClock framedClock) { - beatmap.BindTo(osuGame.Beatmap); + this.beatmap.BindTo(beatmap); try { diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 45da628ce8..9b09f0bd6d 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) { - // todo: fix ordering of objects so we don't have to do this (#2740). - foreach (var d in drawables.Reverse().Skip(IncreaseFirstObjectVisibility ? 1 : 0)) + foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility ? 1 : 0)) d.ApplyCustomUpdateState += ApplyHiddenState; } diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index 1941724879..913ba23701 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mods { DifficultyReduction, DifficultyIncrease, - Special, + Special } } diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index 3c90a4eedb..b65773e93f 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -1,6 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Linq; + namespace osu.Game.Rulesets.Mods { public class MultiMod : Mod @@ -10,6 +13,13 @@ namespace osu.Game.Rulesets.Mods public override string Description => string.Empty; public override double ScoreMultiplier => 0; - public Mod[] Mods; + public Mod[] Mods { get; } + + public MultiMod(params Mod[] mods) + { + Mods = mods; + } + + public override Type[] IncompatibleMods => Mods.SelectMany(m => m.IncompatibleMods).ToArray(); } } diff --git a/osu.Game/Rulesets/Mods/NoModMod.cs b/osu.Game/Rulesets/Mods/NoModMod.cs new file mode 100644 index 0000000000..dcab3538da --- /dev/null +++ b/osu.Game/Rulesets/Mods/NoModMod.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Indicates a type of mod that doesn't do anything. + /// + public sealed class NoModMod : Mod + { + public override string Name => "No Mod"; + public override string ShortenedName => "NM"; + public override double ScoreMultiplier => 1; + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 395eeab419..a39e8bb8d4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -15,6 +15,8 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets @@ -69,7 +71,13 @@ namespace osu.Game.Rulesets public abstract string Description { get; } - public virtual SettingsSubsection CreateSettings() => null; + public virtual RulesetSettingsSubsection CreateSettings() => null; + + /// + /// Creates the for this . + /// + /// The to store the settings. + public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null; /// /// Do not override this unless you are a legacy mode. diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs new file mode 100644 index 0000000000..7e83ba0961 --- /dev/null +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2007-2018 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.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; + +namespace osu.Game.Rulesets +{ + /// + /// A cache that provides a single per-ruleset. + /// This is done to support referring to and updating ruleset configs from multiple locations in the absence of inter-config bindings. + /// + public class RulesetConfigCache : Component + { + private readonly Dictionary configCache = new Dictionary(); + private readonly SettingsStore settingsStore; + + public RulesetConfigCache(SettingsStore settingsStore) + { + this.settingsStore = settingsStore; + } + + /// + /// Retrieves the for a . + /// + /// The to retrieve the for. + /// The defined by , null if doesn't define one. + /// If doesn't have a valid . + public IRulesetConfigManager GetConfigFor(Ruleset ruleset) + { + if (ruleset.RulesetInfo.ID == null) + throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); + + if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing)) + return existing; + + return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore); + } + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 345930ed04..dd4120f2fb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt HighestCombo = new BindableInt(); + /// + /// The used to calculate scores. + /// + public readonly Bindable Mode = new Bindable(); + /// /// Whether all s have been processed. /// @@ -169,8 +174,6 @@ namespace osu.Game.Rulesets.Scoring private const double combo_portion = 0.7; private const double max_score = 1000000; - public readonly Bindable Mode = new Bindable(); - protected sealed override bool HasCompleted => JudgedHits == MaxHits; protected int MaxHits { get; private set; } @@ -199,16 +202,18 @@ namespace osu.Game.Rulesets.Scoring if (maxBaseScore == 0 || maxHighestCombo == 0) { - Mode.Value = ScoringMode.Exponential; + Mode.Value = ScoringMode.Classic; Mode.Disabled = true; } + + Mode.ValueChanged += _ => updateScore(); } /// /// Simulates an autoplay of s that will be judged by this /// by adding s for each in the . /// - /// This is required for to work, otherwise will be used. + /// This is required for to work, otherwise will be used. /// /// /// The containing the s that will be judged by this . @@ -295,8 +300,9 @@ namespace osu.Game.Rulesets.Scoring case ScoringMode.Standardised: TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; break; - case ScoringMode.Exponential: - TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2); + case ScoringMode.Classic: + // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) + TotalScore.Value = bonusScore + baseScore * (1 + Math.Max(0, HighestCombo - 1) / 25); break; } } @@ -322,6 +328,6 @@ namespace osu.Game.Rulesets.Scoring public enum ScoringMode { Standardised, - Exponential + Classic } } diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 1b6841c9bd..af18d98561 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.UI { public class HitObjectContainer : CompositeDrawable { - public virtual IEnumerable Objects => InternalChildren.Cast(); - public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast(); + public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); + public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 384b71cccc..15383946e8 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -73,11 +73,6 @@ namespace osu.Game.Rulesets.UI private IRulesetConfigManager rulesetConfig; private OnScreenDisplay onScreenDisplay; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - /// /// A visual representation of a . /// @@ -90,18 +85,20 @@ namespace osu.Game.Rulesets.UI Cursor = CreateCursor(); } - [BackgroundDependencyLoader(true)] - private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings) + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) { - this.onScreenDisplay = onScreenDisplay; + var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - rulesetConfig = CreateConfig(Ruleset, settings); + onScreenDisplay = dependencies.Get(); + rulesetConfig = dependencies.Get().GetConfigFor(Ruleset); if (rulesetConfig != null) { dependencies.Cache(rulesetConfig); onScreenDisplay?.BeginTracking(this, rulesetConfig); } + + return dependencies; } public abstract ScoreProcessor CreateScoreProcessor(); @@ -136,8 +133,6 @@ namespace osu.Game.Rulesets.UI /// protected virtual CursorContainer CreateCursor() => null; - protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null; - /// /// Creates a Playfield. /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index b35616985a..58a66a5224 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -121,6 +121,8 @@ namespace osu.Game.Rulesets.UI /// 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; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 6f86d20295..830214803c 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// The step increase/decrease of the span of time visible by the length of the scrolling axes. /// - private const double time_span_step = 50; + private const double time_span_step = 200; /// /// The span of time that is visible by the length of the scrolling axes. @@ -88,10 +88,10 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (args.Key) { case Key.Minus: - this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint); + this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 600, Easing.OutQuint); break; case Key.Plus: - this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint); + this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 600, Easing.OutQuint); break; } } diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 399f9274a6..caf9ba27ff 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components private const float corner_radius = 5; private const float contents_padding = 15; - public readonly Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); protected Track Track => Beatmap.Value.Track; private readonly Drawable background; @@ -42,8 +42,9 @@ namespace osu.Game.Screens.Edit.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(IBindableBeatmap beatmap, OsuColour colours) { + Beatmap.BindTo(beatmap); background.Colour = colours.Gray1; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index c00e9ac4d5..07d9398d38 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; using OpenTK; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -15,7 +16,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public abstract class TimelinePart : CompositeDrawable { - public Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); private readonly Container timeline; @@ -30,6 +31,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts }; } + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + Beatmap.BindTo(beatmap); + } + private void updateRelativeChildSize() { // the track may not be loaded completely (only has a length once it is). diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 0301870588..77878288f9 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -20,19 +20,17 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary [BackgroundDependencyLoader] private void load(OsuColour colours, IAdjustableClock adjustableClock) { - TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart; - Children = new Drawable[] { - markerPart = new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, - controlPointPart = new ControlPointPart + new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, + new ControlPointPart { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, Height = 0.35f }, - bookmarkPart = new BookmarkPart + new BookmarkPart { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, @@ -67,7 +65,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary }, } }, - breakPart = new BreakPart + new BreakPart { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -75,11 +73,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Height = 0.25f } }; - - markerPart.Beatmap.BindTo(Beatmap); - controlPointPart.Beatmap.BindTo(Beatmap); - bookmarkPart.Beatmap.BindTo(Beatmap); - breakPart.Beatmap.BindTo(Beatmap); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b71d3aee18..d4f66c2f09 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -41,14 +41,14 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); [BackgroundDependencyLoader] private void load(OsuColour colours) { // TODO: should probably be done at a RulesetContainer level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); - clock = new EditorClock(Beatmap, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); @@ -128,9 +128,9 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 10 }, - Child = timeInfo = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, }, - timeline = new SummaryTimeline + new SummaryTimeline { RelativeSizeAxes = Axes.Both, }, @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, - Child = playback = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, } }, } @@ -148,9 +148,6 @@ namespace osu.Game.Screens.Edit }, }; - timeInfo.Beatmap.BindTo(Beatmap); - timeline.Beatmap.BindTo(Beatmap); - playback.Beatmap.BindTo(Beatmap); menuBar.Mode.ValueChanged += onModeChanged; bottomBackground.Colour = colours.Gray2; @@ -178,7 +175,6 @@ namespace osu.Game.Screens.Edit break; } - currentScreen.Beatmap.BindTo(Beatmap); LoadComponentAsync(currentScreen, screenContainer.Add); } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 72fb91e7df..1c40181ec9 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Configuration; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -24,12 +23,12 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor; - public EditorClock(Bindable beatmap, BindableBeatDivisor beatDivisor) + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; - ControlPointInfo = beatmap.Value.Beatmap.ControlPointInfo; - TrackLength = beatmap.Value.Track.Length; + ControlPointInfo = beatmap.Beatmap.ControlPointInfo; + TrackLength = beatmap.Track.Length; } public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index fea4883144..a862485fd6 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose if (beatDivisor != null) this.beatDivisor.BindTo(beatDivisor); - ScrollableTimeline timeline; Children = new Drawable[] { new GridContainer @@ -65,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + Child = new TimelineArea { RelativeSizeAxes = Axes.Both } }, new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } }, @@ -94,8 +93,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose }, }; - timeline.Beatmap.BindTo(Beatmap); - var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); if (ruleset == null) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs deleted file mode 100644 index 72dda24b62..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class BeatmapWaveformGraph : CompositeDrawable - { - public readonly Bindable Beatmap = new Bindable(); - - private readonly WaveformGraph graph; - - public BeatmapWaveformGraph() - { - InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both }; - Beatmap.ValueChanged += b => graph.Waveform = b.Waveform; - } - - /// - /// Gets or sets the . - /// - public float Resolution - { - get { return graph.Resolution; } - set { graph.Resolution = value; } - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs deleted file mode 100644 index 3223c08c1f..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class ScrollableTimeline : CompositeDrawable - { - public readonly Bindable Beatmap = new Bindable(); - - private readonly ScrollingTimelineContainer timelineContainer; - - public ScrollableTimeline() - { - Masking = true; - CornerRadius = 5; - - OsuCheckbox hitObjectsCheckbox; - OsuCheckbox hitSoundsCheckbox; - OsuCheckbox waveformCheckbox; - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("111") - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("222") - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - Width = 160, - Padding = new MarginPadding { Horizontal = 15 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 4), - Children = new[] - { - hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" }, - hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" }, - waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } - } - } - } - }, - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("333") - }, - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new[] - { - new TimelineButton - { - RelativeSizeAxes = Axes.Y, - Height = 0.5f, - Icon = FontAwesome.fa_search_plus, - Action = () => timelineContainer.Zoom++ - }, - new TimelineButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Y, - Height = 0.5f, - Icon = FontAwesome.fa_search_minus, - Action = () => timelineContainer.Zoom-- - }, - } - } - } - }, - timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y } - } - } - }; - - hitObjectsCheckbox.Current.Value = true; - hitSoundsCheckbox.Current.Value = true; - waveformCheckbox.Current.Value = true; - - timelineContainer.Beatmap.BindTo(Beatmap); - timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current); - } - - protected override void Update() - { - base.Update(); - - timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1); - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs deleted file mode 100644 index 2902e74e00..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using OpenTK; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Graphics; - -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline -{ - public class ScrollingTimelineContainer : ScrollContainer - { - public readonly Bindable HitObjectsVisible = new Bindable(); - public readonly Bindable HitSoundsVisible = new Bindable(); - public readonly Bindable WaveformVisible = new Bindable(); - public readonly Bindable Beatmap = new Bindable(); - - private readonly BeatmapWaveformGraph waveform; - - public ScrollingTimelineContainer() - : base(Direction.Horizontal) - { - Masking = true; - - Add(waveform = new BeatmapWaveformGraph - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("222"), - Depth = float.MaxValue - }); - - Content.AutoSizeAxes = Axes.None; - Content.RelativeSizeAxes = Axes.Both; - - waveform.Beatmap.BindTo(Beatmap); - WaveformVisible.ValueChanged += waveformVisibilityChanged; - - Zoom = 10; - } - - private float minZoom = 1; - /// - /// The minimum zoom level allowed. - /// - public float MinZoom - { - get { return minZoom; } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException(nameof(value)); - if (minZoom == value) - return; - minZoom = value; - - // Update the zoom level - Zoom = Zoom; - } - } - - private float maxZoom = 30; - /// - /// The maximum zoom level allowed. - /// - public float MaxZoom - { - get { return maxZoom; } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException(nameof(value)); - if (maxZoom == value) - return; - maxZoom = value; - - // Update the zoom level - Zoom = Zoom; - } - } - - private float zoom = 1; - /// - /// The current zoom level. - /// - public float Zoom - { - get { return zoom; } - set - { - value = MathHelper.Clamp(value, MinZoom, MaxZoom); - if (zoom == value) - return; - zoom = value; - - // Make the zoom target default to the center of the graph if it hasn't been set - if (relativeContentZoomTarget == null) - relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X; - if (localZoomTarget == null) - localZoomTarget = DrawSize.X / 2; - - Content.ResizeWidthTo(Zoom); - - // Update the scroll position to focus on the zoom target - float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value; - ScrollTo(scrollPos, false); - - relativeContentZoomTarget = null; - localZoomTarget = null; - } - } - - /// - /// Zoom target as a relative position in the space. - /// - private float? relativeContentZoomTarget; - - /// - /// Zoom target as a position in our local space. - /// - private float? localZoomTarget; - - protected override bool OnScroll(InputState state) - { - if (!state.Keyboard.ControlPressed) - return base.OnScroll(state); - - relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X; - localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X; - - Zoom += state.Mouse.ScrollDelta.Y; - - return true; - } - - private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs new file mode 100644 index 0000000000..3649b24cd0 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Framework.Graphics.Audio; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class Timeline : ZoomableScrollContainer + { + public readonly Bindable WaveformVisible = new Bindable(); + public readonly IBindable Beatmap = new Bindable(); + + public Timeline() + { + ZoomDuration = 200; + ZoomEasing = Easing.OutQuint; + Zoom = 10; + } + + private WaveformGraph waveform; + + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + Child = waveform = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222"), + Depth = float.MaxValue + }; + + WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); + + Beatmap.BindTo(beatmap); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform); + waveform.Waveform = Beatmap.Value.Waveform; + } + + protected override void Update() + { + base.Update(); + + // We want time = 0 to be at the centre of the container when scrolled to the start + Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 }; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs new file mode 100644 index 0000000000..006317e57e --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs @@ -0,0 +1,128 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class TimelineArea : CompositeDrawable + { + private readonly Timeline timeline; + + public TimelineArea() + { + Masking = true; + CornerRadius = 5; + + OsuCheckbox hitObjectsCheckbox; + OsuCheckbox hitSoundsCheckbox; + OsuCheckbox waveformCheckbox; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("111") + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222") + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + Width = 160, + Padding = new MarginPadding { Horizontal = 15 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 4), + Children = new[] + { + hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" }, + hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" }, + waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("333") + }, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new[] + { + new TimelineButton + { + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Icon = FontAwesome.fa_search_plus, + Action = () => timeline.Zoom++ + }, + new TimelineButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Y, + Height = 0.5f, + Icon = FontAwesome.fa_search_minus, + Action = () => timeline.Zoom-- + }, + } + } + } + }, + timeline = new Timeline { RelativeSizeAxes = Axes.Both } + }, + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed), + } + } + }; + + hitObjectsCheckbox.Current.Value = true; + hitSoundsCheckbox.Current.Value = true; + waveformCheckbox.Current.Value = true; + + timeline.WaveformVisible.BindTo(waveformCheckbox.Current); + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs new file mode 100644 index 0000000000..035e6a0804 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs @@ -0,0 +1,174 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using OpenTK; + +namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +{ + public class ZoomableScrollContainer : ScrollContainer + { + /// + /// The time to zoom into/out of a point. + /// All user scroll input will be overwritten during the zoom transform. + /// + public double ZoomDuration; + + /// + /// The easing with which to transform the zoom. + /// + public Easing ZoomEasing; + + private readonly Container zoomedContent; + protected override Container Content => zoomedContent; + + private float currentZoom = 1; + + public ZoomableScrollContainer() + : base(Direction.Horizontal) + { + base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); + } + + private int minZoom = 1; + + /// + /// The minimum zoom level allowed. + /// + public int MinZoom + { + get => minZoom; + set + { + if (value < 1) + throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value)); + minZoom = value; + + if (Zoom < value) + Zoom = value; + } + } + + private int maxZoom = 60; + + /// + /// The maximum zoom level allowed. + /// + public int MaxZoom + { + get => maxZoom; + set + { + if (value < 1) + throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value)); + maxZoom = value; + + if (Zoom > value) + Zoom = value; + } + } + + /// + /// Gets or sets the content zoom level of this . + /// + public float Zoom + { + get => zoomTarget; + set + { + value = MathHelper.Clamp(value, MinZoom, MaxZoom); + + if (IsLoaded) + setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); + else + currentZoom = zoomTarget = value; + } + } + + protected override void Update() + { + base.Update(); + + zoomedContent.Width = DrawWidth * currentZoom; + } + + protected override bool OnScroll(InputState state) + { + if (!state.Keyboard.ControlPressed) + return base.OnScroll(state); + + setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.X, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X); + return true; + } + + private float zoomTarget = 1; + private void setZoomTarget(float newZoom, float focusPoint) + { + zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom); + transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + } + + private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None) + => this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing)); + + private class TransformZoom : Transform + { + /// + /// The focus point in absolute coordinates local to the content. + /// + private readonly float focusPoint; + + /// + /// The size of the content. + /// + private readonly float contentSize; + + /// + /// The scroll offset at the start of the transform. + /// + private readonly float scrollOffset; + + /// + /// Transforms to a new value. + /// + /// The focus point in absolute coordinates local to the content. + /// The size of the content. + /// The scroll offset at the start of the transform. + public TransformZoom(float focusPoint, float contentSize, float scrollOffset) + { + this.focusPoint = focusPoint; + this.contentSize = contentSize; + this.scrollOffset = scrollOffset; + } + + public override string TargetMember => nameof(currentZoom); + + private float valueAt(double time) + { + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + + protected override void Apply(ZoomableScrollContainer d, double time) + { + float newZoom = valueAt(time); + + float focusOffset = focusPoint - scrollOffset; + float expectedWidth = d.DrawWidth * newZoom; + float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; + + d.currentZoom = newZoom; + d.ScrollTo(targetOffset, false); + } + + protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index f70c462cd8..f8402b9a9f 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // 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.Framework.Graphics.Containers; @@ -13,7 +14,7 @@ namespace osu.Game.Screens.Edit.Screens /// public class EditorScreen : Container { - public readonly Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); protected override Container Content => content; private readonly Container content; @@ -27,6 +28,12 @@ namespace osu.Game.Screens.Edit.Screens InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; } + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + Beatmap.BindTo(beatmap); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 42e25aad43..81abc4cd3d 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -326,7 +326,10 @@ namespace osu.Game.Screens.Menu logoTracking = false; if (game != null) - game.OverlayActivationMode.Value = state == MenuState.Exit ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + { + game.OverlayActivationMode.Value = state == MenuState.Exit ? OverlayActivation.Disabled : OverlayActivation.All; + game.Toolbar.Hide(); + } logo.ClearTransforms(targetMember: nameof(Position)); logo.RelativePositionAxes = Axes.Both; diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index c5bd345a31..c1032011f7 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Menu /// public bool DidLoadMenu; + private readonly Bindable beatmap = new Bindable(); + private MainMenu mainMenu; private SampleChannel welcome; private SampleChannel seeya; @@ -42,11 +44,13 @@ namespace osu.Game.Screens.Menu private Bindable menuVoice; private Bindable menuMusic; private Track track; - private WorkingBeatmap beatmap; + private WorkingBeatmap introBeatmap; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game, BindableBeatmap beatmap) { + this.beatmap.BindTo(beatmap); + menuVoice = config.GetBindable(OsuSetting.MenuVoice); menuMusic = config.GetBindable(OsuSetting.MenuMusic); @@ -73,8 +77,8 @@ namespace osu.Game.Screens.Menu } } - beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - track = beatmap.Track; + introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + track = introBeatmap.Track; welcome = audio.Sample.Get(@"welcome"); seeya = audio.Sample.Get(@"seeya"); @@ -91,7 +95,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { - Game.Beatmap.Value = beatmap; + beatmap.Value = introBeatmap; if (menuVoice) welcome.Play(); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 1f2cb915b3..fb6130fa36 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Menu { public class LogoVisualisation : Drawable, IHasAccentColour { - private readonly Bindable beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); /// /// The number of bars to jump each update iteration. @@ -78,9 +78,9 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, OsuGameBase game) + private void load(ShaderManager shaders, IBindableBeatmap beatmap) { - beatmap.BindTo(game.Beatmap); + this.beatmap.BindTo(beatmap); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index c321c98b24..7d46aad089 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Menu public override bool HandleKeyboardInput => false; public override bool HandleMouseInput => false; - private readonly Bindable beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); private Box leftBox; private Box rightBox; @@ -45,9 +45,9 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours) + private void load(IBindableBeatmap beatmap, OsuColour colours) { - beatmap.BindTo(game.Beatmap); + this.beatmap.BindTo(beatmap); // linear colour looks better in this case, so let's use it for now. Color4 gradientDark = colours.Blue.Opacity(0).ToLinear(); diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index d98aac8f84..61018f9e08 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -77,30 +77,15 @@ namespace osu.Game.Screens private ParallaxContainer backgroundParallaxContainer; - public WorkingBeatmap InitialBeatmap - { - set - { - if (IsLoaded) throw new InvalidOperationException($"Cannot set {nameof(InitialBeatmap)} post-load."); - Beatmap.Value = value; - } - } - protected readonly Bindable Ruleset = new Bindable(); private SampleChannel sampleExit; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuGameBase game, OsuGame osuGame, AudioManager audio) + [BackgroundDependencyLoader(true)] + private void load(BindableBeatmap beatmap, OsuGame osuGame, AudioManager audio) { - if (game != null) - { - //if we were given a beatmap at ctor time, we want to pass this on to the game-wide beatmap. - var localMap = Beatmap.Value; - Beatmap.BindTo(game.Beatmap); - if (localMap != null) - Beatmap.Value = localMap; - } + if (beatmap != null) + Beatmap.BindTo(beatmap); if (osuGame != null) { diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 8cbb9986e5..114ea83ba6 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -18,7 +18,8 @@ namespace osu.Game.Screens.Play { private const int duration = 100; - private Bindable showKeyCounter; + public readonly Bindable Visible = new Bindable(true); + private readonly Bindable configVisibility = new Bindable(); public KeyCounterCollection() { @@ -46,9 +47,10 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - showKeyCounter = config.GetBindable(OsuSetting.KeyOverlay); - showKeyCounter.ValueChanged += keyCounterVisibility => this.FadeTo(keyCounterVisibility ? 1 : 0, duration); - showKeyCounter.TriggerChange(); + config.BindWith(OsuSetting.KeyOverlay, configVisibility); + + Visible.BindValueChanged(_ => updateVisibility()); + configVisibility.BindValueChanged(_ => updateVisibility(), true); } //further: change default values here and in KeyCounter if needed, instead of passing them in every constructor @@ -111,6 +113,8 @@ namespace osu.Game.Screens.Play } } + private void updateVisibility() => this.FadeTo(Visible.Value || configVisibility.Value ? 1 : 0, duration); + public override bool HandleKeyboardInput => receptor == null; public override bool HandleMouseInput => receptor == null; diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index bd334ad2e2..6262f71ddc 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -44,13 +44,18 @@ namespace osu.Game.Screens.Play public Action OnResume; public Action OnPause; - private readonly IAdjustableClock adjustableClock; private readonly FramedClock framedClock; + private readonly DecoupleableInterpolatingFramedClock decoupledClock; - public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock) + /// + /// Creates a new . + /// + /// The gameplay clock. This is the clock that will process frames. + /// The seekable clock. This is the clock that will be paused and resumed. + public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock) { this.framedClock = framedClock; - this.adjustableClock = adjustableClock; + this.decoupledClock = decoupledClock; RelativeSizeAxes = Axes.Both; @@ -80,7 +85,7 @@ namespace osu.Game.Screens.Play if (IsPaused) return; // stop the seekable clock (stops the audio eventually) - adjustableClock.Stop(); + decoupledClock.Stop(); IsPaused = true; OnPause?.Invoke(); @@ -97,10 +102,10 @@ namespace osu.Game.Screens.Play IsResuming = false; lastPauseActionTime = Time.Current; - // seek back to the time of the framed clock. - // this accounts for the audio clock potentially taking time to enter a completely stopped state. - adjustableClock.Seek(framedClock.CurrentTime); - adjustableClock.Start(); + // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time + // This accounts for the audio clock source potentially taking time to enter a completely stopped state + decoupledClock.Seek(decoupledClock.CurrentTime); + decoupledClock.Start(); OnResume?.Invoke(); pauseOverlay.Hide(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 54f65e3991..a2ed01f5a7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -120,7 +121,7 @@ namespace osu.Game.Screens.Play // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap); + RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value); } if (!RulesetContainer.Objects.Any()) @@ -146,13 +147,18 @@ namespace osu.Game.Screens.Play adjustableClock.ProcessFrame(); + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; + // the final usable gameplay clock with user-set offsets applied. - var offsetClock = new FramedOffsetClock(adjustableClock); + var offsetClock = new FramedOffsetClock(platformOffsetClock); userAudioOffset.ValueChanged += v => offsetClock.Offset = v; userAudioOffset.TriggerChange(); ScoreProcessor = RulesetContainer.CreateScoreProcessor(); + config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); Children = new Drawable[] { @@ -223,6 +229,7 @@ namespace osu.Game.Screens.Play }; hudOverlay.HoldToQuit.Action = Exit; + hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); if (ShowStoryboard) initializeStoryboard(false); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 2d5bc889c3..6e1c3d24f0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - Add(info = new BeatmapMetadataDisplay(Beatmap) + Add(info = new BeatmapMetadataDisplay(Beatmap.Value) { Alpha = 0, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 1ccc5e2fe8..7f18305b1c 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Play { public abstract class ScreenWithBeatmapBackground : OsuScreen { - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); public override bool AllowBeatmapRulesetChange => false; diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 7cbd2e4403..56a270f559 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking private static readonly Vector2 background_blur = new Vector2(20); - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); private const float overscan = 1.3f; @@ -274,10 +274,10 @@ namespace osu.Game.Screens.Ranking switch (mode) { case ResultMode.Summary: - currentPage = new ResultsPageScore(score, Beatmap); + currentPage = new ResultsPageScore(score, Beatmap.Value); break; case ResultMode.Ranking: - currentPage = new ResultsPageRanking(score, Beatmap); + currentPage = new ResultsPageRanking(score, Beatmap.Value); break; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41ba38cb0f..70b473bcd9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -63,7 +63,8 @@ namespace osu.Game.Screens.Select private SampleChannel sampleChangeBeatmap; private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); protected SongSelect() { @@ -175,7 +176,7 @@ namespace osu.Game.Screens.Select } } - [BackgroundDependencyLoader(permitNulls: true)] + [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { dependencies.CacheAs(this); @@ -206,15 +207,13 @@ namespace osu.Game.Screens.Select Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSetsEnumerable(); - Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; - Beatmap.TriggerChange(); - - Beatmap.ValueChanged += workingBeatmapChanged; + Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); + Beatmap.BindValueChanged(workingBeatmapChanged); } public void Edit(BeatmapInfo beatmap) { - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); Push(new Editor()); } @@ -280,7 +279,7 @@ namespace osu.Game.Screens.Select { bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); ensurePlayingSelected(preview); } @@ -367,7 +366,7 @@ namespace osu.Game.Screens.Select { if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { - UpdateBeatmap(Beatmap); + UpdateBeatmap(Beatmap.Value); ensurePlayingSelected(); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 135b5e2b1e..d15f3053a3 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using System.Linq; +using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { @@ -63,14 +64,14 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(OsuGameBase game, TextureStore textureStore) + private void load(IBindableBeatmap beatmap, TextureStore textureStore) { var basePath = Animation.Path.ToLowerInvariant(); for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = basePath.Replace(".", frame + "."); - var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 9f22bebcc2..efbb3de253 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using System.Linq; +using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { @@ -62,10 +63,10 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(OsuGameBase game, TextureStore textureStore) + private void load(IBindableBeatmap beatmap, TextureStore textureStore) { var spritePath = Sprite.Path.ToLowerInvariant(); - var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index 1ce7023be0..08dc6a3bbd 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -20,29 +20,19 @@ namespace osu.Game.Tests.Visual protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected readonly EditorClock Clock; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - private OsuGameBase osuGame; - protected EditorClockTestCase() { Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - this.osuGame = osuGame; + Dependencies.Cache(BeatDivisor); + Dependencies.CacheAs(Clock); + Dependencies.CacheAs(Clock); - dependencies.Cache(BeatDivisor); - dependencies.CacheAs(Clock); - dependencies.CacheAs(Clock); - - osuGame.Beatmap.ValueChanged += beatmapChanged; - beatmapChanged(osuGame.Beatmap.Value); + Beatmap.BindValueChanged(beatmapChanged, true); } private void beatmapChanged(WorkingBeatmap working) @@ -68,12 +58,5 @@ namespace osu.Game.Tests.Visual return true; } - - protected override void Dispose(bool isDisposing) - { - osuGame.Beatmap.ValueChanged -= beatmapChanged; - - base.Dispose(isDisposing); - } } } diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs index c0b72f56a4..2ab121fcc9 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo); + Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo); LoadComponentAsync(new Editor(), LoadScreen); } diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index fa441d8012..1740658da6 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -1,12 +1,47 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Testing; +using osu.Game.Beatmaps; namespace osu.Game.Tests.Visual { public abstract class OsuTestCase : TestCase { + private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); + protected BindableBeatmap Beatmap => beatmap; + + protected DependencyContainer Dependencies { get; private set; } + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + { + Dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + Dependencies.CacheAs(beatmap); + Dependencies.CacheAs(beatmap); + + return Dependencies; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audioManager) + { + beatmap.SetAudioManager(audioManager); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap != null) + { + beatmap.Disabled = true; + beatmap.Value.Track.Stop(); + } + } + protected override ITestCaseTestRunner CreateRunner() => new OsuTestCaseTestRunner(); public class OsuTestCaseTestRunner : OsuGameBase, ITestCaseTestRunner @@ -23,5 +58,22 @@ namespace osu.Game.Tests.Visual public void RunTestBlocking(TestCase test) => runner.RunTestBlocking(test); } + + private class OsuTestBeatmap : BindableBeatmap + { + public OsuTestBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + + public void SetAudioManager(AudioManager audioManager) => RegisterAudioManager(audioManager); + + public override BindableBeatmap GetBoundCopy() + { + var copy = new OsuTestBeatmap(Default); + copy.BindTo(this); + return copy; + } + } } } diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index 51460ecb6d..dfae8fbc1d 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual new ScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new BeatmapList(ruleset) + Child = new BeatmapList(ruleset, Beatmap) } } }, @@ -108,10 +108,12 @@ namespace osu.Game.Tests.Visual { private readonly Container beatmapDisplays; private readonly Ruleset ruleset; + private readonly BindableBeatmap beatmapBindable; - public BeatmapList(Ruleset ruleset) + public BeatmapList(Ruleset ruleset, BindableBeatmap beatmapBindable) { this.ruleset = ruleset; + this.beatmapBindable = beatmapBindable; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -130,7 +132,7 @@ namespace osu.Game.Tests.Visual var sets = beatmaps.GetAllUsableBeatmapSets(); var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID == null || b.RulesetID == ruleset.LegacyID); - allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b))); + allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b, beatmapBindable))); } private class BeatmapDisplay : CompositeDrawable, IHasTooltip @@ -138,27 +140,47 @@ namespace osu.Game.Tests.Visual private readonly OsuSpriteText text; private readonly BeatmapInfo beatmap; + private readonly BindableBeatmap beatmapBindable; + private BeatmapManager beatmaps; - private OsuGameBase osuGame; private bool isSelected; public string TooltipText => text.Text; - public BeatmapDisplay(BeatmapInfo beatmap) + public BeatmapDisplay(BeatmapInfo beatmap, BindableBeatmap beatmapBindable) { this.beatmap = beatmap; + this.beatmapBindable = beatmapBindable; AutoSizeAxes = Axes.Both; InternalChild = text = new OsuSpriteText(); + + this.beatmapBindable.ValueChanged += beatmapChanged; + } + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + + var working = beatmaps.GetWorkingBeatmap(beatmap); + text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]"; + } + + private void beatmapChanged(WorkingBeatmap newBeatmap) + { + if (isSelected) + this.FadeColour(Color4.White, 100); + isSelected = false; } protected override bool OnClick(InputState state) { - if (osuGame.Beatmap.Value.BeatmapInfo.ID == beatmap.ID) + if (beatmapBindable.Value.BeatmapInfo.ID == beatmap.ID) return false; - osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); + beatmapBindable.Value = beatmaps.GetWorkingBeatmap(beatmap); isSelected = true; return true; } @@ -177,25 +199,6 @@ namespace osu.Game.Tests.Visual return; this.FadeColour(Color4.White, 100); } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, BeatmapManager beatmaps) - { - this.osuGame = osuGame; - this.beatmaps = beatmaps; - - var working = beatmaps.GetWorkingBeatmap(beatmap); - text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]"; - - osuGame.Beatmap.ValueChanged += beatmapChanged; - } - - private void beatmapChanged(WorkingBeatmap newBeatmap) - { - if (isSelected) - this.FadeColour(Color4.White, 100); - isSelected = false; - } } } @@ -204,7 +207,7 @@ namespace osu.Game.Tests.Visual private readonly FillFlowContainer scores; private APIAccess api; - private readonly Bindable currentBeatmap = new Bindable(); + private readonly IBindable currentBeatmap = new Bindable(); public PerformanceList() { @@ -220,7 +223,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, APIAccess api) + private void load(IBindableBeatmap beatmap, APIAccess api) { this.api = api; @@ -235,7 +238,7 @@ namespace osu.Game.Tests.Visual } currentBeatmap.ValueChanged += beatmapChanged; - currentBeatmap.BindTo(osuGame.Beatmap); + currentBeatmap.BindTo(beatmap); } private GetScoresRequest lastRequest; @@ -333,9 +336,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load(IBindableBeatmap beatmap) { - osuGame.Beatmap.ValueChanged += beatmapChanged; + beatmap.ValueChanged += beatmapChanged; } private Cached informationCache = new Cached(); diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index bf6236e4d5..3cdc496ee1 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -19,8 +19,6 @@ namespace osu.Game.Tests.Visual protected Player Player; - private TestWorkingBeatmap working; - protected TestCasePlayer(Ruleset ruleset) { this.ruleset = ruleset; @@ -67,13 +65,13 @@ namespace osu.Game.Tests.Visual { var beatmap = CreateBeatmap(r); - working = new TestWorkingBeatmap(beatmap); - working.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + Beatmap.Value = new TestWorkingBeatmap(beatmap); + Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; if (Player != null) Remove(Player); - var player = CreatePlayer(working, r); + var player = CreatePlayer(r); LoadComponentAsync(player, LoadScreen); @@ -84,14 +82,12 @@ namespace osu.Game.Tests.Visual { base.Update(); - if (working != null) - // note that this will override any mod rate application - working.Track.Rate = Clock.Rate; + // note that this will override any mod rate application + Beatmap.Value.Track.Rate = Clock.Rate; } - protected virtual Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) => new Player + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player { - InitialBeatmap = beatmap, AllowPause = false, AllowLeadIn = false, AllowResults = false, diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2300ba6a72..d87e190352 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,12 +15,12 @@ - - - - + + + + - + \ No newline at end of file diff --git a/osu.TestProject.props b/osu.TestProject.props index b51ca13ed5..8f7128f8b7 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -11,9 +11,9 @@ - + - + @@ -25,4 +25,4 @@ false - \ No newline at end of file +