diff --git a/.vscode/launch.json b/.vscode/launch.json
index b8c026d891..506915f462 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,13 +1,13 @@
{
"version": "0.2.0",
"configurations": [{
- "name": "osu! (VisualTests)",
+ "name": "osu! VisualTests (Debug)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
- "program": "${workspaceRoot}/osu.Game/bin/Debug/osu!.exe",
+ "program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe",
"args": [
"--tests"
],
@@ -18,13 +18,30 @@
"console": "internalConsole"
},
{
- "name": "osu! (debug)",
+ "name": "osu! VisualTests (Release)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
- "program": "${workspaceRoot}/osu.Game/bin/Debug/osu!.exe",
+ "program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe",
+ "args": [
+ "--tests"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Release)",
+ "runtimeExecutable": null,
+ "env": {},
+ "console": "internalConsole"
+ },
+ {
+ "name": "osu! (Debug)",
+ "windows": {
+ "type": "clr"
+ },
+ "type": "mono",
+ "request": "launch",
+ "program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
"runtimeExecutable": null,
@@ -32,13 +49,13 @@
"console": "internalConsole"
},
{
- "name": "osu! (release)",
+ "name": "osu! (Release)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
- "program": "${workspaceRoot}/osu.Game/bin/Release/osu!.exe",
+ "program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
"runtimeExecutable": null,
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 3db43ca9bb..35bf9e7a0e 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -23,6 +23,7 @@
},
{
"taskName": "Build (Release)",
+ "group": "build",
"args": [
"/property:Configuration=Release"
],
diff --git a/appveyor.yml b/appveyor.yml
index 21c15724e6..9048428590 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,6 +1,7 @@
# 2017-09-14
clone_depth: 1
version: '{branch}-{build}'
+image: Visual Studio 2017
configuration: Debug
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
@@ -11,7 +12,7 @@ install:
- cmd: git submodule update --init --recursive
- cmd: choco install resharper-clt -y
- cmd: choco install nvika -y
- - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.2/CodeFileSanity.exe
+ - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe
before_build:
- cmd: CodeFileSanity.exe
- cmd: nuget restore
diff --git a/osu-framework b/osu-framework
index cdb031c3a8..383a8da7bc 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit cdb031c3a8ef693cd71458c5e19c68127ab72938
+Subproject commit 383a8da7bc45af498288b4b72c72a048a0996e74
diff --git a/osu.Desktop/OpenTK.dll.config b/osu.Desktop/OpenTK.dll.config
new file mode 100644
index 0000000000..5620e3d9e2
--- /dev/null
+++ b/osu.Desktop/OpenTK.dll.config
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Game/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
similarity index 76%
rename from osu.Game/OsuGameDesktop.cs
rename to osu.Desktop/OsuGameDesktop.cs
index 47e64a0d5b..1e4bf3119d 100644
--- a/osu.Game/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -7,14 +7,15 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
-using System.Windows.Forms;
using Microsoft.Win32;
+using osu.Desktop.Overlays;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
-using osu.Game.Overlays;
+using osu.Game;
using osu.Game.Screens.Menu;
+using OpenTK.Input;
-namespace osu.Game
+namespace osu.Desktop
{
internal class OsuGameDesktop : OsuGame
{
@@ -104,16 +105,13 @@ namespace osu.Game
desktopWindow.Icon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
desktopWindow.Title = Name;
- desktopWindow.DragEnter += dragEnter;
- desktopWindow.DragDrop += dragDrop;
+ desktopWindow.FileDrop += fileDrop;
}
}
- private void dragDrop(DragEventArgs e)
+ private void fileDrop(object sender, FileDropEventArgs e)
{
- // this method will only be executed if e.Effect in dragEnter gets set to something other that None.
- var dropData = (object[])e.Data.GetData(DataFormats.FileDrop);
- var filePaths = dropData.Select(f => f.ToString()).ToArray();
+ var filePaths = new [] { e.FileName };
if (filePaths.All(f => Path.GetExtension(f) == @".osz"))
Task.Run(() => BeatmapManager.Import(filePaths));
@@ -126,16 +124,5 @@ namespace osu.Game
}
private static readonly string[] allowed_extensions = { @".osz", @".osr" };
-
- private void dragEnter(DragEventArgs e)
- {
- // dragDrop will only be executed if e.Effect gets set to something other that None in this method.
- bool isFile = e.Data.GetDataPresent(DataFormats.FileDrop);
- if (isFile)
- {
- var paths = ((object[])e.Data.GetData(DataFormats.FileDrop)).Select(f => f.ToString()).ToArray();
- e.Effect = allowed_extensions.Any(ext => paths.All(p => p.EndsWith(ext))) ? DragDropEffects.Copy : DragDropEffects.None;
- }
- }
}
}
diff --git a/osu.Game/OsuTestBrowser.cs b/osu.Desktop/OsuTestBrowser.cs
similarity index 92%
rename from osu.Game/OsuTestBrowser.cs
rename to osu.Desktop/OsuTestBrowser.cs
index b0864e441f..23617de1c0 100644
--- a/osu.Game/OsuTestBrowser.cs
+++ b/osu.Desktop/OsuTestBrowser.cs
@@ -3,9 +3,10 @@
using osu.Framework.Platform;
using osu.Framework.Testing;
+using osu.Game;
using osu.Game.Screens.Backgrounds;
-namespace osu.Game
+namespace osu.Desktop
{
internal class OsuTestBrowser : OsuGameBase
{
diff --git a/osu.Game/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
similarity index 96%
rename from osu.Game/Overlays/VersionManager.cs
rename to osu.Desktop/Overlays/VersionManager.cs
index 7b0b3520cb..e7c3370354 100644
--- a/osu.Game/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -13,15 +13,17 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
+using osu.Game;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using OpenTK;
using OpenTK.Graphics;
using Squirrel;
-namespace osu.Game.Overlays
+namespace osu.Desktop.Overlays
{
public class VersionManager : OverlayContainer
{
diff --git a/osu.Game/Program.cs b/osu.Desktop/Program.cs
similarity index 95%
rename from osu.Game/Program.cs
rename to osu.Desktop/Program.cs
index 8044e9fa87..720b38144c 100644
--- a/osu.Game/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -8,7 +8,7 @@ using osu.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
-namespace osu.Game
+namespace osu.Desktop
{
public static class Program
{
diff --git a/osu.Desktop/Properties/AssemblyInfo.cs b/osu.Desktop/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2ed304ebd7
--- /dev/null
+++ b/osu.Desktop/Properties/AssemblyInfo.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("osu!lazer")]
+[assembly: AssemblyDescription("click the circles. to the beat.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("ppy Pty Ltd")]
+[assembly: AssemblyProduct("osu!lazer")]
+[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("b0cb1d48-e4c2-4612-a347-beea7b1a71e7")]
+
+[assembly: AssemblyVersion("0.0.0")]
+[assembly: AssemblyFileVersion("0.0.0")]
diff --git a/osu.Game/Properties/app.manifest b/osu.Desktop/Properties/app.manifest
similarity index 100%
rename from osu.Game/Properties/app.manifest
rename to osu.Desktop/Properties/app.manifest
diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config
new file mode 100644
index 0000000000..0841541f3d
--- /dev/null
+++ b/osu.Desktop/app.config
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Game/lazer.ico b/osu.Desktop/lazer.ico
similarity index 100%
rename from osu.Game/lazer.ico
rename to osu.Desktop/lazer.ico
diff --git a/osu.Desktop/osu!.res b/osu.Desktop/osu!.res
new file mode 100644
index 0000000000..7c70e30401
Binary files /dev/null and b/osu.Desktop/osu!.res differ
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
new file mode 100644
index 0000000000..fb1ca7eb10
--- /dev/null
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -0,0 +1,261 @@
+
+
+
+ {419659FD-72EA-4678-9EB8-B22A746CED70}
+ Debug
+ AnyCPU
+ WinExe
+ Properties
+ osu.Desktop
+ osu!
+ 3CF060CD28877D0E3112948951A64B2A7CEEC909
+ codesigning.pfx
+ false
+ false
+ false
+
+
+ 3.5
+
+
+ osu.Desktop.Program
+ OnOutputUpdated
+ false
+ LocalIntranet
+ v4.6.1
+ true
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 2
+ 1.0.0.%2a
+ false
+ true
+ 12.0.0
+ 2.0
+
+
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG
+ prompt
+ 0
+ true
+ false
+ AnyCPU
+ true
+ false
+ false
+ false
+
+
+ 6
+
+
+ none
+ true
+ bin\Release\
+ CuttingEdge NoUpdate
+ prompt
+ 4
+ true
+ false
+ AnyCPU
+ true
+ false
+ false
+
+
+
+
+
+
+ lazer.ico
+
+
+ Properties\app.manifest
+
+
+ true
+ bin\Debug\
+ DEBUG
+ true
+ 0
+ true
+ full
+ AnyCPU
+ false
+ 6
+ prompt
+ --tests
+
+
+
+ $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.dll
+ True
+
+
+ $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.MsDelta.dll
+ True
+
+
+ $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll
+ True
+
+
+ $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll
+ True
+
+
+ $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll
+ True
+
+
+ $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll
+ True
+
+
+ $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll
+ True
+
+
+
+ ..\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll
+ True
+
+
+ ..\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll
+ True
+
+
+ ..\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll
+ True
+
+
+ $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll
+ True
+
+
+ ..\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll
+ True
+
+
+
+
+
+ ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll
+ True
+
+
+
+
+
+ osu.licenseheader
+
+
+
+
+
+
+
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 2.0 %28x86%29
+ true
+
+
+ False
+ .NET Framework 3.0 %28x86%29
+ false
+
+
+ False
+ .NET Framework 3.5
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {c76bf5b3-985e-4d39-95fe-97c9c879b83a}
+ osu.Framework
+
+
+ {d9a367c9-4c1a-489f-9b05-a0cea2b53b58}
+ osu.Game.Resources
+
+
+ {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
+ osu.Game.Rulesets.Catch
+
+
+ {48f4582b-7687-4621-9cbe-5c24197cb536}
+ osu.Game.Rulesets.Mania
+
+
+ {c92a607b-1fdd-4954-9f92-03ff547d9080}
+ osu.Game.Rulesets.Osu
+
+
+ {f167e17a-7de6-4af5-b920-a5112296c695}
+ osu.Game.Rulesets.Taiko
+
+
+ {2a66dd92-adb1-4994-89e2-c94e04acda0d}
+ osu.Game
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
new file mode 100644
index 0000000000..4c529f57e5
--- /dev/null
+++ b/osu.Desktop/osu.nuspec
@@ -0,0 +1,26 @@
+
+
+
+ osulazer
+ 0.0.0
+ osulazer
+ ppy Pty Ltd
+ Dean Herbert
+ https://osu.ppy.sh/
+ https://puu.sh/tYyXZ/9a01a5d1b0.ico
+ false
+ click the circles. to the beat.
+ click the circles.
+ testing
+ Copyright ppy Pty Ltd 2007-2017
+ en-AU
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config
new file mode 100644
index 0000000000..80eb533644
--- /dev/null
+++ b/osu.Desktop/packages.config
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 2efb0c0707..0e4935aa7a 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -5,9 +5,9 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
using System;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -17,14 +17,37 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
{
- if (!(obj is IHasXPosition))
+ var curveData = obj as IHasCurve;
+ var positionData = obj as IHasPosition;
+ var comboData = obj as IHasCombo;
+
+ if (positionData == null)
yield break;
+ if (curveData != null)
+ {
+ yield return new JuiceStream
+ {
+ StartTime = obj.StartTime,
+ Samples = obj.Samples,
+ ControlPoints = curveData.ControlPoints,
+ CurveType = curveData.CurveType,
+ Distance = curveData.Distance,
+ RepeatSamples = curveData.RepeatSamples,
+ RepeatCount = curveData.RepeatCount,
+ X = positionData.X / CatchPlayfield.BASE_WIDTH,
+ NewCombo = comboData?.NewCombo ?? false
+ };
+
+ yield break;
+ }
+
yield return new Fruit
{
StartTime = obj.StartTime,
- NewCombo = (obj as IHasCombo)?.NewCombo ?? false,
- X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X
+ Samples = obj.Samples,
+ NewCombo = comboData?.NewCombo ?? false,
+ X = positionData.X / CatchPlayfield.BASE_WIDTH
};
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
new file mode 100644
index 0000000000..e057bf3d8e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -0,0 +1,60 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public abstract class DrawableCatchHitObject : DrawableCatchHitObject
+ where TObject : CatchBaseHit
+ {
+ public new TObject HitObject;
+
+ protected DrawableCatchHitObject(TObject hitObject)
+ : base(hitObject)
+ {
+ HitObject = hitObject;
+ }
+ }
+
+ public abstract class DrawableCatchHitObject : DrawableScrollingHitObject
+ {
+ protected DrawableCatchHitObject(CatchBaseHit hitObject)
+ : base(hitObject)
+ {
+ RelativePositionAxes = Axes.Both;
+ X = hitObject.X;
+ Y = (float)HitObject.StartTime;
+ }
+
+ public Func CheckPosition;
+
+ protected override void CheckForJudgements(bool userTriggered, double timeOffset)
+ {
+ if (timeOffset > 0)
+ AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss });
+ }
+
+ private const float preempt = 1000;
+
+ protected override void UpdateState(ArmedState state)
+ {
+ using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
+ {
+ // animation
+ this.FadeIn(200);
+ }
+
+ switch (state)
+ {
+ case ArmedState.Miss:
+ using (BeginAbsoluteSequence(HitObject.StartTime, true))
+ this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
new file mode 100644
index 0000000000..2b2a8e7f8d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public class DrawableDroplet : DrawableCatchHitObject
+ {
+ public DrawableDroplet(Droplet h)
+ : base(h)
+ {
+ Origin = Anchor.Centre;
+
+ Size = new Vector2(Pulp.PULP_SIZE);
+
+ AccentColour = h.ComboColour;
+ Masking = false;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new Pulp
+ {
+ AccentColour = AccentColour,
+ Scale = new Vector2(0.8f),
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index e0c9f0c028..4c28a9d021 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -1,72 +1,29 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
-using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
- public class DrawableFruit : DrawableScrollingHitObject
+ public class DrawableFruit : DrawableCatchHitObject
{
- private const float pulp_size = 20;
-
- private class Pulp : Circle, IHasAccentColour
- {
- public Pulp()
- {
- Size = new Vector2(pulp_size);
-
- Blending = BlendingMode.Additive;
- Colour = Color4.White.Opacity(0.9f);
- }
-
- private Color4 accentColour;
- public Color4 AccentColour
- {
- get { return accentColour; }
- set
- {
- accentColour = value;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Lighten(100),
- };
- }
- }
- }
-
-
- public DrawableFruit(CatchBaseHit h)
+ public DrawableFruit(Fruit h)
: base(h)
{
Origin = Anchor.Centre;
- Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f);
-
- RelativePositionAxes = Axes.Both;
- X = h.X;
+ Size = new Vector2(Pulp.PULP_SIZE * 2.2f, Pulp.PULP_SIZE * 2.8f);
AccentColour = HitObject.ComboColour;
-
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
}
- public Func CheckPosition;
-
[BackgroundDependencyLoader]
private void load()
{
@@ -114,30 +71,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
}
};
}
-
- private const float preempt = 1000;
-
- protected override void CheckForJudgements(bool userTriggered, double timeOffset)
- {
- if (timeOffset > 0)
- AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss });
- }
-
- protected override void UpdateState(ArmedState state)
- {
- using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
- {
- // animation
- this.FadeIn(200);
- }
-
- switch (state)
- {
- case ArmedState.Miss:
- using (BeginAbsoluteSequence(HitObject.StartTime, true))
- this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
- break;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
new file mode 100644
index 0000000000..afda91d0b4
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
@@ -0,0 +1,54 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using OpenTK;
+using osu.Game.Rulesets.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable
+{
+ public class DrawableJuiceStream : DrawableCatchHitObject
+ {
+ private readonly Container dropletContainer;
+
+ public DrawableJuiceStream(JuiceStream s) : base(s)
+ {
+ RelativeSizeAxes = Axes.Both;
+ Height = (float)HitObject.Duration;
+ X = 0;
+
+ Child = dropletContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
+ RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
+ };
+
+ foreach (CatchBaseHit tick in s.Ticks)
+ {
+ TinyDroplet tiny = tick as TinyDroplet;
+ if (tiny != null)
+ {
+ AddNested(new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) });
+ continue;
+ }
+
+ Droplet droplet = tick as Droplet;
+ if (droplet != null)
+ AddNested(new DrawableDroplet(droplet));
+
+ Fruit fruit = tick as Fruit;
+ if (fruit != null)
+ AddNested(new DrawableFruit(fruit));
+ }
+ }
+
+ protected override void AddNested(DrawableHitObject h)
+ {
+ ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
+ dropletContainer.Add(h);
+ base.AddNested(h);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
new file mode 100644
index 0000000000..00ddd365e3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
+{
+ public class Pulp : Circle, IHasAccentColour
+ {
+ public const float PULP_SIZE = 20;
+
+ public Pulp()
+ {
+ Size = new Vector2(PULP_SIZE);
+
+ Blending = BlendingMode.Additive;
+ Colour = Color4.White.Opacity(0.9f);
+ }
+
+ private Color4 accentColour;
+ public Color4 AccentColour
+ {
+ get { return accentColour; }
+ set
+ {
+ accentColour = value;
+
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Lighten(100),
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
new file mode 100644
index 0000000000..6462f6f6a8
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -0,0 +1,169 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using OpenTK;
+using osu.Framework.Lists;
+
+namespace osu.Game.Rulesets.Catch.Objects
+{
+ public class JuiceStream : CatchBaseHit, IHasCurve
+ {
+ ///
+ /// Positional distance that results in a duration of one second, before any speed adjustments.
+ ///
+ private const float base_scoring_distance = 100;
+
+ public readonly SliderCurve Curve = new SliderCurve();
+
+ public int RepeatCount { get; set; } = 1;
+
+ public double Velocity;
+ public double TickDistance;
+
+ public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ {
+ base.ApplyDefaults(controlPointInfo, difficulty);
+
+ TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
+ DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
+
+ double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
+
+ Velocity = scoringDistance / timingPoint.BeatLength;
+ TickDistance = scoringDistance / difficulty.SliderTickRate;
+ }
+
+ public IEnumerable Ticks
+ {
+ get
+ {
+ SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime));
+
+ if (TickDistance == 0)
+ return ticks;
+
+ var length = Curve.Distance;
+ var tickDistance = Math.Min(TickDistance, length);
+ var repeatDuration = length / Velocity;
+
+ var minDistanceFromEnd = Velocity * 0.01;
+
+ ticks.Add(new Fruit
+ {
+ Samples = Samples,
+ ComboColour = ComboColour,
+ StartTime = StartTime,
+ X = X
+ });
+
+ for (var repeat = 0; repeat < RepeatCount; repeat++)
+ {
+ var repeatStartTime = StartTime + repeat * repeatDuration;
+ var reversed = repeat % 2 == 1;
+
+ for (var d = tickDistance; d <= length; d += tickDistance)
+ {
+ if (d > length - minDistanceFromEnd)
+ break;
+
+ var timeProgress = d / length;
+ var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
+
+ var lastTickTime = repeatStartTime + timeProgress * repeatDuration;
+ ticks.Add(new Droplet
+ {
+ StartTime = lastTickTime,
+ ComboColour = ComboColour,
+ X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
+ Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
+ {
+ Bank = s.Bank,
+ Name = @"slidertick",
+ Volume = s.Volume
+ }))
+ });
+ }
+
+ double tinyTickInterval = tickDistance / length * repeatDuration;
+ while (tinyTickInterval > 100)
+ tinyTickInterval /= 2;
+
+ for (double t = 0; t < repeatDuration; t += tinyTickInterval)
+ {
+ double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration;
+
+ ticks.Add(new TinyDroplet
+ {
+ StartTime = repeatStartTime + t,
+ ComboColour = ComboColour,
+ X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
+ Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
+ {
+ Bank = s.Bank,
+ Name = @"slidertick",
+ Volume = s.Volume
+ }))
+ });
+ }
+
+ ticks.Add(new Fruit
+ {
+ Samples = Samples,
+ ComboColour = ComboColour,
+ StartTime = repeatStartTime + repeatDuration,
+ X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
+ });
+ }
+
+ return ticks;
+ }
+ }
+
+ public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
+
+ public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
+
+ public double Duration => EndTime - StartTime;
+
+ public double Distance
+ {
+ get { return Curve.Distance; }
+ set { Curve.Distance = value; }
+ }
+
+ public List ControlPoints
+ {
+ get { return Curve.ControlPoints; }
+ set { Curve.ControlPoints = value; }
+ }
+
+ public List RepeatSamples { get; set; } = new List();
+
+ public CurveType CurveType
+ {
+ get { return Curve.CurveType; }
+ set { Curve.CurveType = value; }
+ }
+
+ public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
+
+ public double ProgressAt(double progress)
+ {
+ double p = progress * RepeatCount % 1;
+ if (RepeatAt(progress) % 2 == 1)
+ p = 1 - p;
+ return p;
+ }
+
+ public int RepeatAt(double progress) => (int)(progress * RepeatCount);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
new file mode 100644
index 0000000000..231f3d5361
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
@@ -0,0 +1,9 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Catch.Objects
+{
+ public class TinyDroplet : Droplet
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
index 42fbc7e082..dd2006c60c 100644
--- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
@@ -8,11 +8,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("osu.Game.Rulesets.Catch")]
-[assembly: AssemblyDescription("")]
+[assembly: AssemblyDescription("catch the fruit. to the beat.")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
+[assembly: AssemblyCompany("ppy Pty Ltd")]
[assembly: AssemblyProduct("osu.Game.Rulesets.Catch")]
-[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -24,15 +24,5 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("58f6c80c-1253-4a0e-a465-b8c85ebeadf3")]
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 16756e65f1..66a5636b74 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -21,6 +21,19 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
foreach (var obj in beatmap.HitObjects)
{
+ var stream = obj as JuiceStream;
+
+ if (stream != null)
+ {
+ AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+ AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+
+ foreach (var unused in stream.Ticks)
+ AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
+
+ continue;
+ }
+
var fruit = obj as Fruit;
if (fruit != null)
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs
index 8d18a712d8..25c095426f 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs
@@ -2,22 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{
- protected override Beatmap CreateBeatmap()
+ public TestCaseCatchPlayer() : base(typeof(CatchRuleset))
{
- var beatmap = new Beatmap();
-
- for (int i = 0; i < 256; i++)
- beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
-
- return beatmap;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
new file mode 100644
index 0000000000..7c3bb2bfd8
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
@@ -0,0 +1,27 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
+ {
+ public TestCaseCatchStacker() : base(typeof(CatchRuleset))
+ {
+ }
+
+ protected override Beatmap CreateBeatmap()
+ {
+ var beatmap = new Beatmap();
+
+ for (int i = 0; i < 256; i++)
+ beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
+
+ return beatmap;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs
index 6a065e197d..21a9bdec51 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(CatcherArea),
+ typeof(Catcher),
};
[BackgroundDependencyLoader]
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
new CatchInputManager(rulesets.GetRuleset(2))
{
RelativeSizeAxes = Axes.Both,
- Child = new CatcherArea
+ Child = new Catcher
{
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 2b6f9bbf5a..987eef5e45 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -1,10 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@@ -13,16 +15,20 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchPlayfield : ScrollingPlayfield
{
+ public static readonly float BASE_WIDTH = 512;
+
protected override Container Content => content;
private readonly Container content;
- private readonly CatcherArea catcherArea;
+
+ private readonly Container catcherContainer;
+ private readonly Catcher catcher;
public CatchPlayfield()
: base(Axes.Y)
{
- Reversed.Value = true;
+ Container explodingFruitContainer;
- Size = new Vector2(1);
+ Reversed.Value = true;
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
@@ -33,24 +39,43 @@ namespace osu.Game.Rulesets.Catch.UI
{
RelativeSizeAxes = Axes.Both,
},
- catcherArea = new CatcherArea
+ explodingFruitContainer = new Container
{
RelativeSizeAxes = Axes.Both,
+ },
+ catcherContainer = new Container
+ {
+ RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
- Height = 0.3f
+ Height = 180,
+ Child = catcher = new Catcher
+ {
+ ExplodingFruitTarget = explodingFruitContainer,
+ RelativePositionAxes = Axes.Both,
+ Origin = Anchor.TopCentre,
+ X = 0.5f,
+ }
}
};
}
+ protected override void Update()
+ {
+ base.Update();
+ catcher.Size = new Vector2(catcherContainer.DrawSize.Y);
+ }
+
+ public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
+
public override void Add(DrawableHitObject h)
{
h.Depth = (float)h.HitObject.StartTime;
base.Add(h);
- var fruit = (DrawableFruit)h;
- fruit.CheckPosition = catcherArea.CheckIfWeCanCatch;
+ var fruit = (DrawableCatchHitObject)h;
+ fruit.CheckPosition = CheckIfWeCanCatch;
}
public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
@@ -58,8 +83,12 @@ namespace osu.Game.Rulesets.Catch.UI
if (judgement.IsHit)
{
Vector2 screenPosition = judgedObject.ScreenSpaceDrawQuad.Centre;
- Remove(judgedObject);
- catcherArea.Add(judgedObject, screenPosition);
+
+ // todo: don't do this
+ (judgedObject.Parent as Container)?.Remove(judgedObject);
+ (judgedObject.Parent as Container)?.Remove(judgedObject);
+
+ catcher.Add(judgedObject, screenPosition);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 8a6ef71996..92912eb177 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -32,8 +32,13 @@ namespace osu.Game.Rulesets.Catch.UI
protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h)
{
- if (h is Fruit)
- return new DrawableFruit(h);
+ var fruit = h as Fruit;
+ if (fruit != null)
+ return new DrawableFruit(fruit);
+
+ var stream = h as JuiceStream;
+ if (stream != null)
+ return new DrawableJuiceStream(stream);
return null;
}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
new file mode 100644
index 0000000000..87fe95ed2f
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -0,0 +1,193 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Bindings;
+using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class Catcher : Container, IKeyBindingHandler
+ {
+ private Texture texture;
+
+ private Container caughtFruit;
+
+ public Container ExplodingFruitTarget;
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
+
+ Children = new Drawable[]
+ {
+ createCatcherSprite(),
+ caughtFruit = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
+ }
+ };
+ }
+
+ private int currentDirection;
+
+ private bool dashing;
+
+ protected bool Dashing
+ {
+ get { return dashing; }
+ set
+ {
+ if (value == dashing) return;
+
+ dashing = value;
+
+ if (dashing)
+ Schedule(addAdditiveSprite);
+ }
+ }
+
+ private void addAdditiveSprite()
+ {
+ if (!dashing) return;
+
+ var additive = createCatcherSprite();
+
+ additive.RelativePositionAxes = Axes.Both;
+ additive.Blending = BlendingMode.Additive;
+ additive.Position = Position;
+ additive.Scale = Scale;
+
+ ((Container)Parent).Add(additive);
+
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
+
+ Scheduler.AddDelayed(addAdditiveSprite, 50);
+ }
+
+ private Sprite createCatcherSprite() => new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Texture = texture,
+ OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
+ };
+
+ public bool OnPressed(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection--;
+ return true;
+ case CatchAction.MoveRight:
+ currentDirection++;
+ return true;
+ case CatchAction.Dash:
+ Dashing = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(CatchAction action)
+ {
+ switch (action)
+ {
+ case CatchAction.MoveLeft:
+ currentDirection++;
+ return true;
+ case CatchAction.MoveRight:
+ currentDirection--;
+ return true;
+ case CatchAction.Dash:
+ Dashing = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
+ ///
+ private const double base_speed = 1.0 / 512;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (currentDirection == 0) return;
+
+ double dashModifier = Dashing ? 1 : 0.5;
+
+ Scale = new Vector2(Math.Sign(currentDirection), 1);
+ X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
+ }
+
+ public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
+ {
+ fruit.RelativePositionAxes = Axes.None;
+ fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
+
+ fruit.Anchor = Anchor.TopCentre;
+ fruit.Origin = Anchor.BottomCentre;
+ fruit.Scale *= 0.7f;
+ fruit.LifetimeEnd = double.MaxValue;
+
+ float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
+
+ while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
+ {
+ fruit.X += RNG.Next(-5, 5);
+ fruit.Y -= RNG.Next(0, 5);
+ }
+
+ caughtFruit.Add(fruit);
+
+ if (((CatchBaseHit)fruit.HitObject).LastInCombo)
+ explode();
+ }
+
+ private void explode()
+ {
+ var fruit = caughtFruit.ToArray();
+
+ foreach (var f in fruit)
+ {
+ var originalX = f.X * Scale.X;
+
+ if (ExplodingFruitTarget != null)
+ {
+ f.Anchor = Anchor.TopLeft;
+ f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
+
+ caughtFruit.Remove(f);
+
+ ExplodingFruitTarget.Add(f);
+ }
+
+ f.MoveToY(f.Y - 50, 250, Easing.OutSine)
+ .Then()
+ .MoveToY(f.Y + 50, 500, Easing.InSine);
+
+ f.MoveToX(f.X + originalX * 6, 1000);
+ f.FadeOut(750);
+
+ f.Expire();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
deleted file mode 100644
index 2930dbb7cc..0000000000
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-using osu.Framework.Input.Bindings;
-using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
-using OpenTK;
-
-namespace osu.Game.Rulesets.Catch.UI
-{
- public class CatcherArea : Container
- {
- private Catcher catcher;
- private Container explodingFruitContainer;
-
- public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition);
-
- public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Children = new Drawable[]
- {
- explodingFruitContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- catcher = new Catcher
- {
- RelativePositionAxes = Axes.Both,
- ExplodingFruitTarget = explodingFruitContainer,
- Origin = Anchor.TopCentre,
- X = 0.5f,
- }
- };
- }
-
- protected override void Update()
- {
- base.Update();
-
- catcher.Size = new Vector2(DrawSize.Y);
- }
-
- private class Catcher : Container, IKeyBindingHandler
- {
- private Texture texture;
-
- private Container caughtFruit;
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
-
- Children = new Drawable[]
- {
- createCatcherSprite(),
- caughtFruit = new Container
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
- }
- };
- }
-
- private int currentDirection;
-
- private bool dashing;
-
- public Container ExplodingFruitTarget;
-
- protected bool Dashing
- {
- get { return dashing; }
- set
- {
- if (value == dashing) return;
-
- dashing = value;
-
- if (dashing)
- Schedule(addAdditiveSprite);
- }
- }
-
- private void addAdditiveSprite()
- {
- if (!dashing) return;
-
- var additive = createCatcherSprite();
-
- additive.RelativePositionAxes = Axes.Both;
- additive.Blending = BlendingMode.Additive;
- additive.Position = Position;
- additive.Scale = Scale;
-
- ((CatcherArea)Parent).Add(additive);
-
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
-
- Scheduler.AddDelayed(addAdditiveSprite, 50);
- }
-
- private Sprite createCatcherSprite() => new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- Texture = texture,
- OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
- };
-
- public bool OnPressed(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection--;
- return true;
- case CatchAction.MoveRight:
- currentDirection++;
- return true;
- case CatchAction.Dash:
- Dashing = true;
- return true;
- }
-
- return false;
- }
-
- public bool OnReleased(CatchAction action)
- {
- switch (action)
- {
- case CatchAction.MoveLeft:
- currentDirection++;
- return true;
- case CatchAction.MoveRight:
- currentDirection--;
- return true;
- case CatchAction.Dash:
- Dashing = false;
- return true;
- }
-
- return false;
- }
-
- ///
- /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
- ///
- private const double base_speed = 1.0 / 512;
-
- protected override void Update()
- {
- base.Update();
-
- if (currentDirection == 0) return;
-
- double dashModifier = Dashing ? 1 : 0.5;
-
- Scale = new Vector2(Math.Sign(currentDirection), 1);
- X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
- }
-
- public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition)
- {
- fruit.RelativePositionAxes = Axes.None;
- fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
-
- fruit.Anchor = Anchor.TopCentre;
- fruit.Origin = Anchor.BottomCentre;
- fruit.Scale *= 0.7f;
- fruit.LifetimeEnd = double.MaxValue;
-
- float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
-
- while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
- {
- fruit.X += RNG.Next(-5, 5);
- fruit.Y -= RNG.Next(0, 5);
- }
-
- caughtFruit.Add(fruit);
-
- if (((CatchBaseHit)fruit.HitObject).LastInCombo)
- explode();
- }
-
- private void explode()
- {
- var fruit = caughtFruit.ToArray();
-
- foreach (var f in fruit)
- {
- var originalX = f.X * Scale.X;
-
- if (ExplodingFruitTarget != null)
- {
- f.Anchor = Anchor.TopLeft;
- f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
-
- caughtFruit.Remove(f);
-
- ExplodingFruitTarget.Add(f);
- }
-
- f.MoveToY(f.Y - 50, 250, Easing.OutSine)
- .Then()
- .MoveToY(f.Y + 50, 500, Easing.InSine);
-
- f.MoveToX(f.X + originalX * 6, 1000);
- f.FadeOut(750);
-
- f.Expire();
- }
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/app.config b/osu.Game.Rulesets.Catch/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Catch/app.config
+++ b/osu.Game.Rulesets.Catch/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
index 787825d482..83b16997a7 100644
--- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
+++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
@@ -16,7 +16,7 @@
true
full
false
- ..\osu.Game\bin\Debug\
+ bin\Debug\
DEBUG;TRACE
prompt
4
@@ -26,7 +26,7 @@
pdbonly
true
- ..\osu.Game\bin\Release\
+ bin\Release\
TRACE
prompt
4
@@ -50,16 +50,23 @@
+
+
+
+
+
+
+
-
+
@@ -79,11 +86,6 @@
osu.Framework
False
-
- {C92A607B-1FDD-4954-9F92-03FF547D9080}
- osu.Game.Rulesets.Osu
- False
-
{2a66dd92-adb1-4994-89e2-c94e04acda0d}
osu.Game
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index d7c86e1f89..f6d30ad3fa 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
beatmap = original;
- BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty;
+ BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 20966a75f7..270c264e0c 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
- double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
+ double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index a3173f9784..c38680c3a5 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (drainTime == 0)
drainTime = 10000;
- BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.Difficulty;
+ BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index b98802db69..784df1f293 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Mania
return 0;
}
- protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize)));
+ protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
}
}
diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
index 790002acd7..85a8f95b14 100644
--- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
@@ -8,11 +8,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("osu.Game.Rulesets.Mania")]
-[assembly: AssemblyDescription("")]
+[assembly: AssemblyDescription("smash the keys. to the beat.")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
+[assembly: AssemblyCompany("ppy Pty Ltd")]
[assembly: AssemblyProduct("osu.Game.Rulesets.Mania")]
-[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -24,15 +24,5 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("48f4582b-7687-4621-9cbe-5c24197cb536")]
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index a200ba31e2..9b8ebe0070 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override void SimulateAutoplay(Beatmap beatmap)
{
- BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
+ BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
diff --git a/osu.Game.Rulesets.Mania/Testing/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs
similarity index 96%
rename from osu.Game.Rulesets.Mania/Testing/TestCaseManiaHitObjects.cs
rename to osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs
index a5568b7f6d..4230171288 100644
--- a/osu.Game.Rulesets.Mania/Testing/TestCaseManiaHitObjects.cs
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs
@@ -10,7 +10,7 @@ using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Testing
+namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
internal class TestCaseManiaHitObjects : OsuTestCase
diff --git a/osu.Game.Rulesets.Mania/Testing/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs
similarity index 96%
rename from osu.Game.Rulesets.Mania/Testing/TestCaseManiaPlayfield.cs
rename to osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs
index 4b68334efb..c1de273a1b 100644
--- a/osu.Game.Rulesets.Mania/Testing/TestCaseManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs
@@ -17,7 +17,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
using osu.Game.Tests.Visual;
-namespace osu.Game.Rulesets.Mania.Testing
+namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
internal class TestCaseManiaPlayfield : OsuTestCase
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 3b49d81674..08acd46c57 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -88,18 +88,18 @@ namespace osu.Game.Rulesets.Mania.UI
protected override BeatmapConverter CreateBeatmapConverter()
{
if (IsForCurrentRuleset)
- AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize));
+ AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.CircleSize));
else
{
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
AvailableColumns = 7;
- else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5)
- AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6;
+ else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.CircleSize) >= 5)
+ AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
- AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4;
+ AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) > 4 ? 5 : 4;
else
- AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7));
+ AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) + 1, 7));
}
return new ManiaBeatmapConverter(IsForCurrentRuleset, AvailableColumns);
diff --git a/osu.Game.Rulesets.Mania/app.config b/osu.Game.Rulesets.Mania/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Mania/app.config
+++ b/osu.Game.Rulesets.Mania/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index fa8b9d35aa..bacb4185b2 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -16,7 +16,7 @@
true
full
false
- ..\osu.Game\bin\Debug\
+ bin\Debug\
DEBUG;TRACE
prompt
4
@@ -26,7 +26,7 @@
pdbonly
true
- ..\osu.Game\bin\Release\
+ bin\Release\
TRACE
prompt
4
@@ -80,8 +80,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 53b3427fc4..8a96640b1e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool RemoveWhenNotAlive => false;
+ public override bool DisplayJudgement => false;
+
public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick)
{
this.sliderTick = sliderTick;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 98dd40b0e6..054a2067ec 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly SpinnerDisc disc;
private readonly SpinnerTicks ticks;
+ private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer;
@@ -103,6 +104,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
}
},
+ spmCounter = new SpinnerSpmCounter
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Y = 120,
+ Alpha = 0
+ }
};
}
@@ -157,6 +165,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
+ if (!spmCounter.IsPresent && disc.Tracking)
+ spmCounter.FadeIn(TIME_FADEIN);
base.Update();
}
@@ -167,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Rotation = disc.Rotation;
ticks.Rotation = disc.Rotation;
+ spmCounter.SetRotation(disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index 6577c7fd50..ca75a61738 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -77,7 +77,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private float lastAngle;
private float currentRotation;
public float RotationAbsolute;
-
private int completeTick;
private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360));
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
new file mode 100644
index 0000000000..ebe978f659
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
@@ -0,0 +1,75 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public class SpinnerSpmCounter : Container
+ {
+ private readonly OsuSpriteText spmText;
+
+ public SpinnerSpmCounter()
+ {
+ Children = new Drawable[]
+ {
+ spmText = new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = @"0",
+ Font = @"Venera",
+ TextSize = 24
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = @"SPINS PER MINUTE",
+ Font = @"Venera",
+ TextSize = 12,
+ Y = 30
+ }
+ };
+ }
+
+ private double spm;
+
+ public double SpinsPerMinute
+ {
+ get { return spm; }
+ private set
+ {
+ if (value == spm) return;
+ spm = value;
+ spmText.Text = Math.Truncate(value).ToString(@"#0");
+ }
+ }
+
+ private struct RotationRecord
+ {
+ public float Rotation;
+ public double Time;
+ }
+
+ private readonly Queue records = new Queue();
+ private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues
+
+ public void SetRotation(float currentRotation)
+ {
+ if (records.Count > 0)
+ {
+ var record = records.Peek();
+ while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
+ record = records.Dequeue();
+ SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
+ }
+
+ records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
index 791c9b594d..b6cf47071a 100644
--- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
@@ -8,11 +8,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("osu.Game.Mode.Osu")]
-[assembly: AssemblyDescription("")]
+[assembly: AssemblyDescription("click the circles. to the beat.")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
+[assembly: AssemblyCompany("ppy Pty Ltd")]
[assembly: AssemblyProduct("osu.Game.Mode.Osu")]
-[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -24,15 +24,5 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c92a607b-1fdd-4954-9f92-03ff547d9080")]
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 9252b3b7b8..6e5dd18c46 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
protected override void SimulateAutoplay(Beatmap beatmap)
{
- hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate;
+ hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
index adfc946f86..d8dd6c7323 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
if (autoCursorScale && beatmap.Value != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.Difficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
+ scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
}
cursorContainer.Scale = new Vector2(scale);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 1bb4e8493b..89f6a4e255 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -91,6 +91,9 @@ namespace osu.Game.Rulesets.Osu.UI
var osuJudgement = (OsuJudgement)judgement;
var osuObject = (OsuHitObject)judgedObject.HitObject;
+ if (!judgedObject.DisplayJudgement)
+ return;
+
DrawableOsuJudgement explosion = new DrawableOsuJudgement(osuJudgement)
{
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Osu/app.config b/osu.Game.Rulesets.Osu/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Osu/app.config
+++ b/osu.Game.Rulesets.Osu/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index d950742589..906b1052d5 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -17,7 +17,7 @@
true
full
false
- ..\osu.Game\bin\Debug\
+ bin\Debug\
DEBUG;TRACE
prompt
4
@@ -27,7 +27,7 @@
pdbonly
true
- ..\osu.Game\bin\Release\
+ bin\Release\
TRACE
prompt
4
@@ -68,6 +68,7 @@
+
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 4f2707ff88..9b4a6c47a9 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -51,18 +51,21 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
// Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone();
- info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier;
+ info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap converted = base.ConvertBeatmap(original);
- // Post processing step to transform hit objects with the same start time into strong hits
- converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
+ if (original.BeatmapInfo.RulesetID == 3)
{
- TaikoHitObject first = x.First();
- if (x.Skip(1).Any())
- first.IsStrong = true;
- return first;
- }).ToList();
+ // Post processing step to transform mania hit objects with the same start time into strong hits
+ converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
+ {
+ TaikoHitObject first = x.First();
+ if (x.Skip(1).Any())
+ first.IsStrong = true;
+ return first;
+ }).ToList();
+ }
return converted;
}
@@ -93,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
- double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+ double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
// The duration of the taiko hit object
double taikoDuration = distance / taikoVelocity;
@@ -103,12 +106,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
speedAdjustedBeatLength *= speedAdjustment;
// The velocity of the osu! hit object - calculated as the velocity of a slider
- double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+ double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
- double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats);
+ double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / repeats);
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
@@ -151,13 +154,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Samples = obj.Samples,
IsStrong = strong,
Duration = taikoDuration,
- TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4,
+ TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
};
}
}
else if (endTimeData != null)
{
- double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
+ double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
yield return new Swell
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 8ac67ba0a6..e662f61bbe 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit;
}
+ public override bool DisplayJudgement => false;
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
index 89c07517ca..f6a9c8f101 100644
--- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
@@ -8,11 +8,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("osu.Game.Rulesets.Taiko")]
-[assembly: AssemblyDescription("")]
+[assembly: AssemblyDescription("bash the drum. to the beat.")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
+[assembly: AssemblyCompany("ppy Pty Ltd")]
[assembly: AssemblyProduct("osu.Game.Rulesets.Taiko")]
-[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -24,15 +24,5 @@ using System.Runtime.InteropServices;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f167e17a-7de6-4af5-b920-a5112296c695")]
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index abdda9676f..752e3bee26 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -71,12 +71,12 @@ namespace osu.Game.Rulesets.Taiko.Scoring
protected override void SimulateAutoplay(Beatmap beatmap)
{
- double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98));
+ double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpIncreaseTick = hp_hit_tick;
hpIncreaseGreat = hpMultiplierNormal * hp_hit_great;
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
- hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
+ hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
index 79ee2945ad..14c27bc332 100644
--- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
@@ -67,12 +67,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitObjects = new List { new CentreHit() },
BeatmapInfo = new BeatmapInfo
{
- Difficulty = new BeatmapDifficulty(),
+ BaseDifficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
- Author = @"peppy",
+ AuthorString = @"peppy",
},
},
ControlPointInfo = controlPointInfo
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index d9a216bbfc..136da8a532 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
- if (judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null)
+ if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null)
{
judgementContainer.Add(new DrawableTaikoJudgement(judgedObject, judgement)
{
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index f0853aef0e..48ee0a5b42 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI
StartTime = time,
};
- barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
+ barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
diff --git a/osu.Game.Rulesets.Taiko/app.config b/osu.Game.Rulesets.Taiko/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Taiko/app.config
+++ b/osu.Game.Rulesets.Taiko/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index 2c49be287b..d38b24f933 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -16,7 +16,7 @@
true
full
false
- ..\osu.Game\bin\Debug\
+ bin\Debug\
DEBUG;TRACE
prompt
4
@@ -26,7 +26,7 @@
pdbonly
true
- ..\osu.Game\bin\Release\
+ bin\Release\
TRACE
prompt
4
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs
index da3b448f74..95b691e07f 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
- Assert.AreEqual("Gamu", meta.Author);
+ Assert.AreEqual("Gamu", meta.AuthorString);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
Assert.AreEqual(164471, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
@@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
- var difficulty = beatmap.BeatmapInfo.Difficulty;
+ var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
@@ -143,4 +143,4 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index be48c997ea..e5b8c7fe57 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public void TestImportWhenClosed()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new HeadlessGameHost())
+ using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenClosed"))
{
var osu = loadOsu(host);
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
- Assert.IsFalse(File.Exists(temp));
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
}
}
@@ -61,15 +61,14 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
- Assert.IsFalse(File.Exists(temp));
+ waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
}
}
[Test]
public void TestImportWhenFileOpen()
{
- //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new HeadlessGameHost())
+ using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenFileOpen"))
{
var osu = loadOsu(host);
@@ -96,13 +95,10 @@ namespace osu.Game.Tests.Beatmaps.IO
private OsuGameBase loadOsu(GameHost host)
{
- host.Storage.DeleteDatabase(@"client");
-
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
- while (!osu.IsLoaded)
- Thread.Sleep(1);
+ waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
@@ -113,37 +109,33 @@ namespace osu.Game.Tests.Beatmaps.IO
var store = osu.Dependencies.Get();
- Action waitAction = () =>
- {
- while (!(resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any())
- Thread.Sleep(50);
- };
-
- Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout),
- @"BeatmapSet did not import to the database in allocated time.");
+ waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(),
+ @"BeatmapSet did not import to the database in allocated time.", timeout);
//ensure we were stored to beatmap database backing...
-
Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
- IEnumerable resultBeatmaps = null;
+ Func> queryBeatmaps = () => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0);
+ Func> queryBeatmapSets = () => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526);
//if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
- waitAction = () =>
- {
- while ((resultBeatmaps = store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12)
- Thread.Sleep(50);
- };
+ waitForOrAssert(() => queryBeatmaps().Count() == 12,
+ @"Beatmaps did not import to the database in allocated time", timeout);
- Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout),
- @"Beatmaps did not import to the database in allocated time");
+ waitForOrAssert(() => queryBeatmapSets().Count() == 1,
+ @"BeatmapSet did not import to the database in allocated time", timeout);
- var set = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526).First();
+ int countBeatmapSetBeatmaps = 0;
+ int countBeatmaps = 0;
- Assert.IsTrue(set.Beatmaps.Count == resultBeatmaps.Count(),
- $@"Incorrect database beatmap count post-import ({resultBeatmaps.Count()} but should be {set.Beatmaps.Count}).");
+ waitForOrAssert(() =>
+ (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
+ (countBeatmaps = queryBeatmaps().Count()),
+ $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
- foreach (BeatmapInfo b in resultBeatmaps)
+ var set = queryBeatmapSets().First();
+
+ foreach (BeatmapInfo b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
Assert.IsTrue(set.Beatmaps.Count > 0);
@@ -160,5 +152,11 @@ namespace osu.Game.Tests.Beatmaps.IO
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
}
+
+ private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
+ {
+ Action waitAction = () => { while (!result()) Thread.Sleep(200); };
+ Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage);
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
index 7a7a8a58bc..12bbde5b57 100644
--- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
- Assert.AreEqual("Deif", meta.Author);
+ Assert.AreEqual("Deif", meta.AuthorString);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
Assert.AreEqual(164471, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config
index faeaf001de..2f5b13a89d 100644
--- a/osu.Game.Tests/app.config
+++ b/osu.Game.Tests/app.config
@@ -6,6 +6,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 8ebd38022e..51c1b03373 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -39,14 +39,9 @@
True
-
- $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll
-
-
- $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll
-
-
- $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll
+
+ $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
+ True
diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config
index af47f642e3..ecc44f0c70 100644
--- a/osu.Game.Tests/packages.config
+++ b/osu.Game.Tests/packages.config
@@ -1,11 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 458c2304f2..35b6cc2b02 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -58,6 +58,23 @@ namespace osu.Game.Beatmaps
ComboColors = original?.ComboColors ?? ComboColors;
HitObjects = original?.HitObjects ?? HitObjects;
Storyboard = original?.Storyboard ?? Storyboard;
+
+ if (original == null && Metadata == null)
+ {
+ // we may have no metadata in cases we weren't sourced from the database.
+ // let's fill it (and other related fields) so we don't need to null-check it in future usages.
+ BeatmapInfo = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Artist = @"Unknown",
+ Title = @"Unknown",
+ AuthorString = @"Unknown Creator",
+ },
+ Version = @"Normal",
+ BaseDifficulty = new BeatmapDifficulty()
+ };
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 7c2294cae9..0b0fca8292 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using SQLite.Net.Attributes;
+using System.ComponentModel.DataAnnotations.Schema;
namespace osu.Game.Beatmaps
{
@@ -12,8 +12,9 @@ namespace osu.Game.Beatmaps
///
public const float DEFAULT_DIFFICULTY = 5;
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
+
public float DrainRate { get; set; } = DEFAULT_DIFFICULTY;
public float CircleSize { get; set; } = DEFAULT_DIFFICULTY;
public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY;
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 5e4e122fb5..08cef3f934 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -2,49 +2,45 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapInfo : IEquatable, IJsonSerializable
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
//TODO: should be in database
public int BeatmapVersion;
+ [JsonProperty("id")]
public int? OnlineBeatmapID { get; set; }
+ [JsonProperty("beatmapset_id")]
+ [NotMapped]
public int? OnlineBeatmapSetID { get; set; }
- [ForeignKey(typeof(BeatmapSetInfo))]
public int BeatmapSetInfoID { get; set; }
- [ManyToOne]
+ [Required]
public BeatmapSetInfo BeatmapSet { get; set; }
- [ForeignKey(typeof(BeatmapMetadata))]
- public int BeatmapMetadataID { get; set; }
-
- [OneToOne(CascadeOperations = CascadeOperation.All)]
public BeatmapMetadata Metadata { get; set; }
- [ForeignKey(typeof(BeatmapDifficulty)), NotNull]
public int BaseDifficultyID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.All)]
- public BeatmapDifficulty Difficulty { get; set; }
+ public BeatmapDifficulty BaseDifficulty { get; set; }
- [Ignore]
+ [NotMapped]
public BeatmapMetrics Metrics { get; set; }
- [Ignore]
+ [NotMapped]
public BeatmapOnlineInfo OnlineInfo { get; set; }
public string Path { get; set; }
@@ -57,7 +53,6 @@ namespace osu.Game.Beatmaps
///
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
///
- [Indexed]
[JsonProperty("file_md5")]
public string MD5Hash { get; set; }
@@ -67,10 +62,8 @@ namespace osu.Game.Beatmaps
public float StackLeniency { get; set; }
public bool SpecialStyle { get; set; }
- [ForeignKey(typeof(RulesetInfo))]
public int RulesetID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)]
public RulesetInfo Ruleset { get; set; }
public bool LetterboxInBreaks { get; set; }
@@ -99,7 +92,7 @@ namespace osu.Game.Beatmaps
}
}
- [Ignore]
+ [NotMapped]
public int[] Bookmarks { get; set; } = new int[0];
public double DistanceSpacing { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index a1b678392b..47dbc72837 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -5,8 +5,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Linq.Expressions;
+using System.Threading.Tasks;
using Ionic.Zip;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
@@ -15,14 +16,13 @@ using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO;
+using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IPC;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
-using SQLite.Net;
-using osu.Game.Online.API.Requests;
-using System.Threading.Tasks;
-using osu.Game.Online.API;
namespace osu.Game.Beatmaps
{
@@ -58,9 +58,19 @@ namespace osu.Game.Beatmaps
private readonly Storage storage;
- private readonly FileStore files;
+ private BeatmapStore createBeatmapStore(Func context)
+ {
+ var store = new BeatmapStore(context);
+ store.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
+ store.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
+ store.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
+ store.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
+ return store;
+ }
- private readonly SQLiteConnection connection;
+ private readonly Func createContext;
+
+ private readonly FileStore files;
private readonly RulesetStore rulesets;
@@ -83,22 +93,27 @@ namespace osu.Game.Beatmaps
///
public Func GetStableStorage { private get; set; }
- public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
+ public BeatmapManager(Storage storage, Func context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
{
- beatmaps = new BeatmapStore(connection);
- beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
- beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
- beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
- beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
+ createContext = context;
+ importContext = new Lazy(() =>
+ {
+ var c = createContext();
+ c.Database.AutoTransactionsEnabled = false;
+ return c;
+ });
- this.storage = storage;
- this.files = files;
- this.connection = connection;
+ beatmaps = createBeatmapStore(context);
+ files = new FileStore(context, storage);
+
+ this.storage = files.Storage;
this.rulesets = rulesets;
this.api = api;
if (importHost != null)
ipc = new BeatmapIPCChannel(importHost, this);
+
+ beatmaps.Cleanup();
}
///
@@ -156,7 +171,7 @@ namespace osu.Game.Beatmaps
notification.State = ProgressNotificationState.Completed;
}
- private readonly object importLock = new object();
+ private readonly Lazy importContext;
///
/// Import a beatmap from an .
@@ -164,13 +179,29 @@ namespace osu.Game.Beatmaps
/// The beatmap to be imported.
public BeatmapSetInfo Import(ArchiveReader archiveReader)
{
- BeatmapSetInfo set = null;
-
// let's only allow one concurrent import at a time for now.
- lock (importLock)
- connection.RunInTransaction(() => Import(set = importToStorage(archiveReader)));
+ lock (importContext)
+ {
+ var context = importContext.Value;
- return set;
+ using (var transaction = context.Database.BeginTransaction())
+ {
+ // create local stores so we can isolate and thread safely, and share a context/transaction.
+ var iFiles = new FileStore(() => context, storage);
+ var iBeatmaps = createBeatmapStore(() => context);
+
+ BeatmapSetInfo set = importToStorage(iFiles, iBeatmaps, archiveReader);
+
+ if (set.ID == 0)
+ {
+ iBeatmaps.Add(set);
+ context.SaveChanges();
+ }
+
+ transaction.Commit();
+ return set;
+ }
+ }
}
///
@@ -182,7 +213,7 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return;
- beatmaps.Add(beatmapSetInfo);
+ createBeatmapStore(createContext).Add(beatmapSetInfo);
}
///
@@ -260,10 +291,33 @@ namespace osu.Game.Beatmaps
/// The beatmap set to delete.
public void Delete(BeatmapSetInfo beatmapSet)
{
- if (!beatmaps.Delete(beatmapSet)) return;
+ lock (importContext)
+ {
+ var context = importContext.Value;
- if (!beatmapSet.Protected)
- files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
+ using (var transaction = context.Database.BeginTransaction())
+ {
+ context.ChangeTracker.AutoDetectChangesEnabled = false;
+
+ // re-fetch the beatmap set on the import context.
+ beatmapSet = context.BeatmapSetInfo.Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == beatmapSet.ID);
+
+ // create local stores so we can isolate and thread safely, and share a context/transaction.
+ var iFiles = new FileStore(() => context, storage);
+ var iBeatmaps = createBeatmapStore(() => context);
+
+ if (iBeatmaps.Delete(beatmapSet))
+ {
+ if (!beatmapSet.Protected)
+ iFiles.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
+ }
+
+ context.ChangeTracker.AutoDetectChangesEnabled = true;
+ context.SaveChanges();
+
+ transaction.Commit();
+ }
+ }
}
///
@@ -283,7 +337,7 @@ namespace osu.Game.Beatmaps
/// Is a no-op for already usable beatmaps.
///
/// The beatmap to restore.
- public void Undelete(BeatmapSetInfo beatmapSet)
+ private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Undelete(beatmapSet)) return;
@@ -302,9 +356,6 @@ namespace osu.Game.Beatmaps
if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
- lock (beatmaps)
- beatmaps.Populate(beatmapInfo);
-
if (beatmapInfo.BeatmapSet == null)
throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database.");
@@ -318,32 +369,12 @@ namespace osu.Game.Beatmaps
return working;
}
- ///
- /// Reset the manager to an empty state.
- ///
- public void Reset()
- {
- lock (beatmaps)
- beatmaps.Reset();
- }
-
///
/// Perform a lookup query on available s.
///
/// The query.
/// The first result for the provided query, or null if no results were found.
- public BeatmapSetInfo QueryBeatmapSet(Func query)
- {
- lock (beatmaps)
- {
- BeatmapSetInfo set = beatmaps.Query().FirstOrDefault(query);
-
- if (set != null)
- beatmaps.Populate(set);
-
- return set;
- }
- }
+ public BeatmapSetInfo QueryBeatmapSet(Func query) => beatmaps.BeatmapSets.FirstOrDefault(query);
///
/// Refresh an existing instance of a from the store.
@@ -357,35 +388,21 @@ namespace osu.Game.Beatmaps
///
/// The query.
/// Results from the provided query.
- public List QueryBeatmapSets(Expression> query)
- {
- return beatmaps.QueryAndPopulate(query);
- }
+ public List QueryBeatmapSets(Func query) => beatmaps.BeatmapSets.Where(query).ToList();
///
/// Perform a lookup query on available s.
///
/// The query.
/// The first result for the provided query, or null if no results were found.
- public BeatmapInfo QueryBeatmap(Func query)
- {
- BeatmapInfo set = beatmaps.Query().FirstOrDefault(query);
-
- if (set != null)
- beatmaps.Populate(set);
-
- return set;
- }
+ public BeatmapInfo QueryBeatmap(Func query) => beatmaps.Beatmaps.FirstOrDefault(query);
///
/// Perform a lookup query on available s.
///
/// The query.
/// Results from the provided query.
- public List QueryBeatmaps(Expression> query)
- {
- lock (beatmaps) return beatmaps.QueryAndPopulate(query);
- }
+ public List QueryBeatmaps(Func query) => beatmaps.Beatmaps.Where(query).ToList();
///
/// Creates an from a valid storage path.
@@ -395,9 +412,9 @@ namespace osu.Game.Beatmaps
private ArchiveReader getReaderFrom(string path)
{
if (ZipFile.IsZipFile(path))
+ // ReSharper disable once InconsistentlySynchronizedField
return new OszArchiveReader(storage.GetStream(path));
- else
- return new LegacyFilesystemReader(path);
+ return new LegacyFilesystemReader(path);
}
///
@@ -406,7 +423,7 @@ namespace osu.Game.Beatmaps
///
/// The beatmap archive to be read.
/// The imported beatmap, or an existing instance if it is already present.
- private BeatmapSetInfo importToStorage(ArchiveReader reader)
+ private BeatmapSetInfo importToStorage(FileStore files, BeatmapStore beatmaps, ArchiveReader reader)
{
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
@@ -422,13 +439,11 @@ namespace osu.Game.Beatmaps
var hash = hashable.ComputeSHA2Hash();
// check if this beatmap has already been imported and exit early if so.
- BeatmapSetInfo beatmapSet;
- lock (beatmaps)
- beatmapSet = beatmaps.QueryAndPopulate(b => b.Hash == hash).FirstOrDefault();
+ var beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash);
if (beatmapSet != null)
{
- Undelete(beatmapSet);
+ undelete(beatmaps, files, beatmapSet);
// ensure all files are present and accessible
foreach (var f in beatmapSet.Files)
@@ -438,6 +453,8 @@ namespace osu.Game.Beatmaps
files.Add(s, false);
}
+ // todo: delete any files which shouldn't exist any more.
+
return beatmapSet;
}
@@ -487,10 +504,11 @@ namespace osu.Game.Beatmaps
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
beatmap.BeatmapInfo.Metadata = null;
+ RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
+
// TODO: this should be done in a better place once we actually need to dynamically update it.
- beatmap.BeatmapInfo.Ruleset = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID);
- beatmap.BeatmapInfo.StarDifficulty = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap)
- .Calculate() ?? 0;
+ beatmap.BeatmapInfo.Ruleset = ruleset;
+ beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
}
@@ -502,17 +520,10 @@ namespace osu.Game.Beatmaps
///
/// Returns a list of all usable s.
///
- /// Whether returned objects should be pre-populated with all data.
/// A list of available .
- public List GetAllUsableBeatmapSets(bool populate = true)
+ public List GetAllUsableBeatmapSets()
{
- lock (beatmaps)
- {
- if (populate)
- return beatmaps.QueryAndPopulate(b => !b.DeletePending).ToList();
- else
- return beatmaps.Query(b => !b.DeletePending).ToList();
- }
+ return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList();
}
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
@@ -547,10 +558,13 @@ namespace osu.Game.Beatmaps
return beatmap;
}
- catch { return null; }
+ catch
+ {
+ return null;
+ }
}
- private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => f.Filename == filename).FileInfo.StoragePath;
+ private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
protected override Texture GetBackground()
{
@@ -561,7 +575,10 @@ namespace osu.Game.Beatmaps
{
return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile));
}
- catch { return null; }
+ catch
+ {
+ return null;
+ }
}
protected override Track GetTrack()
@@ -571,8 +588,13 @@ namespace osu.Game.Beatmaps
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new TrackBass(trackData);
}
- catch { return new TrackVirtual(); }
+ catch
+ {
+ return new TrackVirtual();
+ }
}
+
+ protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
}
///
@@ -593,9 +615,9 @@ namespace osu.Game.Beatmaps
public void DeleteAll()
{
- var maps = GetAllUsableBeatmapSets().ToArray();
+ var maps = GetAllUsableBeatmapSets();
- if (maps.Length == 0) return;
+ if (maps.Count == 0) return;
var notification = new ProgressNotification
{
@@ -613,8 +635,8 @@ namespace osu.Game.Beatmaps
// user requested abort
return;
- notification.Text = $"Deleting ({i} of {maps.Length})";
- notification.Progress = (float)++i / maps.Length;
+ notification.Text = $"Deleting ({i} of {maps.Count})";
+ notification.Progress = (float)++i / maps.Count;
Delete(b);
}
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index cc9a51b4e2..85bcfecfb8 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -1,17 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
-using SQLite.Net.Attributes;
+using osu.Game.Users;
namespace osu.Game.Beatmaps
{
public class BeatmapMetadata
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
+ [NotMapped]
public int? OnlineBeatmapSetID { get; set; }
public string Title { get; set; }
@@ -19,8 +22,24 @@ namespace osu.Game.Beatmaps
public string Artist { get; set; }
public string ArtistUnicode { get; set; }
+ public List Beatmaps { get; set; }
+ public List BeatmapSets { get; set; }
+
+ ///
+ /// Helper property to deserialize a username to .
+ ///
[JsonProperty(@"creator")]
- public string Author { get; set; }
+ [Column("Author")]
+ public string AuthorString
+ {
+ get { return Author?.Username; }
+ set { Author = new User { Username = value }; }
+ }
+
+ ///
+ /// The author of the beatmaps in this set.
+ ///
+ public User Author;
public string Source { get; set; }
@@ -32,7 +51,7 @@ namespace osu.Game.Beatmaps
public string[] SearchableTerms => new[]
{
- Author,
+ Author?.Username,
Artist,
ArtistUnicode,
Title,
diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
index a05362b32d..0e3aa61d9f 100644
--- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
@@ -1,27 +1,24 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
using osu.Game.IO;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapSetFileInfo
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- [ForeignKey(typeof(BeatmapSetInfo)), NotNull]
public int BeatmapSetInfoID { get; set; }
- [ForeignKey(typeof(FileInfo)), NotNull]
public int FileInfoID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)]
public FileInfo FileInfo { get; set; }
- [NotNull]
+ [Required]
public string Filename { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index f47affcab8..2dfc4d0fe0 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -2,41 +2,34 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapSetInfo
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int? OnlineBeatmapSetID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.All)]
public BeatmapMetadata Metadata { get; set; }
- [NotNull, ForeignKey(typeof(BeatmapMetadata))]
- public int BeatmapMetadataID { get; set; }
-
- [OneToMany(CascadeOperations = CascadeOperation.All)]
public List Beatmaps { get; set; }
- [Ignore]
+ [NotMapped]
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty);
- [Indexed]
+ [NotMapped]
public bool DeletePending { get; set; }
public string Hash { get; set; }
public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
- [OneToMany(CascadeOperations = CascadeOperation.All)]
public List Files { get; set; }
public bool Protected { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
index 6b59f0f298..27d1f057ca 100644
--- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
@@ -3,7 +3,6 @@
using System;
using Newtonsoft.Json;
-using osu.Game.Users;
namespace osu.Game.Beatmaps
{
@@ -12,11 +11,6 @@ namespace osu.Game.Beatmaps
///
public class BeatmapSetOnlineInfo
{
- ///
- /// The author of the beatmaps in this set.
- ///
- public User Author;
-
///
/// The date this beatmap set was submitted to the online listing.
///
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index 0f2d8cffa6..3322a4c236 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -2,9 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Game.Database;
-using SQLite.Net;
-using SQLiteNetExtensions.Extensions;
namespace osu.Game.Beatmaps
{
@@ -19,76 +20,23 @@ namespace osu.Game.Beatmaps
public event Action BeatmapHidden;
public event Action BeatmapRestored;
- ///
- /// The current version of this store. Used for migrations (see ).
- /// The initial version is 1.
- ///
- protected override int StoreVersion => 4;
-
- public BeatmapStore(SQLiteConnection connection)
- : base(connection)
+ public BeatmapStore(Func factory)
+ : base(factory)
{
}
- protected override Type[] ValidTypes => new[]
- {
- typeof(BeatmapSetInfo),
- typeof(BeatmapInfo),
- typeof(BeatmapMetadata),
- typeof(BeatmapDifficulty),
- };
-
protected override void Prepare(bool reset = false)
{
if (reset)
{
- Connection.DropTable();
- Connection.DropTable();
- Connection.DropTable();
- Connection.DropTable();
- Connection.DropTable();
- }
+ var context = GetContext();
- Connection.CreateTable();
- Connection.CreateTable();
- Connection.CreateTable();
- Connection.CreateTable();
- Connection.CreateTable();
- }
-
- protected override void StartupTasks()
- {
- base.StartupTasks();
- cleanupPendingDeletions();
- }
-
- ///
- /// Perform migrations between two store versions.
- ///
- /// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected override void PerformMigration(int currentVersion, int targetVersion)
- {
- base.PerformMigration(currentVersion, targetVersion);
-
- while (currentVersion++ < targetVersion)
- {
- switch (currentVersion)
- {
- case 1:
- case 2:
- // cannot migrate; breaking underlying changes.
- Reset();
- break;
- case 3:
- // Added MD5Hash column to BeatmapInfo
- Connection.MigrateTable();
- break;
- case 4:
- // Added Hidden column to BeatmapInfo
- Connection.MigrateTable();
- break;
- }
+ // https://stackoverflow.com/a/10450893
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo");
}
}
@@ -98,10 +46,10 @@ namespace osu.Game.Beatmaps
/// The beatmap to add.
public void Add(BeatmapSetInfo beatmapSet)
{
- Connection.RunInTransaction(() =>
- {
- Connection.InsertOrReplaceWithChildren(beatmapSet, true);
- });
+ var context = GetContext();
+
+ context.BeatmapSetInfo.Attach(beatmapSet);
+ context.SaveChanges();
BeatmapSetAdded?.Invoke(beatmapSet);
}
@@ -113,10 +61,13 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Delete(BeatmapSetInfo beatmapSet)
{
+ var context = GetContext();
+
if (beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = true;
- Connection.Update(beatmapSet);
+ context.Update(beatmapSet);
+ context.SaveChanges();
BeatmapSetRemoved?.Invoke(beatmapSet);
return true;
@@ -129,10 +80,13 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Undelete(BeatmapSetInfo beatmapSet)
{
+ var context = GetContext();
+
if (!beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = false;
- Connection.Update(beatmapSet);
+ context.Update(beatmapSet);
+ context.SaveChanges();
BeatmapSetAdded?.Invoke(beatmapSet);
return true;
@@ -145,10 +99,13 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Hide(BeatmapInfo beatmap)
{
+ var context = GetContext();
+
if (beatmap.Hidden) return false;
beatmap.Hidden = true;
- Connection.Update(beatmap);
+ context.Update(beatmap);
+ context.SaveChanges();
BeatmapHidden?.Invoke(beatmap);
return true;
@@ -161,22 +118,50 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Restore(BeatmapInfo beatmap)
{
+ var context = GetContext();
+
if (!beatmap.Hidden) return false;
beatmap.Hidden = false;
- Connection.Update(beatmap);
+ context.Update(beatmap);
+ context.SaveChanges();
BeatmapRestored?.Invoke(beatmap);
return true;
}
- private void cleanupPendingDeletions()
+ public override void Cleanup()
{
- Connection.RunInTransaction(() =>
- {
- foreach (var b in QueryAndPopulate(b => b.DeletePending && !b.Protected))
- Connection.Delete(b, true);
- });
+ var context = GetContext();
+
+ var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Metadata);
+
+ // metadata is M-N so we can't rely on cascades
+ context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
+ context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
+
+ // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly.
+ context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
+
+ // cascades down to beatmaps.
+ context.BeatmapSetInfo.RemoveRange(purgeable);
+ context.SaveChanges();
}
+
+ public IEnumerable BeatmapSets => GetContext().BeatmapSetInfo
+ .Include(s => s.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Files).ThenInclude(f => f.FileInfo);
+
+ public IEnumerable Beatmaps => GetContext().BeatmapInfo
+ .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
+ .Include(b => b.Metadata)
+ .Include(b => b.Ruleset)
+ .Include(b => b.BaseDifficulty);
}
}
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index 60cbf0ac61..bb6a292d9d 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
foreach (var h in Objects)
- h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
+ h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
PreprocessHitObjects();
}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs
index 9c62289bfa..6e5af29799 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs
@@ -31,6 +31,8 @@ namespace osu.Game.Beatmaps.Drawables
public Action HideDifficultyRequested;
+ public Action EditRequested;
+
public BeatmapSetHeader Header;
public List BeatmapPanels;
@@ -81,13 +83,13 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X,
};
- BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList();
- BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
+ BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b)
{
Alpha = 0,
GainedSelection = panelGainedSelection,
HideRequested = p => HideDifficultyRequested?.Invoke(p),
- StartRequested = p => { StartRequested?.Invoke(p.Beatmap); },
+ StartRequested = p => StartRequested?.Invoke(p.Beatmap),
+ EditRequested = p => EditRequested?.Invoke(p.Beatmap),
RelativeSizeAxes = Axes.X,
}).ToList();
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs
index e216f1b83e..c0705d8f61 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Beatmaps.Drawables
new OsuSpriteText
{
Font = @"Exo2.0-MediumItalic",
- Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author}",
+ Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author.Username}",
TextSize = 16,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index d8cd58d939..b9376849c1 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -22,10 +22,10 @@ namespace osu.Game.Beatmaps
{
Artist = "please load a beatmap!",
Title = "no beatmaps available!",
- Author = "no one",
+ AuthorString = "no one",
},
BeatmapSet = new BeatmapSetInfo(),
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
DrainRate = 0,
CircleSize = 0,
diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
index 81695c3b5a..962c6ad49a 100644
--- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata(),
- Difficulty = new BeatmapDifficulty(),
+ BaseDifficulty = new BeatmapDifficulty(),
},
};
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index 21fee0f465..d775ab409b 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -171,7 +171,7 @@ namespace osu.Game.Beatmaps.Formats
metadata.ArtistUnicode = pair.Value;
break;
case @"Creator":
- metadata.Author = pair.Value;
+ metadata.AuthorString = pair.Value;
break;
case @"Version":
beatmap.BeatmapInfo.Version = pair.Value;
@@ -196,7 +196,7 @@ namespace osu.Game.Beatmaps.Formats
{
var pair = splitKeyVal(line, ':');
- var difficulty = beatmap.BeatmapInfo.Difficulty;
+ var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
switch (pair.Key)
{
case @"HPDrainRate":
@@ -613,7 +613,7 @@ namespace osu.Game.Beatmaps.Formats
string line;
while ((line = stream.ReadLine()) != null)
{
- if (string.IsNullOrEmpty(line))
+ if (string.IsNullOrWhiteSpace(line))
continue;
if (line.StartsWith("//"))
@@ -674,15 +674,17 @@ namespace osu.Game.Beatmaps.Formats
}
foreach (var hitObject in beatmap.HitObjects)
- hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
+ hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
}
private KeyValuePair splitKeyVal(string line, char separator)
{
+ var split = line.Trim().Split(new[] { separator }, 2);
+
return new KeyValuePair
(
- line.Remove(line.IndexOf(separator)).Trim(),
- line.Substring(line.IndexOf(separator) + 1).Trim()
+ split[0].Trim(),
+ split.Length > 1 ? split[1].Trim() : string.Empty
);
}
diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs
index fb307b7144..0cf4a0c65b 100644
--- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs
+++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Beatmaps.Timing
///
/// The minimum duration required for a break to have any effect.
///
- private const double min_break_duration = 650;
+ public const double MIN_BREAK_DURATION = 650;
///
/// The break start time.
@@ -28,6 +28,6 @@ namespace osu.Game.Beatmaps.Timing
///
/// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
///
- public bool HasEffect => Duration >= min_break_duration;
+ public bool HasEffect => Duration >= MIN_BREAK_DURATION;
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 277846ee80..959e71d48d 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -43,6 +43,7 @@ namespace osu.Game.Beatmaps
protected abstract Beatmap GetBeatmap();
protected abstract Texture GetBackground();
protected abstract Track GetTrack();
+ protected virtual Waveform GetWaveform() => new Waveform();
private Beatmap beatmap;
private readonly object beatmapLock = new object();
@@ -96,6 +97,17 @@ namespace osu.Game.Beatmaps
}
}
+ private Waveform waveform;
+ private readonly object waveformLock = new object();
+ public Waveform Waveform
+ {
+ get
+ {
+ lock (waveformLock)
+ return waveform ?? (waveform = GetWaveform());
+ }
+ }
+
public bool TrackLoaded => track != null;
public void TransferTo(WorkingBeatmap other)
@@ -114,6 +126,8 @@ namespace osu.Game.Beatmaps
{
background?.Dispose();
background = null;
+
+ waveform?.Dispose();
}
public void DisposeTrack()
diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs
index d8e2e35bd7..be86d35335 100644
--- a/osu.Game/Database/DatabaseBackedStore.cs
+++ b/osu.Game/Database/DatabaseBackedStore.cs
@@ -2,27 +2,21 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
using osu.Framework.Logging;
using osu.Framework.Platform;
-using SQLite.Net;
-using SQLiteNetExtensions.Extensions;
namespace osu.Game.Database
{
public abstract class DatabaseBackedStore
{
protected readonly Storage Storage;
- protected readonly SQLiteConnection Connection;
- protected virtual int StoreVersion => 1;
+ protected readonly Func GetContext;
- protected DatabaseBackedStore(SQLiteConnection connection, Storage storage = null)
+ protected DatabaseBackedStore(Func getContext, Storage storage = null)
{
Storage = storage;
- Connection = connection;
+ GetContext = getContext;
try
{
@@ -33,46 +27,15 @@ namespace osu.Game.Database
Logger.Error(e, $@"Failed to initialise the {GetType()}! Trying again with a clean database...");
Prepare(true);
}
-
- checkMigrations();
- }
-
- private void checkMigrations()
- {
- var storeName = GetType().Name;
-
- var reportedVersion = Connection.Table().Where(s => s.StoreName == storeName).FirstOrDefault() ?? new StoreVersion
- {
- StoreName = storeName,
- Version = 0
- };
-
- if (reportedVersion.Version != StoreVersion)
- PerformMigration(reportedVersion.Version, reportedVersion.Version = StoreVersion);
-
- Connection.InsertOrReplace(reportedVersion);
-
- StartupTasks();
}
///
- /// Called when the database version of this store doesn't match the local version.
- /// Any manual migration operations should be performed in this.
+ /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary.
///
- /// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected virtual void PerformMigration(int currentVersion, int targetVersion)
+ public virtual void Cleanup()
{
}
- ///
- /// Perform any common startup tasks. Runs after and .
- ///
- protected virtual void StartupTasks()
- {
-
- }
-
///
/// Prepare this database for use. Tables should be created here.
///
@@ -82,50 +45,5 @@ namespace osu.Game.Database
/// Reset this database to a default state. Undo all changes to database and storage backings.
///
public void Reset() => Prepare(true);
-
-
- public TableQuery Query(Expression> filter = null) where T : class
- {
- checkType(typeof(T));
-
- var query = Connection.Table();
-
- if (filter != null)
- query = query.Where(filter);
-
- return query;
- }
-
- ///
- /// Query and populate results.
- ///
- /// An filter to refine results.
- ///
- public List QueryAndPopulate(Expression> filter)
- where T : class
- {
- checkType(typeof(T));
-
- return Connection.GetAllWithChildren(filter, true);
- }
-
- ///
- /// Populate a database-backed item.
- ///
- ///
- /// Whether population should recurse beyond a single level.
- public void Populate(T item, bool recursive = true)
- {
- checkType(item.GetType());
- Connection.GetChildren(item, recursive);
- }
-
- private void checkType(Type type)
- {
- if (!ValidTypes.Contains(type))
- throw new InvalidOperationException($"The requested operation specified a type of {type}, which is invalid for this {nameof(DatabaseBackedStore)}.");
- }
-
- protected abstract Type[] ValidTypes { get; }
}
}
diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs
new file mode 100644
index 0000000000..6154016083
--- /dev/null
+++ b/osu.Game/Database/DatabaseContextFactory.cs
@@ -0,0 +1,27 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Platform;
+
+namespace osu.Game.Database
+{
+ public class DatabaseContextFactory
+ {
+ private readonly GameHost host;
+
+ private const string database_name = @"client";
+
+ public DatabaseContextFactory(GameHost host)
+ {
+ this.host = host;
+ }
+
+ public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
+
+ public void ResetDatabase()
+ {
+ // todo: we probably want to make sure there are no active contexts before performing this operation.
+ host.Storage.DeleteDatabase(database_name);
+ }
+ }
+}
diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs
new file mode 100644
index 0000000000..20d36f3ebe
--- /dev/null
+++ b/osu.Game/Database/OsuDbContext.cs
@@ -0,0 +1,265 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.Extensions.Logging;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps;
+using osu.Game.Input.Bindings;
+using osu.Game.IO;
+using osu.Game.Rulesets;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace osu.Game.Database
+{
+ public class OsuDbContext : DbContext
+ {
+ public DbSet BeatmapInfo { get; set; }
+ public DbSet BeatmapDifficulty { get; set; }
+ public DbSet BeatmapMetadata { get; set; }
+ public DbSet BeatmapSetInfo { get; set; }
+ public DbSet DatabasedKeyBinding { get; set; }
+ public DbSet FileInfo { get; set; }
+ public DbSet RulesetInfo { get; set; }
+ private readonly string connectionString;
+
+ private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory());
+
+ static OsuDbContext()
+ {
+ // required to initialise native SQLite libraries on some platforms.
+ SQLitePCL.Batteries_V2.Init();
+ }
+
+ ///
+ /// Create a new in-memory OsuDbContext instance.
+ ///
+ public OsuDbContext()
+ : this("DataSource=:memory:")
+ {
+ // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0).
+ }
+
+ ///
+ /// Create a new OsuDbContext instance.
+ ///
+ /// A valid SQLite connection string.
+ public OsuDbContext(string connectionString)
+ {
+ this.connectionString = connectionString;
+
+ Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10));
+
+ var connection = Database.GetDbConnection();
+ connection.Open();
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA journal_mode=WAL;";
+ cmd.ExecuteNonQuery();
+ }
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder
+ // this is required for the time being due to the way we are querying in places like BeatmapStore.
+ // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled.
+ .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning))
+ .UseSqlite(connectionString)
+ .UseLoggerFactory(logger.Value);
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity().HasIndex(b => b.MD5Hash);
+ modelBuilder.Entity().HasIndex(b => b.Hash);
+
+ modelBuilder.Entity().HasIndex(b => b.DeletePending);
+ modelBuilder.Entity().HasIndex(b => b.Hash);
+
+ modelBuilder.Entity().HasIndex(b => b.Variant);
+ modelBuilder.Entity().HasIndex(b => b.IntAction);
+
+ modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique();
+ modelBuilder.Entity().HasIndex(b => b.ReferenceCount);
+
+ modelBuilder.Entity().HasIndex(b => b.Available);
+
+ modelBuilder.Entity().HasOne(b => b.BaseDifficulty);
+ }
+
+ private class OsuDbLoggerFactory : ILoggerFactory
+ {
+ #region Disposal
+
+ public void Dispose()
+ {
+ }
+
+ #endregion
+
+ public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
+
+ public void AddProvider(ILoggerProvider provider)
+ {
+ // no-op. called by tooling.
+ }
+
+ private class OsuDbLoggerProvider : ILoggerProvider
+ {
+ #region Disposal
+
+ public void Dispose()
+ {
+ }
+
+ #endregion
+
+ public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
+ }
+
+ private class OsuDbLogger : ILogger
+ {
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (logLevel < LogLevel.Information)
+ return;
+
+ Framework.Logging.LogLevel frameworkLogLevel;
+
+ switch (logLevel)
+ {
+ default:
+ frameworkLogLevel = Framework.Logging.LogLevel.Debug;
+ break;
+ case LogLevel.Warning:
+ frameworkLogLevel = Framework.Logging.LogLevel.Important;
+ break;
+ case LogLevel.Error:
+ case LogLevel.Critical:
+ frameworkLogLevel = Framework.Logging.LogLevel.Error;
+ break;
+ }
+
+ Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel);
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+#if DEBUG
+ return logLevel > LogLevel.Debug;
+#else
+ return logLevel > LogLevel.Information;
+#endif
+ }
+
+ public IDisposable BeginScope(TState state) => null;
+ }
+ }
+
+ public void Migrate()
+ {
+ migrateFromSqliteNet();
+
+ try
+ {
+ Database.Migrate();
+ }
+ catch (Exception e)
+ {
+ throw new MigrationFailedException(e);
+ }
+ }
+
+ private void migrateFromSqliteNet()
+ {
+ try
+ {
+ // will fail if EF hasn't touched the database yet.
+ Database.ExecuteSqlCommand("SELECT * FROM __EFMigrationsHistory LIMIT 1");
+ }
+ catch
+ {
+ try
+ {
+ // will fail (intentionally) if we don't have sqlite-net data present.
+ Database.ExecuteSqlCommand("SELECT OnlineBeatmapSetId FROM BeatmapMetadata LIMIT 1");
+
+ try
+ {
+ Logger.Log("Performing migration from sqlite-net to EF...", LoggingTarget.Database, Framework.Logging.LogLevel.Important);
+
+ // we are good to perform messy migration of data!.
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapDifficulty RENAME TO BeatmapDifficulty_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapMetadata RENAME TO BeatmapMetadata_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE FileInfo RENAME TO FileInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE KeyBinding RENAME TO KeyBinding_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetInfo RENAME TO BeatmapSetInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapInfo RENAME TO BeatmapInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetFileInfo RENAME TO BeatmapSetFileInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE RulesetInfo RENAME TO RulesetInfo_Old");
+
+ Database.ExecuteSqlCommand("DROP TABLE StoreVersion");
+
+ // perform EF migrations to create sane table structure.
+ Database.Migrate();
+
+ // copy data table by table to new structure, dropping old tables as we go.
+ Database.ExecuteSqlCommand("INSERT INTO FileInfo SELECT * FROM FileInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE FileInfo_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO KeyBinding SELECT ID, [Action], Keys, RulesetID, Variant FROM KeyBinding_Old");
+ Database.ExecuteSqlCommand("DROP TABLE KeyBinding_Old");
+
+ Database.ExecuteSqlCommand(
+ "INSERT INTO BeatmapMetadata SELECT ID, Artist, ArtistUnicode, AudioFile, Author, BackgroundFile, PreviewTime, Source, Tags, Title, TitleUnicode FROM BeatmapMetadata_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapMetadata_Old");
+
+ Database.ExecuteSqlCommand(
+ "INSERT INTO BeatmapDifficulty SELECT `ID`, `ApproachRate`, `CircleSize`, `DrainRate`, `OverallDifficulty`, `SliderMultiplier`, `SliderTickRate` FROM BeatmapDifficulty_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapDifficulty_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO BeatmapSetInfo SELECT ID, DeletePending, Hash, BeatmapMetadataID, OnlineBeatmapSetID, Protected FROM BeatmapSetInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapSetInfo_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO BeatmapSetFileInfo SELECT ID, BeatmapSetInfoID, FileInfoID, Filename FROM BeatmapSetFileInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapSetFileInfo_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO RulesetInfo SELECT ID, Available, InstantiationInfo, Name FROM RulesetInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE RulesetInfo_Old");
+
+ Database.ExecuteSqlCommand(
+ "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, IFNULL(Hidden, 0), LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old");
+
+ Logger.Log("Migration complete!", LoggingTarget.Database, Framework.Logging.LogLevel.Important);
+ }
+ catch (Exception e)
+ {
+ throw new MigrationFailedException(e);
+ }
+ }
+ catch (MigrationFailedException e)
+ {
+ throw;
+ }
+ catch
+ {
+ }
+ }
+ }
+ }
+
+ public class MigrationFailedException : Exception
+ {
+ public MigrationFailedException(Exception exception)
+ : base("sqlite-net migration failed", exception)
+ {
+ }
+ }
+}
diff --git a/osu.Game/Database/StoreVersion.cs b/osu.Game/Database/StoreVersion.cs
deleted file mode 100644
index 00314875a6..0000000000
--- a/osu.Game/Database/StoreVersion.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using SQLite.Net.Attributes;
-
-namespace osu.Game.Database
-{
- public class StoreVersion
- {
- [PrimaryKey]
- public string StoreName { get; set; }
-
- public int Version { get; set; }
- }
-}
diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs
new file mode 100644
index 0000000000..3f82ad2179
--- /dev/null
+++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs
@@ -0,0 +1,33 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+
+namespace osu.Game.Graphics.Containers
+{
+ public class OsuHoverContainer : OsuClickableContainer
+ {
+ private Color4 hoverColour;
+
+ protected override bool OnHover(InputState state)
+ {
+ this.FadeColour(hoverColour, 500, Easing.OutQuint);
+ return base.OnHover(state);
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ this.FadeColour(Color4.White, 500, Easing.OutQuint);
+ base.OnHoverLost(state);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ hoverColour = colours.Yellow;
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Processing/RatioAdjust.cs b/osu.Game/Graphics/Processing/RatioAdjust.cs
deleted file mode 100644
index 640814d8e1..0000000000
--- a/osu.Game/Graphics/Processing/RatioAdjust.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using osu.Framework.Graphics.Containers;
-using OpenTK;
-using osu.Framework.Graphics;
-
-namespace osu.Game.Graphics.Processing
-{
- internal class RatioAdjust : Container
- {
- public RatioAdjust()
- {
- RelativeSizeAxes = Axes.Both;
- }
-
- protected override void Update()
- {
- base.Update();
- Vector2 parent = Parent.DrawSize;
-
- Scale = new Vector2(Math.Min(parent.Y / 768f, parent.X / 1024f));
- Size = new Vector2(1 / Scale.X);
- }
- }
-}
diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs
index 1808dc4b6c..afffd930ef 100644
--- a/osu.Game/Graphics/UserInterface/IconButton.cs
+++ b/osu.Game/Graphics/UserInterface/IconButton.cs
@@ -15,32 +15,91 @@ namespace osu.Game.Graphics.UserInterface
{
public class IconButton : OsuClickableContainer
{
- private readonly SpriteIcon icon;
- private readonly Box hover;
- private readonly Container content;
+ private const float button_size = 30;
+ private Color4? flashColour;
+ ///
+ /// The colour that should be flashed when the is clicked.
+ ///
+ public Color4 FlashColour
+ {
+ get { return flashColour ?? Color4.White; }
+ set { flashColour = value; }
+ }
+
+ private Color4? iconColour;
+ ///
+ /// The icon colour. This does not affect .
+ ///
+ public Color4 IconColour
+ {
+ get { return iconColour ?? Color4.White; }
+ set
+ {
+ iconColour = value;
+ icon.Colour = value;
+ }
+ }
+
+ private Color4? iconHoverColour;
+ ///
+ /// The icon colour while the is hovered.
+ ///
+ public Color4 IconHoverColour
+ {
+ get { return iconHoverColour ?? IconColour; }
+ set { iconHoverColour = value; }
+ }
+
+ private Color4? hoverColour;
+ ///
+ /// The background colour of the while it is hovered.
+ ///
+ public Color4 HoverColour
+ {
+ get { return hoverColour ?? Color4.White; }
+ set
+ {
+ hoverColour = value;
+ hover.Colour = value;
+ }
+ }
+
+ ///
+ /// The icon.
+ ///
public FontAwesome Icon
{
get { return icon.Icon; }
set { icon.Icon = value; }
}
- private const float button_size = 30;
- private Color4 flashColour;
-
+ ///
+ /// The icon scale. This does not affect .
+ ///
public Vector2 IconScale
{
get { return icon.Scale; }
set { icon.Scale = value; }
}
+ ///
+ /// The size of the while it is not being pressed.
+ ///
+ public Vector2 ButtonSize
+ {
+ get { return content.Size; }
+ set { content.Size = value; }
+ }
+
+ private readonly Container content;
+ private readonly SpriteIcon icon;
+ private readonly Box hover;
+
public IconButton()
{
AutoSizeAxes = Axes.Both;
- Origin = Anchor.Centre;
- Anchor = Anchor.Centre;
-
Children = new Drawable[]
{
content = new Container
@@ -48,7 +107,6 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(button_size),
-
CornerRadius = 5,
Masking = true,
EdgeEffect = new EdgeEffectParameters
@@ -78,8 +136,11 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- hover.Colour = colours.Yellow.Opacity(0.6f);
- flashColour = colours.Yellow;
+ if (hoverColour == null)
+ HoverColour = colours.Yellow.Opacity(0.6f);
+
+ if (flashColour == null)
+ FlashColour = colours.Yellow;
Enabled.ValueChanged += enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint);
}
@@ -87,18 +148,20 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(InputState state)
{
hover.FadeIn(500, Easing.OutQuint);
+ icon.FadeColour(IconHoverColour, 500, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hover.FadeOut(500, Easing.OutQuint);
+ icon.FadeColour(IconColour, 500, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
- hover.FlashColour(flashColour, 800, Easing.OutQuint);
+ hover.FlashColour(FlashColour, 800, Easing.OutQuint);
return base.OnClick(state);
}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index 89b1f4124b..b053195030 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface
public class OsuTabItem : TabItem, IHasAccentColour
{
protected readonly SpriteText Text;
- private readonly Box box;
+ protected readonly Box Bar;
private Color4 accentColour;
public Color4 AccentColour
@@ -77,13 +77,13 @@ namespace osu.Game.Graphics.UserInterface
private void fadeActive()
{
- box.FadeIn(transition_length, Easing.OutQuint);
+ Bar.FadeIn(transition_length, Easing.OutQuint);
Text.FadeColour(Color4.White, transition_length, Easing.OutQuint);
}
private void fadeInactive()
{
- box.FadeOut(transition_length, Easing.OutQuint);
+ Bar.FadeOut(transition_length, Easing.OutQuint);
Text.FadeColour(AccentColour, transition_length, Easing.OutQuint);
}
@@ -123,7 +123,7 @@ namespace osu.Game.Graphics.UserInterface
TextSize = 14,
Font = @"Exo2.0-Bold", // Font should only turn bold when active?
},
- box = new Box
+ Bar = new Box
{
RelativeSizeAxes = Axes.X,
Height = 1,
diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs
index 367fd68f7b..31b63ecd21 100644
--- a/osu.Game/IO/FileInfo.cs
+++ b/osu.Game/IO/FileInfo.cs
@@ -1,22 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.ComponentModel.DataAnnotations.Schema;
using System.IO;
-using SQLite.Net.Attributes;
namespace osu.Game.IO
{
public class FileInfo
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- [Indexed(Unique = true)]
public string Hash { get; set; }
public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash);
- [Indexed]
public int ReferenceCount { get; set; }
}
}
diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs
index c3d8c1df46..6654fa7cb1 100644
--- a/osu.Game/IO/FileStore.cs
+++ b/osu.Game/IO/FileStore.cs
@@ -4,12 +4,12 @@
using System;
using System.IO;
using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Database;
-using SQLite.Net;
namespace osu.Game.IO
{
@@ -18,75 +18,37 @@ namespace osu.Game.IO
///
public class FileStore : DatabaseBackedStore
{
- private const string prefix = "files";
+ public readonly IResourceStore Store;
- public readonly ResourceStore Store;
+ public Storage Storage => base.Storage;
- protected override int StoreVersion => 2;
-
- public FileStore(SQLiteConnection connection, Storage storage) : base(connection, storage)
+ public FileStore(Func getContext, Storage storage) : base(getContext, storage.GetStorageForDirectory(@"files"))
{
- Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix);
+ Store = new StorageBackedResourceStore(Storage);
}
- protected override Type[] ValidTypes => new[] {
- typeof(FileInfo),
- };
-
protected override void Prepare(bool reset = false)
{
if (reset)
{
- // in earlier versions we stored beatmaps as solid archives, but not any more.
- if (Storage.ExistsDirectory("beatmaps"))
- Storage.DeleteDirectory("beatmaps");
+ if (Storage.ExistsDirectory(string.Empty))
+ Storage.DeleteDirectory(string.Empty);
- if (Storage.ExistsDirectory(prefix))
- Storage.DeleteDirectory(prefix);
-
- Connection.DropTable();
- }
-
- Connection.CreateTable();
- }
-
- protected override void StartupTasks()
- {
- base.StartupTasks();
- deletePending();
- }
-
- ///
- /// Perform migrations between two store versions.
- ///
- /// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected override void PerformMigration(int currentVersion, int targetVersion)
- {
- base.PerformMigration(currentVersion, targetVersion);
-
- while (currentVersion++ < targetVersion)
- {
- switch (currentVersion)
- {
- case 1:
- case 2:
- // cannot migrate; breaking underlying changes.
- Reset();
- break;
- }
+ GetContext().Database.ExecuteSqlCommand("DELETE FROM FileInfo");
}
}
public FileInfo Add(Stream data, bool reference = true)
{
+ var context = GetContext();
+
string hash = data.ComputeSHA2Hash();
- var existing = Connection.Table().Where(f => f.Hash == hash).FirstOrDefault();
+ var existing = context.FileInfo.FirstOrDefault(f => f.Hash == hash);
var info = existing ?? new FileInfo { Hash = hash };
- string path = Path.Combine(prefix, info.StoragePath);
+ string path = info.StoragePath;
// we may be re-adding a file to fix missing store entries.
if (!Storage.Exists(path))
@@ -99,62 +61,58 @@ namespace osu.Game.IO
data.Seek(0, SeekOrigin.Begin);
}
- if (existing == null)
- Connection.Insert(info);
-
if (reference || existing == null)
Reference(info);
return info;
}
- public void Reference(params FileInfo[] files)
- {
- Connection.RunInTransaction(() =>
- {
- var incrementedFiles = files.GroupBy(f => f.ID).Select(f =>
- {
- var accurateRefCount = Connection.Get(f.First().ID);
- accurateRefCount.ReferenceCount += f.Count();
- return accurateRefCount;
- });
+ public void Reference(params FileInfo[] files) => reference(GetContext(), files);
- Connection.UpdateAll(incrementedFiles);
- });
+ private void reference(OsuDbContext context, FileInfo[] files)
+ {
+ foreach (var f in files.GroupBy(f => f.ID))
+ {
+ var refetch = context.Find(f.First().ID) ?? f.First();
+ refetch.ReferenceCount += f.Count();
+ context.FileInfo.Update(refetch);
+ }
+
+ context.SaveChanges();
}
- public void Dereference(params FileInfo[] files)
- {
- Connection.RunInTransaction(() =>
- {
- var incrementedFiles = files.GroupBy(f => f.ID).Select(f =>
- {
- var accurateRefCount = Connection.Get(f.First().ID);
- accurateRefCount.ReferenceCount -= f.Count();
- return accurateRefCount;
- });
+ public void Dereference(params FileInfo[] files) => dereference(GetContext(), files);
- Connection.UpdateAll(incrementedFiles);
- });
+ private void dereference(OsuDbContext context, FileInfo[] files)
+ {
+ foreach (var f in files.GroupBy(f => f.ID))
+ {
+ var refetch = context.FileInfo.Find(f.Key);
+ refetch.ReferenceCount -= f.Count();
+ context.FileInfo.Update(refetch);
+ }
+
+ context.SaveChanges();
}
- private void deletePending()
+ public override void Cleanup()
{
- Connection.RunInTransaction(() =>
+ var context = GetContext();
+
+ foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1))
{
- foreach (var f in Query(f => f.ReferenceCount < 1))
+ try
{
- try
- {
- Storage.Delete(Path.Combine(prefix, f.StoragePath));
- Connection.Delete(f);
- }
- catch (Exception e)
- {
- Logger.Error(e, $@"Could not delete beatmap {f}");
- }
+ Storage.Delete(f.StoragePath);
+ context.FileInfo.Remove(f);
}
- });
+ catch (Exception e)
+ {
+ Logger.Error(e, $@"Could not delete beatmap {f}");
+ }
+ }
+
+ context.SaveChanges();
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
index cbf74d6984..c0cecf361d 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
@@ -1,23 +1,19 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.ComponentModel.DataAnnotations.Schema;
using osu.Framework.Input.Bindings;
-using osu.Game.Rulesets;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Input.Bindings
{
[Table("KeyBinding")]
public class DatabasedKeyBinding : KeyBinding
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- [ForeignKey(typeof(RulesetInfo))]
public int? RulesetID { get; set; }
- [Indexed]
public int? Variant { get; set; }
[Column("Keys")]
@@ -27,7 +23,6 @@ namespace osu.Game.Input.Bindings
private set { KeyCombination = value; }
}
- [Indexed]
[Column("Action")]
public int IntAction
{
@@ -35,4 +30,4 @@ namespace osu.Game.Input.Bindings
set { Action = value; }
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs
index 0a4fcf4389..8a3c65a35e 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs
@@ -51,4 +51,4 @@ namespace osu.Game.Input.Bindings
KeyBindings = store.Query(ruleset?.ID, variant);
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs
index c5ba1683dd..54cf48bc2a 100644
--- a/osu.Game/Input/KeyBindingStore.cs
+++ b/osu.Game/Input/KeyBindingStore.cs
@@ -4,21 +4,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Input.Bindings;
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
-using SQLite.Net;
namespace osu.Game.Input
{
public class KeyBindingStore : DatabaseBackedStore
{
- public KeyBindingStore(SQLiteConnection connection, RulesetStore rulesets, Storage storage = null)
- : base(connection, storage)
+ public KeyBindingStore(Func getContext, RulesetStore rulesets, Storage storage = null)
+ : base(getContext, storage)
{
- foreach (var info in rulesets.AllRulesets)
+ foreach (var info in rulesets.AvailableRulesets)
{
var ruleset = info.CreateInstance();
foreach (var variant in ruleset.AvailableVariants)
@@ -28,66 +28,55 @@ namespace osu.Game.Input
public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings);
- protected override int StoreVersion => 3;
-
- protected override void PerformMigration(int currentVersion, int targetVersion)
- {
- base.PerformMigration(currentVersion, targetVersion);
-
- while (currentVersion++ < targetVersion)
- {
- switch (currentVersion)
- {
- case 1:
- case 2:
- case 3:
- // cannot migrate; breaking underlying changes.
- Reset();
- break;
- }
- }
- }
-
protected override void Prepare(bool reset = false)
{
if (reset)
- Connection.DropTable();
-
- Connection.CreateTable();
+ GetContext().Database.ExecuteSqlCommand("DELETE FROM KeyBinding");
}
private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null)
{
- var query = Query(rulesetId, variant);
+ var context = GetContext();
// compare counts in database vs defaults
foreach (var group in defaults.GroupBy(k => k.Action))
{
- int count;
- while (group.Count() > (count = query.Count(k => (int)k.Action == (int)group.Key)))
- {
- var insertable = group.Skip(count).First();
+ int count = query(context, rulesetId, variant).Count(k => (int)k.Action == (int)group.Key);
+ int aimCount = group.Count();
+ if (aimCount <= count)
+ continue;
+
+ foreach (var insertable in group.Skip(count).Take(aimCount - count))
// insert any defaults which are missing.
- Connection.Insert(new DatabasedKeyBinding
+ context.DatabasedKeyBinding.Add(new DatabasedKeyBinding
{
KeyCombination = insertable.KeyCombination,
Action = insertable.Action,
RulesetID = rulesetId,
Variant = variant
});
- }
}
+
+ context.SaveChanges();
}
- protected override Type[] ValidTypes => new[]
+ ///
+ /// Retrieve s for a specified ruleset/variant content.
+ ///
+ /// The ruleset's internal ID.
+ /// An optional variant.
+ ///
+ public IEnumerable Query(int? rulesetId = null, int? variant = null) => query(GetContext(), rulesetId, variant);
+
+ private IEnumerable query(OsuDbContext context, int? rulesetId = null, int? variant = null) =>
+ context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant);
+
+ public void Update(KeyBinding keyBinding)
{
- typeof(DatabasedKeyBinding)
- };
-
- public IEnumerable Query(int? rulesetId = null, int? variant = null) =>
- Query(b => b.RulesetID == rulesetId && b.Variant == variant);
-
- public void Update(KeyBinding keyBinding) => Connection.Update(keyBinding);
+ var context = GetContext();
+ context.Update(keyBinding);
+ context.SaveChanges();
+ }
}
}
diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs
new file mode 100644
index 0000000000..0820041643
--- /dev/null
+++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs
@@ -0,0 +1,293 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using osu.Game.Database;
+using System;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20171019041408_InitialCreate")]
+ partial class InitialCreate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs
new file mode 100644
index 0000000000..23e5b6f8bb
--- /dev/null
+++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs
@@ -0,0 +1,313 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+using System.Collections.Generic;
+
+namespace osu.Game.Migrations
+{
+ public partial class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "BeatmapDifficulty",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ ApproachRate = table.Column(type: "REAL", nullable: false),
+ CircleSize = table.Column(type: "REAL", nullable: false),
+ DrainRate = table.Column(type: "REAL", nullable: false),
+ OverallDifficulty = table.Column(type: "REAL", nullable: false),
+ SliderMultiplier = table.Column(type: "REAL", nullable: false),
+ SliderTickRate = table.Column(type: "REAL", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapMetadata",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Artist = table.Column(type: "TEXT", nullable: true),
+ ArtistUnicode = table.Column(type: "TEXT", nullable: true),
+ AudioFile = table.Column(type: "TEXT", nullable: true),
+ Author = table.Column(type: "TEXT", nullable: true),
+ BackgroundFile = table.Column(type: "TEXT", nullable: true),
+ PreviewTime = table.Column(type: "INTEGER", nullable: false),
+ Source = table.Column(type: "TEXT", nullable: true),
+ Tags = table.Column(type: "TEXT", nullable: true),
+ Title = table.Column(type: "TEXT", nullable: true),
+ TitleUnicode = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapMetadata", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "FileInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Hash = table.Column(type: "TEXT", nullable: true),
+ ReferenceCount = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_FileInfo", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "KeyBinding",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Action = table.Column(type: "INTEGER", nullable: false),
+ Keys = table.Column(type: "TEXT", nullable: true),
+ RulesetID = table.Column(type: "INTEGER", nullable: true),
+ Variant = table.Column(type: "INTEGER", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_KeyBinding", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "RulesetInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Available = table.Column(type: "INTEGER", nullable: false),
+ InstantiationInfo = table.Column(type: "TEXT", nullable: true),
+ Name = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_RulesetInfo", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapSetInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ DeletePending = table.Column(type: "INTEGER", nullable: false),
+ Hash = table.Column(type: "TEXT", nullable: true),
+ MetadataID = table.Column(type: "INTEGER", nullable: true),
+ OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true),
+ Protected = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID);
+ table.ForeignKey(
+ name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID",
+ column: x => x.MetadataID,
+ principalTable: "BeatmapMetadata",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ AudioLeadIn = table.Column(type: "INTEGER", nullable: false),
+ BaseDifficultyID = table.Column(type: "INTEGER", nullable: false),
+ BeatDivisor = table.Column(type: "INTEGER", nullable: false),
+ BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false),
+ Countdown = table.Column(type: "INTEGER", nullable: false),
+ DistanceSpacing = table.Column(type: "REAL", nullable: false),
+ GridSize = table.Column(type: "INTEGER", nullable: false),
+ Hash = table.Column(type: "TEXT", nullable: true),
+ Hidden = table.Column(type: "INTEGER", nullable: false),
+ LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false),
+ MD5Hash = table.Column(type: "TEXT", nullable: true),
+ MetadataID = table.Column(type: "INTEGER", nullable: true),
+ OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true),
+ Path = table.Column(type: "TEXT", nullable: true),
+ RulesetID = table.Column(type: "INTEGER", nullable: false),
+ SpecialStyle = table.Column(type: "INTEGER", nullable: false),
+ StackLeniency = table.Column(type: "REAL", nullable: false),
+ StarDifficulty = table.Column(type: "REAL", nullable: false),
+ StoredBookmarks = table.Column(type: "TEXT", nullable: true),
+ TimelineZoom = table.Column(type: "REAL", nullable: false),
+ Version = table.Column(type: "TEXT", nullable: true),
+ WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapInfo", x => x.ID);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID",
+ column: x => x.BaseDifficultyID,
+ principalTable: "BeatmapDifficulty",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID",
+ column: x => x.BeatmapSetInfoID,
+ principalTable: "BeatmapSetInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID",
+ column: x => x.MetadataID,
+ principalTable: "BeatmapMetadata",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_RulesetInfo_RulesetID",
+ column: x => x.RulesetID,
+ principalTable: "RulesetInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapSetFileInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false),
+ FileInfoID = table.Column(type: "INTEGER", nullable: false),
+ Filename = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID);
+ table.ForeignKey(
+ name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID",
+ column: x => x.BeatmapSetInfoID,
+ principalTable: "BeatmapSetInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID",
+ column: x => x.FileInfoID,
+ principalTable: "FileInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_BaseDifficultyID",
+ table: "BeatmapInfo",
+ column: "BaseDifficultyID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_BeatmapSetInfoID",
+ table: "BeatmapInfo",
+ column: "BeatmapSetInfoID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_Hash",
+ table: "BeatmapInfo",
+ column: "Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_MD5Hash",
+ table: "BeatmapInfo",
+ column: "MD5Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_MetadataID",
+ table: "BeatmapInfo",
+ column: "MetadataID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_RulesetID",
+ table: "BeatmapInfo",
+ column: "RulesetID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID",
+ table: "BeatmapSetFileInfo",
+ column: "BeatmapSetInfoID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetFileInfo_FileInfoID",
+ table: "BeatmapSetFileInfo",
+ column: "FileInfoID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetInfo_DeletePending",
+ table: "BeatmapSetInfo",
+ column: "DeletePending");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetInfo_Hash",
+ table: "BeatmapSetInfo",
+ column: "Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetInfo_MetadataID",
+ table: "BeatmapSetInfo",
+ column: "MetadataID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_FileInfo_Hash",
+ table: "FileInfo",
+ column: "Hash",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_FileInfo_ReferenceCount",
+ table: "FileInfo",
+ column: "ReferenceCount");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_KeyBinding_Action",
+ table: "KeyBinding",
+ column: "Action");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_KeyBinding_Variant",
+ table: "KeyBinding",
+ column: "Variant");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_RulesetInfo_Available",
+ table: "RulesetInfo",
+ column: "Available");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "BeatmapInfo");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapSetFileInfo");
+
+ migrationBuilder.DropTable(
+ name: "KeyBinding");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapDifficulty");
+
+ migrationBuilder.DropTable(
+ name: "RulesetInfo");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapSetInfo");
+
+ migrationBuilder.DropTable(
+ name: "FileInfo");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapMetadata");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
new file mode 100644
index 0000000000..0576242648
--- /dev/null
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -0,0 +1,292 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using osu.Game.Database;
+using System;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ partial class OsuDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property