diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 35bf9e7a0e..f67d7a8c4e 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,63 +2,70 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
- "command": "msbuild",
- "type": "shell",
- "suppressTaskName": true,
- "args": [
- "/property:GenerateFullPaths=true",
- "/property:DebugType=portable",
- "/verbosity:minimal",
- "/m" //parallel compiling support.
- ],
"tasks": [{
- "taskName": "Build (Debug)",
+ "label": "Build (Debug)",
+ "type": "shell",
+ "command": "msbuild",
+ "args": [
+ "/p:GenerateFullPaths=true",
+ "/p:DebugType=portable",
+ "/m",
+ "/v:m"
+ ],
"group": {
"kind": "build",
"isDefault": true
},
- "problemMatcher": [
- "$msCompile"
- ]
+ "problemMatcher": "$msCompile"
},
{
- "taskName": "Build (Release)",
+ "label": "Build (Release)",
+ "type": "shell",
+ "command": "msbuild",
+ "args": [
+ "/p:Configuration=Release",
+ "/p:DebugType=portable",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/v:m"
+ ],
"group": "build",
- "args": [
- "/property:Configuration=Release"
- ],
- "problemMatcher": [
- "$msCompile"
- ]
+ "problemMatcher": "$msCompile"
},
{
- "taskName": "Clean (Debug)",
+ "label": "Clean (Debug)",
+ "type": "shell",
+ "command": "msbuild",
"args": [
- "/target:Clean"
+ "/p:DebugType=portable",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/t:Clean",
+ "/v:m"
],
- "problemMatcher": [
- "$msCompile"
- ]
+ "problemMatcher": "$msCompile"
},
{
- "taskName": "Clean (Release)",
+ "label": "Clean (Release)",
+ "type": "shell",
+ "command": "msbuild",
"args": [
- "/target:Clean",
- "/property:Configuration=Release"
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/p:DebugType=portable",
+ "/m",
+ "/t:Clean",
+ "/v:m"
],
- "problemMatcher": [
- "$msCompile"
- ]
+ "problemMatcher": "$msCompile"
},
{
- "taskName": "Clean All",
+ "label": "Clean All",
"dependsOn": [
"Clean (Debug)",
"Clean (Release)"
],
- "problemMatcher": [
- "$msCompile"
- ]
+ "problemMatcher": "$msCompile"
}
]
}
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index 9048428590..9cf68803a2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,17 +9,17 @@ cache:
- inspectcode -> appveyor.yml
- packages -> **\packages.config
install:
- - cmd: git submodule update --init --recursive
+ - cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y
- cmd: choco install nvika -y
- cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe
before_build:
- cmd: CodeFileSanity.exe
- - cmd: nuget restore
+ - cmd: nuget restore -verbosity quiet
build:
project: osu.sln
parallel: true
verbosity: minimal
after_build:
- - cmd: inspectcode /o="inspectcodereport.xml" /caches-home="inspectcode" osu.sln
+ - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
\ No newline at end of file
diff --git a/osu-framework b/osu-framework
index fe49ccb3c8..8480ab5009 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit fe49ccb3c8f8661d653752d225ae1dc183944bb4
+Subproject commit 8480ab5009b2b4a7810a817a12433959424d5339
diff --git a/osu-resources b/osu-resources
index 1750ab8f67..4287ee8043 160000
--- a/osu-resources
+++ b/osu-resources
@@ -1 +1 @@
-Subproject commit 1750ab8f6761ab35592fd46da71fbe0c141bfd93
+Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f
diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config
index 2fae7a5e1c..2fbea810f6 100644
--- a/osu.Desktop.Deploy/App.config
+++ b/osu.Desktop.Deploy/App.config
@@ -13,7 +13,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
-
+
diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs
index 54fb50d0f8..e90fb1e567 100644
--- a/osu.Desktop.Deploy/Program.cs
+++ b/osu.Desktop.Deploy/Program.cs
@@ -145,6 +145,8 @@ namespace osu.Desktop.Deploy
///
private static void checkReleaseFiles()
{
+ if (!canGitHub) return;
+
var releaseLines = getReleaseLines();
//ensure we have all files necessary
@@ -157,6 +159,8 @@ namespace osu.Desktop.Deploy
private static void pruneReleases()
{
+ if (!canGitHub) return;
+
write("Pruning RELEASES...");
var releaseLines = getReleaseLines().ToList();
@@ -190,7 +194,7 @@ namespace osu.Desktop.Deploy
private static void uploadBuild(string version)
{
- if (string.IsNullOrEmpty(GitHubAccessToken) || string.IsNullOrEmpty(codeSigningCertPath))
+ if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate))
return;
write("Publishing to GitHub...");
@@ -228,8 +232,12 @@ namespace osu.Desktop.Deploy
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
+ private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken);
+
private static void checkGitHubReleases()
{
+ if (!canGitHub) return;
+
write("Checking GitHub releases...");
var req = new JsonWebRequest>($"{GitHubApiEndpoint}");
req.AuthenticatedBlockingPerform();
diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
index 6727a86a91..3bec56d322 100644
--- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
+++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
@@ -1,6 +1,6 @@
-
+
Debug
AnyCPU
@@ -22,7 +22,6 @@
DEBUG;TRACE
prompt
4
- 6
AnyCPU
@@ -102,9 +101,6 @@
-
- osu.licenseheader
-
PreserveNewest
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 91c0da6f65..e4e9807754 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,5 +1,6 @@
-
+
+
{419659FD-72EA-4678-9EB8-B22A746CED70}
Debug
@@ -62,7 +63,6 @@
false
- 6
none
@@ -98,7 +98,6 @@
full
AnyCPU
false
- 6
prompt
--tests
@@ -174,9 +173,6 @@
-
- osu.licenseheader
-
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 7126b6586d..6b9ec8b9a4 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -11,11 +11,11 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
- internal class CatchBeatmapConverter : BeatmapConverter
+ internal class CatchBeatmapConverter : BeatmapConverter
{
protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
- protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap)
{
var curveData = obj as IHasCurve;
var positionData = obj as IHasXPosition;
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 7fac19d135..9901dbde18 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -1,14 +1,20 @@
// 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.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects.Types;
+using OpenTK;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
- internal class CatchBeatmapProcessor : BeatmapProcessor
+ internal class CatchBeatmapProcessor : BeatmapProcessor
{
- public override void PostProcess(Beatmap beatmap)
+ public override void PostProcess(Beatmap beatmap)
{
if (beatmap.ComboColors.Count == 0)
return;
@@ -16,7 +22,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
int comboIndex = 0;
int colourIndex = 0;
- CatchBaseHit lastObj = null;
+ CatchHitObject lastObj = null;
+
+ initialiseHyperDash(beatmap.HitObjects);
foreach (var obj in beatmap.HitObjects)
{
@@ -34,5 +42,49 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
lastObj = obj;
}
}
+
+ private void initialiseHyperDash(List objects)
+ {
+ // todo: add difficulty adjust.
+ double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
+
+ int lastDirection = 0;
+ double lastExcess = halfCatcherWidth;
+
+ int objCount = objects.Count;
+
+ for (int i = 0; i < objCount - 1; i++)
+ {
+ CatchHitObject currentObject = objects[i];
+
+ // not needed?
+ // if (currentObject is TinyDroplet) continue;
+
+ CatchHitObject nextObject = objects[i + 1];
+
+ // while (nextObject is TinyDroplet)
+ // {
+ // if (++i == objCount - 1) break;
+ // nextObject = objects[i + 1];
+ // }
+
+ int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
+ double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4;
+ double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
+
+ if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext)
+ {
+ currentObject.HyperDashTarget = nextObject;
+ lastExcess = halfCatcherWidth;
+ }
+ else
+ {
+ //currentObject.DistanceToHyperDash = timeToNext - distanceToNext;
+ lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth);
+ }
+
+ lastDirection = thisDirection;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
index b77be9d1f0..e9524a867d 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
@@ -8,14 +8,14 @@ using System.Collections.Generic;
namespace osu.Game.Rulesets.Catch
{
- public class CatchDifficultyCalculator : DifficultyCalculator
+ public class CatchDifficultyCalculator : DifficultyCalculator
{
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
- public override double Calculate(Dictionary categoryDifficulty = null) => 0;
+ public override double Calculate(Dictionary categoryDifficulty = null) => 0;
- protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+ protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
}
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 1d5fc0545e..cb46c75583 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch";
+ public override string ShortName => "fruits";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs b/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs
deleted file mode 100644
index 2f33cf1093..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
-using OpenTK.Graphics;
-
-namespace osu.Game.Rulesets.Catch.Objects
-{
- public abstract class CatchBaseHit : HitObject, IHasXPosition, IHasCombo
- {
- public float X { get; set; }
-
- public Color4 ComboColour { get; set; } = Color4.Gray;
- public int ComboIndex { get; set; }
-
- public virtual bool NewCombo { get; set; }
-
- ///
- /// The next fruit starts a new combo. Used for explodey.
- ///
- public virtual bool LastInCombo { get; set; }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
new file mode 100644
index 0000000000..38757d4928
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects
+{
+ public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
+ {
+ public const double OBJECT_RADIUS = 44;
+
+ public float X { get; set; }
+
+ public Color4 ComboColour { get; set; } = Color4.Gray;
+ public int ComboIndex { get; set; }
+
+ public virtual bool NewCombo { get; set; }
+
+ ///
+ /// The next fruit starts a new combo. Used for explodey.
+ ///
+ public virtual bool LastInCombo { get; set; }
+
+ public float Scale { get; set; } = 1;
+
+ ///
+ /// Whether this fruit can initiate a hyperdash.
+ ///
+ public bool HyperDash => HyperDashTarget != null;
+
+ ///
+ /// The target fruit if we are to initiate a hyperdash.
+ ///
+ public CatchHitObject HyperDashTarget;
+
+ public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ {
+ base.ApplyDefaults(controlPointInfo, difficulty);
+
+ Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index e057bf3d8e..b90a06b94e 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -5,11 +5,12 @@ using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public abstract class DrawableCatchHitObject : DrawableCatchHitObject
- where TObject : CatchBaseHit
+ where TObject : CatchHitObject
{
public new TObject HitObject;
@@ -17,12 +18,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(hitObject)
{
HitObject = hitObject;
+
+ Scale = new Vector2(HitObject.Scale);
}
}
- public abstract class DrawableCatchHitObject : DrawableScrollingHitObject
+ public abstract class DrawableCatchHitObject : DrawableScrollingHitObject
{
- protected DrawableCatchHitObject(CatchBaseHit hitObject)
+ protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject)
{
RelativePositionAxes = Axes.Both;
@@ -30,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Y = (float)HitObject.StartTime;
}
- public Func CheckPosition;
+ public Func CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 4c28a9d021..9f46bbd3a4 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -70,6 +71,20 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
}
}
};
+
+ if (HitObject.HyperDash)
+ {
+ Add(new Pulp
+ {
+ RelativePositionAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AccentColour = Color4.Red,
+ Blending = BlendingMode.Additive,
+ Alpha = 0.5f,
+ Scale = new Vector2(2)
+ });
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
index afda91d0b4..bfb674d1b4 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
};
- foreach (CatchBaseHit tick in s.Ticks)
+ foreach (CatchHitObject tick in s.Ticks)
{
TinyDroplet tiny = tick as TinyDroplet;
if (tiny != null)
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
}
}
- protected override void AddNested(DrawableHitObject h)
+ protected override void AddNested(DrawableHitObject h)
{
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index 00ddd365e3..2de266b3f0 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
{
public class Pulp : Circle, IHasAccentColour
{
- public const float PULP_SIZE = 20;
+ public const float PULP_SIZE = (float)CatchHitObject.OBJECT_RADIUS / 2.2f;
public Pulp()
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
index b1206e0d75..a2bdf830e5 100644
--- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Droplet : CatchBaseHit
+ public class Droplet : CatchHitObject
{
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
index fc55f83969..5f1060fb51 100644
--- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Objects
{
- public class Fruit : CatchBaseHit
+ public class Fruit : CatchHitObject
{
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 6462f6f6a8..bf9f0bd44b 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -15,7 +15,7 @@ using osu.Framework.Lists;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class JuiceStream : CatchBaseHit, IHasCurve
+ public class JuiceStream : CatchHitObject, IHasCurve
{
///
/// Positional distance that results in a duration of one second, before any speed adjustments.
@@ -42,11 +42,11 @@ namespace osu.Game.Rulesets.Catch.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
- public IEnumerable Ticks
+ public IEnumerable Ticks
{
get
{
- SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime));
+ SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime));
if (TickDistance == 0)
return ticks;
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 66a5636b74..0806c4b29d 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -10,14 +10,14 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring
{
- internal class CatchScoreProcessor : ScoreProcessor
+ internal class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(RulesetContainer rulesetContainer)
+ public CatchScoreProcessor(RulesetContainer rulesetContainer)
: base(rulesetContainer)
{
}
- protected override void SimulateAutoplay(Beatmap beatmap)
+ protected override void SimulateAutoplay(Beatmap beatmap)
{
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
index a890a8a386..586de17f15 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
@@ -11,16 +11,26 @@ namespace osu.Game.Rulesets.Catch.Tests
[Ignore("getting CI working")]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{
- public TestCaseCatchStacker() : base(typeof(CatchRuleset))
+ public TestCaseCatchStacker()
+ : base(typeof(CatchRuleset))
{
}
protected override Beatmap CreateBeatmap()
{
- var beatmap = new Beatmap();
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 6,
+ }
+ }
+ };
- for (int i = 0; i < 256; i++)
- beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
+ for (int i = 0; i < 512; i++)
+ beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), 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
deleted file mode 100644
index 341612b760..0000000000
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Tests.Visual;
-using OpenTK;
-
-namespace osu.Game.Rulesets.Catch.Tests
-{
- [TestFixture]
- [Ignore("getting CI working")]
- internal class TestCaseCatcher : OsuTestCase
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(Catcher),
- };
-
- [BackgroundDependencyLoader]
- private void load(RulesetStore rulesets)
- {
- Children = new Drawable[]
- {
- new CatchInputManager(rulesets.GetRuleset(2))
- {
- RelativeSizeAxes = Axes.Both,
- Child = new Catcher
- {
- RelativePositionAxes = Axes.Both,
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Size = new Vector2(1, 0.2f),
- }
- },
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
new file mode 100644
index 0000000000..daa3e12800
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
@@ -0,0 +1,62 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ [Ignore("getting CI working")]
+ internal class TestCaseCatcherArea : OsuTestCase
+ {
+ private RulesetInfo catchRuleset;
+ private TestCatcherArea catcherArea;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherArea),
+ };
+
+ public TestCaseCatcherArea()
+ {
+ AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
+ AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t));
+ }
+
+ private void createCatcher(float size)
+ {
+ Child = new CatchInputManager(catchRuleset)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.BottomLeft
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ catchRuleset = rulesets.GetRuleset(2);
+ }
+
+ private class TestCatcherArea : CatcherArea
+ {
+ public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
+ : base(beatmapDifficulty)
+ {
+ }
+
+ public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
new file mode 100644
index 0000000000..ce3f79bae2
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
@@ -0,0 +1,30 @@
+// 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]
+ [Ignore("getting CI working")]
+ public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer
+ {
+ public TestCaseHyperdash()
+ : base(typeof(CatchRuleset))
+ {
+ }
+
+ protected override Beatmap CreateBeatmap()
+ {
+ var beatmap = new Beatmap();
+
+ for (int i = 0; i < 512; i++)
+ if (i % 5 < 3)
+ beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 });
+
+ return beatmap;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..0d2dc14160
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new CatchRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 987eef5e45..76dbfa77c6 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -1,11 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
@@ -15,15 +15,14 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchPlayfield : ScrollingPlayfield
{
- public static readonly float BASE_WIDTH = 512;
+ public const float BASE_WIDTH = 512;
protected override Container Content => content;
private readonly Container content;
- private readonly Container catcherContainer;
- private readonly Catcher catcher;
+ private readonly CatcherArea catcherArea;
- public CatchPlayfield()
+ public CatchPlayfield(BeatmapDifficulty difficulty)
: base(Axes.Y)
{
Container explodingFruitContainer;
@@ -43,30 +42,16 @@ namespace osu.Game.Rulesets.Catch.UI
{
RelativeSizeAxes = Axes.Both,
},
- catcherContainer = new Container
+ catcherArea = new CatcherArea(difficulty)
{
- RelativeSizeAxes = Axes.X,
+ ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
- 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 bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj);
public override void Add(DrawableHitObject h)
{
@@ -88,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.UI
(judgedObject.Parent as Container)?.Remove(judgedObject);
(judgedObject.Parent as Container)?.Remove(judgedObject);
- catcher.Add(judgedObject, screenPosition);
+ catcherArea.Add(judgedObject, screenPosition);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 92912eb177..3ed9090098 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.UI
{
- public class CatchRulesetContainer : ScrollingRulesetContainer
+ public class CatchRulesetContainer : ScrollingRulesetContainer
{
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset)
@@ -22,15 +22,15 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
- protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor();
+ protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor();
- protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter();
+ protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter();
- protected override Playfield CreatePlayfield() => new CatchPlayfield();
+ protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
- protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h)
+ protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h)
{
var fruit = h as Fruit;
if (fruit != null)
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
deleted file mode 100644
index 87fe95ed2f..0000000000
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ /dev/null
@@ -1,193 +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 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
new file mode 100644
index 0000000000..2bb0f3cd18
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -0,0 +1,342 @@
+// 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.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherArea : Container
+ {
+ public const float CATCHER_SIZE = 172;
+
+ protected readonly Catcher MovableCatcher;
+
+ public Container ExplodingFruitTarget
+ {
+ set { MovableCatcher.ExplodingFruitTarget = value; }
+ }
+
+ public CatcherArea(BeatmapDifficulty difficulty = null)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = CATCHER_SIZE;
+ Child = MovableCatcher = new Catcher(difficulty)
+ {
+ AdditiveTarget = this,
+ };
+ }
+
+ public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
+ {
+ fruit.RelativePositionAxes = Axes.None;
+ fruit.Position = new Vector2(MovableCatcher.ToLocalSpace(absolutePosition).X - MovableCatcher.DrawSize.X / 2, 0);
+
+ fruit.Anchor = Anchor.TopCentre;
+ fruit.Origin = Anchor.BottomCentre;
+ fruit.Scale *= 0.7f;
+ fruit.LifetimeEnd = double.MaxValue;
+
+ MovableCatcher.Add(fruit);
+ }
+
+ public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
+
+ public class Catcher : Container, IKeyBindingHandler
+ {
+ private Texture texture;
+
+ private Container caughtFruit;
+
+ public Container ExplodingFruitTarget;
+
+ public Container AdditiveTarget;
+
+ public Catcher(BeatmapDifficulty difficulty = null)
+ {
+ RelativePositionAxes = Axes.X;
+ X = 0.5f;
+
+ Origin = Anchor.TopCentre;
+ Anchor = Anchor.TopLeft;
+
+ Size = new Vector2(CATCHER_SIZE);
+ if (difficulty != null)
+ Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ }
+
+ [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;
+
+ Trail |= dashing;
+ }
+ }
+
+ private bool trail;
+
+ ///
+ /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
+ ///
+ protected bool Trail
+ {
+ get { return trail; }
+ set
+ {
+ if (value == trail) return;
+
+ trail = value;
+
+ if (Trail)
+ beginTrail();
+ }
+ }
+
+ private void beginTrail()
+ {
+ Trail &= dashing || HyperDashing;
+ Trail &= AdditiveTarget != null;
+
+ if (!Trail) return;
+
+ var additive = createCatcherSprite();
+
+ additive.Anchor = Anchor;
+ additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
+ additive.Position = Position;
+ additive.Scale = Scale;
+ additive.Colour = HyperDashing ? Color4.Red : Color4.White;
+ additive.RelativePositionAxes = RelativePositionAxes;
+ additive.Blending = BlendingMode.Additive;
+
+ AdditiveTarget.Add(additive);
+
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
+
+ Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
+ }
+
+ private Sprite createCatcherSprite() => new Sprite
+ {
+ Size = new Vector2(CATCHER_SIZE),
+ FillMode = FillMode.Fill,
+ Texture = texture,
+ OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
+ };
+
+ ///
+ /// Add a caught fruit to the catcher's stack.
+ ///
+ /// The fruit that was caught.
+ public void Add(DrawableHitObject fruit)
+ {
+ 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);
+
+ var catchObject = (CatchHitObject)fruit.HitObject;
+
+ if (catchObject.LastInCombo)
+ explode();
+ }
+
+ ///
+ /// Let the catcher attempt to catch a fruit.
+ ///
+ /// The fruit to catch.
+ /// Whether the catch is possible.
+ public bool AttemptCatch(CatchHitObject fruit)
+ {
+ const double relative_catcher_width = CATCHER_SIZE / 2;
+
+ // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
+ var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
+ var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
+
+ var validCatch =
+ catchObjectPosition >= catcherPosition - relative_catcher_width / 2 &&
+ catchObjectPosition <= catcherPosition + relative_catcher_width / 2;
+
+ if (validCatch && fruit.HyperDash)
+ {
+ HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED;
+ HyperDashDirection = fruit.HyperDashTarget.X - fruit.X;
+ }
+ else
+ HyperDashModifier = 1;
+
+ return validCatch;
+ }
+
+ ///
+ /// Whether we are hypderdashing or not.
+ ///
+ public bool HyperDashing => hyperDashModifier != 1;
+
+ private double hyperDashModifier = 1;
+
+ ///
+ /// The direction in which hyperdash is allowed. 0 allows both directions.
+ ///
+ public double HyperDashDirection;
+
+ ///
+ /// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
+ ///
+ public double HyperDashModifier
+ {
+ get { return hyperDashModifier; }
+ set
+ {
+ if (value == hyperDashModifier) return;
+ hyperDashModifier = value;
+
+ const float transition_length = 180;
+
+ if (HyperDashing)
+ {
+ this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint);
+ this.FadeTo(0.2f, transition_length, Easing.OutQuint);
+ Trail = true;
+ }
+ else
+ {
+ HyperDashDirection = 0;
+ this.FadeColour(Color4.White, transition_length, Easing.OutQuint);
+ this.FadeTo(1, transition_length, Easing.OutQuint);
+ }
+ }
+ }
+
+ 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.
+ ///
+ public const double BASE_SPEED = 1.0 / 512;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (currentDirection == 0) return;
+
+ var direction = Math.Sign(currentDirection);
+
+ double dashModifier = Dashing ? 1 : 0.5;
+
+ if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
+ dashModifier = hyperDashModifier;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
+ X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
+ }
+
+ 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/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
index a666984b95..b03c8d2eea 100644
--- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
+++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
@@ -1,6 +1,6 @@
-
+
Debug
AnyCPU
@@ -21,7 +21,6 @@
prompt
4
false
- 6
pdbonly
@@ -57,25 +56,24 @@
-
+
-
+
+
-
+
+
-
- osu.licenseheader
-
@@ -92,6 +90,9 @@
True
+
+
+
+
+
+
+
+ 7
+
+
+
+ osu.licenseheader
+
+
+
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 35b6cc2b02..c8390310d4 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
-using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
{
@@ -41,11 +40,6 @@ namespace osu.Game.Beatmaps
///
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
- ///
- /// The Beatmap's Storyboard.
- ///
- public Storyboard Storyboard = new Storyboard();
-
///
/// Constructs a new beatmap.
///
@@ -57,7 +51,6 @@ namespace osu.Game.Beatmaps
Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors;
HitObjects = original?.HitObjects ?? HitObjects;
- Storyboard = original?.Storyboard ?? Storyboard;
if (original == null && Metadata == null)
{
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 962c790fb2..e087eebbfe 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -80,6 +80,7 @@ namespace osu.Game.Beatmaps
///
/// Performs the conversion of a hit object.
+ /// This method is generally executed sequentially for all objects in a beatmap.
///
/// The hit object to convert.
/// The un-converted Beatmap.
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 022d64db03..f3a9694982 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -115,6 +115,7 @@ namespace osu.Game.Beatmaps
// Metadata
public string Version { get; set; }
+ [JsonProperty("difficulty_rating")]
public double StarDifficulty { get; set; }
public bool Equals(BeatmapInfo other)
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 0641cabcd8..b0fbef235b 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -25,6 +25,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
+using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
{
@@ -494,17 +495,23 @@ namespace osu.Game.Beatmaps
BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(mapName)))
- metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
+ metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
+
// check if a set already exists with the same online id.
- beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) ?? new BeatmapSetInfo
- {
- OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
- Beatmaps = new List(),
- Hash = hash,
- Files = fileInfos,
- Metadata = metadata
- };
+ if (metadata.OnlineBeatmapSetID != null)
+ beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID);
+
+ if (beatmapSet == null)
+ beatmapSet = new BeatmapSetInfo
+ {
+ OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
+ Beatmaps = new List(),
+ Hash = hash,
+ Files = fileInfos,
+ Metadata = metadata
+ };
+
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
@@ -517,19 +524,20 @@ namespace osu.Game.Beatmaps
raw.CopyTo(ms);
ms.Position = 0;
- var decoder = BeatmapDecoder.GetDecoder(sr);
- Beatmap beatmap = decoder.Decode(sr);
+ var decoder = Decoder.GetDecoder(sr);
+ Beatmap beatmap = decoder.DecodeBeatmap(sr);
beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
- var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID);
+ var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID);
if (existing == null)
{
- // TODO: Diff beatmap metadata with set metadata and leave it here if necessary
- beatmap.BeatmapInfo.Metadata = null;
+ // Exclude beatmap-metadata if it's equal to beatmapset-metadata
+ if (metadata.Equals(beatmap.Metadata))
+ beatmap.BeatmapInfo.Metadata = null;
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
@@ -568,23 +576,11 @@ namespace osu.Game.Beatmaps
{
try
{
- Beatmap beatmap;
-
- BeatmapDecoder decoder;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
{
- decoder = BeatmapDecoder.GetDecoder(stream);
- beatmap = decoder.Decode(stream);
+ Decoder decoder = Decoder.GetDecoder(stream);
+ return decoder.DecodeBeatmap(stream);
}
-
- if (beatmap == null || BeatmapSetInfo.StoryboardFile == null)
- return beatmap;
-
- using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
- decoder.Decode(stream, beatmap);
-
-
- return beatmap;
}
catch
{
@@ -623,6 +619,28 @@ namespace osu.Game.Beatmaps
}
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
+
+ protected override Storyboard GetStoryboard()
+ {
+ if (BeatmapInfo?.Path == null && BeatmapSetInfo?.StoryboardFile == null)
+ return new Storyboard();
+
+ try
+ {
+ Decoder decoder;
+ using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo?.Path))))
+ decoder = Decoder.GetDecoder(stream);
+
+ // try for .osb first and fall back to .osu
+ string storyboardFile = BeatmapSetInfo.StoryboardFile ?? BeatmapInfo.Path;
+ using (var stream = new StreamReader(store.GetStream(getPathForFile(storyboardFile))))
+ return decoder.GetStoryboardDecoder().DecodeStoryboard(stream);
+ }
+ catch
+ {
+ return new Storyboard();
+ }
+ }
}
///
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 89f9ebf47a..a78ef25166 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
@@ -9,7 +10,7 @@ using osu.Game.Users;
namespace osu.Game.Beatmaps
{
- public class BeatmapMetadata
+ public class BeatmapMetadata : IEquatable
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
@@ -66,5 +67,23 @@ namespace osu.Game.Beatmaps
Source,
Tags
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
+
+ public bool Equals(BeatmapMetadata other)
+ {
+ if (other == null)
+ return false;
+
+ return onlineBeatmapSetID == other.onlineBeatmapSetID
+ && Title == other.Title
+ && TitleUnicode == other.TitleUnicode
+ && Artist == other.Artist
+ && ArtistUnicode == other.ArtistUnicode
+ && AuthorString == other.AuthorString
+ && Source == other.Source
+ && Tags == other.Tags
+ && PreviewTime == other.PreviewTime
+ && AudioFile == other.AudioFile
+ && BackgroundFile == other.BackgroundFile;
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index 3875202e32..fb45c17454 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -32,6 +32,18 @@ namespace osu.Game.Beatmaps
{
var context = GetContext();
+ foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
+ {
+ // If we detect a new metadata object it'll be attached to the current context so it can be reused
+ // to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
+ // of the corresponding table (.Set()) for matching entries to our criteria.
+ var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
+ if (contextMetadata != null)
+ beatmap.Metadata = contextMetadata;
+ else
+ context.BeatmapMetadata.Attach(beatmap.Metadata);
+ }
+
context.BeatmapSetInfo.Attach(beatmapSet);
context.SaveChanges();
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index f58f433cb2..687e1b2177 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps
{
protected double TimeRate = 1;
- public abstract double Calculate(Dictionary categoryDifficulty = null);
+ public abstract double Calculate(Dictionary categoryDifficulty = null);
}
public abstract class DifficultyCalculator : DifficultyCalculator where T : HitObject
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs
index 614ebc236b..ba79db3f48 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs
@@ -11,21 +11,44 @@ namespace osu.Game.Beatmaps.Drawables
public class BeatmapSetCover : Sprite
{
private readonly BeatmapSetInfo set;
- public BeatmapSetCover(BeatmapSetInfo set)
+ private readonly BeatmapSetCoverType type;
+
+ public BeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover)
{
if (set == null)
throw new ArgumentNullException(nameof(set));
this.set = set;
+ this.type = type;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- string resource = set.OnlineInfo.Covers.Cover;
+ string resource = null;
+
+ switch (type)
+ {
+ case BeatmapSetCoverType.Cover:
+ resource = set.OnlineInfo.Covers.Cover;
+ break;
+ case BeatmapSetCoverType.Card:
+ resource = set.OnlineInfo.Covers.Card;
+ break;
+ case BeatmapSetCoverType.List:
+ resource = set.OnlineInfo.Covers.List;
+ break;
+ }
if (resource != null)
Texture = textures.Get(resource);
}
}
+
+ public enum BeatmapSetCoverType
+ {
+ Cover,
+ Card,
+ List,
+ }
}
diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Beatmaps/Drawables/Panel.cs
index d6ed306b39..c990a0ea46 100644
--- a/osu.Game/Beatmaps/Drawables/Panel.cs
+++ b/osu.Game/Beatmaps/Drawables/Panel.cs
@@ -3,12 +3,18 @@
using System;
using osu.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.MathUtils;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
@@ -22,6 +28,10 @@ namespace osu.Game.Beatmaps.Drawables
private readonly Container nestedContainer;
+ private readonly Container borderContainer;
+
+ private readonly Box hoverLayer;
+
protected override Container Content => nestedContainer;
protected Panel()
@@ -29,20 +39,56 @@ namespace osu.Game.Beatmaps.Drawables
Height = MAX_HEIGHT;
RelativeSizeAxes = Axes.X;
- AddInternal(nestedContainer = new Container
+ AddInternal(borderContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
BorderColour = new Color4(221, 255, 255, 255),
+ Children = new Drawable[]
+ {
+ nestedContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ hoverLayer = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ Blending = BlendingMode.Additive,
+ },
+ }
});
Alpha = 0;
}
+ private SampleChannel sampleHover;
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, OsuColour colours)
+ {
+ sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}");
+ hoverLayer.Colour = colours.Blue.Opacity(0.1f);
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ sampleHover?.Play();
+
+ hoverLayer.FadeIn(100, Easing.OutQuint);
+ return base.OnHover(state);
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ hoverLayer.FadeOut(1000, Easing.OutQuint);
+ base.OnHoverLost(state);
+ }
+
public void SetMultiplicativeAlpha(float alpha)
{
- nestedContainer.Alpha = alpha;
+ borderContainer.Alpha = alpha;
}
protected override void LoadComplete()
@@ -94,8 +140,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void Selected()
{
- nestedContainer.BorderThickness = 2.5f;
- nestedContainer.EdgeEffect = new EdgeEffectParameters
+ borderContainer.BorderThickness = 2.5f;
+ borderContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = new Color4(130, 204, 255, 150),
@@ -106,8 +152,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void Deselected()
{
- nestedContainer.BorderThickness = 0;
- nestedContainer.EdgeEffect = new EdgeEffectParameters
+ borderContainer.BorderThickness = 0;
+ borderContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(1),
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index c187aa592a..1434943da0 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
public override string Description => "dummy";
+ public override string ShortName => "dummy";
+
public DummyRuleset(RulesetInfo rulesetInfo)
: base(rulesetInfo)
{
diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
deleted file mode 100644
index 7e1a87085c..0000000000
--- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace osu.Game.Beatmaps.Formats
-{
- public abstract class BeatmapDecoder
- {
- private static readonly Dictionary decoders = new Dictionary();
-
- static BeatmapDecoder()
- {
- OsuLegacyDecoder.Register();
- }
-
- public static BeatmapDecoder GetDecoder(StreamReader stream)
- {
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
-
- string line;
- do { line = stream.ReadLine()?.Trim(); }
- while (line != null && line.Length == 0);
-
- if (line == null || !decoders.ContainsKey(line))
- throw new IOException(@"Unknown file format");
- return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line);
- }
-
- protected static void AddDecoder(string magic) where T : BeatmapDecoder
- {
- decoders[magic] = typeof(T);
- }
-
- public virtual Beatmap Decode(StreamReader stream)
- {
- return ParseFile(stream);
- }
-
- public virtual void Decode(StreamReader stream, Beatmap beatmap)
- {
- ParseFile(stream, beatmap);
- }
-
- protected virtual Beatmap ParseFile(StreamReader stream)
- {
- var beatmap = new Beatmap
- {
- BeatmapInfo = new BeatmapInfo
- {
- Metadata = new BeatmapMetadata(),
- BaseDifficulty = new BeatmapDifficulty(),
- },
- };
-
- ParseFile(stream, beatmap);
- return beatmap;
- }
-
- protected abstract void ParseFile(StreamReader stream, Beatmap beatmap);
- }
-}
diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs
new file mode 100644
index 0000000000..e157150651
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/Decoder.cs
@@ -0,0 +1,80 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ public abstract class Decoder
+ {
+ private static readonly Dictionary decoders = new Dictionary();
+
+ static Decoder()
+ {
+ LegacyDecoder.Register();
+ }
+
+ ///
+ /// Retrieves a to parse a .
+ ///
+ /// A stream pointing to the .
+ public static Decoder GetDecoder(StreamReader stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+
+ string line;
+ do
+ { line = stream.ReadLine()?.Trim(); }
+ while (line != null && line.Length == 0);
+
+ if (line == null || !decoders.ContainsKey(line))
+ throw new IOException(@"Unknown file format");
+ return (Decoder)Activator.CreateInstance(decoders[line], line);
+ }
+
+ ///
+ /// Adds the to the list of and decoder.
+ ///
+ /// Type to decode a with.
+ /// A string representation of the version.
+ protected static void AddDecoder(string version) where T : Decoder
+ {
+ decoders[version] = typeof(T);
+ }
+
+ ///
+ /// Retrieves a to parse a
+ ///
+ public abstract Decoder GetStoryboardDecoder();
+
+ public virtual Beatmap DecodeBeatmap(StreamReader stream)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata(),
+ BaseDifficulty = new BeatmapDifficulty(),
+ },
+ };
+
+ ParseBeatmap(stream, beatmap);
+ return beatmap;
+ }
+
+ protected abstract void ParseBeatmap(StreamReader stream, Beatmap beatmap);
+
+ public virtual Storyboard DecodeStoryboard(StreamReader stream)
+ {
+ var storyboard = new Storyboard();
+ ParseStoryboard(stream, storyboard);
+ return storyboard;
+ }
+
+ protected abstract void ParseStoryboard(StreamReader stream, Storyboard storyboard);
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
new file mode 100644
index 0000000000..b7004dd3eb
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -0,0 +1,421 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Globalization;
+using System.IO;
+using OpenTK.Graphics;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects.Legacy;
+using osu.Game.Beatmaps.ControlPoints;
+using System.Collections.Generic;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ public class LegacyBeatmapDecoder : LegacyDecoder
+ {
+ private Beatmap beatmap;
+
+ private bool hasCustomColours;
+ private ConvertHitObjectParser parser;
+
+ private LegacySampleBank defaultSampleBank;
+ private int defaultSampleVolume = 100;
+
+ public LegacyBeatmapDecoder()
+ {
+ }
+
+ public LegacyBeatmapDecoder(string header)
+ {
+ BeatmapVersion = int.Parse(header.Substring(17));
+ }
+
+ protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+ if (beatmap == null)
+ throw new ArgumentNullException(nameof(beatmap));
+
+ this.beatmap = beatmap;
+ this.beatmap.BeatmapInfo.BeatmapVersion = BeatmapVersion;
+
+ ParseContent(stream);
+
+ foreach (var hitObject in this.beatmap.HitObjects)
+ hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
+ }
+
+ protected override bool ShouldSkipLine(string line)
+ {
+ if (base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"))
+ return true;
+ return false;
+ }
+
+ protected override void ProcessSection(Section section, string line)
+ {
+ switch (section)
+ {
+ case Section.General:
+ handleGeneral(line);
+ break;
+ case Section.Editor:
+ handleEditor(line);
+ break;
+ case Section.Metadata:
+ handleMetadata(line);
+ break;
+ case Section.Difficulty:
+ handleDifficulty(line);
+ break;
+ case Section.Events:
+ handleEvents(line);
+ break;
+ case Section.TimingPoints:
+ handleTimingPoints(line);
+ break;
+ case Section.Colours:
+ handleColours(line);
+ break;
+ case Section.HitObjects:
+ handleHitObjects(line);
+ break;
+ case Section.Variables:
+ handleVariables(line);
+ break;
+ }
+ }
+
+ private void handleGeneral(string line)
+ {
+ var pair = splitKeyVal(line, ':');
+
+ var metadata = beatmap.BeatmapInfo.Metadata;
+ switch (pair.Key)
+ {
+ case @"AudioFilename":
+ metadata.AudioFile = pair.Value;
+ break;
+ case @"AudioLeadIn":
+ beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
+ break;
+ case @"PreviewTime":
+ metadata.PreviewTime = int.Parse(pair.Value);
+ break;
+ case @"Countdown":
+ beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
+ break;
+ case @"SampleSet":
+ defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
+ break;
+ case @"SampleVolume":
+ defaultSampleVolume = int.Parse(pair.Value);
+ break;
+ case @"StackLeniency":
+ beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"Mode":
+ beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
+
+ switch (beatmap.BeatmapInfo.RulesetID)
+ {
+ case 0:
+ parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
+ break;
+ case 1:
+ parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
+ break;
+ case 2:
+ parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
+ break;
+ case 3:
+ parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
+ break;
+ }
+ break;
+ case @"LetterboxInBreaks":
+ beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
+ break;
+ case @"SpecialStyle":
+ beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
+ break;
+ case @"WidescreenStoryboard":
+ beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
+ break;
+ }
+ }
+
+ private void handleEditor(string line)
+ {
+ var pair = splitKeyVal(line, ':');
+
+ switch (pair.Key)
+ {
+ case @"Bookmarks":
+ beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
+ break;
+ case @"DistanceSpacing":
+ beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"BeatDivisor":
+ beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
+ break;
+ case @"GridSize":
+ beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
+ break;
+ case @"TimelineZoom":
+ beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ }
+ }
+
+ private void handleMetadata(string line)
+ {
+ var pair = splitKeyVal(line, ':');
+
+ var metadata = beatmap.BeatmapInfo.Metadata;
+ switch (pair.Key)
+ {
+ case @"Title":
+ metadata.Title = pair.Value;
+ break;
+ case @"TitleUnicode":
+ metadata.TitleUnicode = pair.Value;
+ break;
+ case @"Artist":
+ metadata.Artist = pair.Value;
+ break;
+ case @"ArtistUnicode":
+ metadata.ArtistUnicode = pair.Value;
+ break;
+ case @"Creator":
+ metadata.AuthorString = pair.Value;
+ break;
+ case @"Version":
+ beatmap.BeatmapInfo.Version = pair.Value;
+ break;
+ case @"Source":
+ beatmap.BeatmapInfo.Metadata.Source = pair.Value;
+ break;
+ case @"Tags":
+ beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
+ break;
+ case @"BeatmapID":
+ beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
+ break;
+ case @"BeatmapSetID":
+ beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
+ metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
+ break;
+ }
+ }
+
+ private void handleDifficulty(string line)
+ {
+ var pair = splitKeyVal(line, ':');
+
+ var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
+ switch (pair.Key)
+ {
+ case @"HPDrainRate":
+ difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"CircleSize":
+ difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"OverallDifficulty":
+ difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"ApproachRate":
+ difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"SliderMultiplier":
+ difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ case @"SliderTickRate":
+ difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ break;
+ }
+ }
+
+ private void handleEvents(string line)
+ {
+ DecodeVariables(ref line);
+
+ string[] split = line.Split(',');
+
+ EventType type;
+ if (!Enum.TryParse(split[0], out type))
+ throw new InvalidDataException($@"Unknown event type {split[0]}");
+
+ switch (type)
+ {
+ case EventType.Background:
+ string filename = split[2].Trim('"');
+ beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
+ break;
+ case EventType.Break:
+ var breakEvent = new BreakPeriod
+ {
+ StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
+ EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
+ };
+
+ if (!breakEvent.HasEffect)
+ return;
+
+ beatmap.Breaks.Add(breakEvent);
+ break;
+ }
+ }
+
+ private void handleTimingPoints(string line)
+ {
+ string[] split = line.Split(',');
+
+ double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
+ double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
+ double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
+
+ TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
+ if (split.Length >= 3)
+ timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]);
+
+ LegacySampleBank sampleSet = defaultSampleBank;
+ if (split.Length >= 4)
+ sampleSet = (LegacySampleBank)int.Parse(split[3]);
+
+ //SampleBank sampleBank = SampleBank.Default;
+ //if (split.Length >= 5)
+ // sampleBank = (SampleBank)int.Parse(split[4]);
+
+ int sampleVolume = defaultSampleVolume;
+ if (split.Length >= 6)
+ sampleVolume = int.Parse(split[5]);
+
+ bool timingChange = true;
+ if (split.Length >= 7)
+ timingChange = split[6][0] == '1';
+
+ bool kiaiMode = false;
+ bool omitFirstBarSignature = false;
+ if (split.Length >= 8)
+ {
+ int effectFlags = int.Parse(split[7]);
+ kiaiMode = (effectFlags & 1) > 0;
+ omitFirstBarSignature = (effectFlags & 8) > 0;
+ }
+
+ string stringSampleSet = sampleSet.ToString().ToLower();
+ if (stringSampleSet == @"none")
+ stringSampleSet = @"normal";
+
+ DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
+ SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
+ EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
+
+ if (timingChange)
+ {
+ beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
+ {
+ Time = time,
+ BeatLength = beatLength,
+ TimeSignature = timeSignature
+ });
+ }
+
+ if (speedMultiplier != difficultyPoint.SpeedMultiplier)
+ {
+ beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
+ beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
+ {
+ Time = time,
+ SpeedMultiplier = speedMultiplier
+ });
+ }
+
+ if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
+ {
+ beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
+ {
+ Time = time,
+ SampleBank = stringSampleSet,
+ SampleVolume = sampleVolume
+ });
+ }
+
+ if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
+ {
+ beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
+ {
+ Time = time,
+ KiaiMode = kiaiMode,
+ OmitFirstBarLine = omitFirstBarSignature
+ });
+ }
+ }
+
+ private void handleColours(string line)
+ {
+ var pair = splitKeyVal(line, ':');
+
+ string[] split = pair.Value.Split(',');
+
+ if (split.Length != 3)
+ throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
+
+ byte r, g, b;
+ if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
+ throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
+
+ if (!hasCustomColours)
+ {
+ beatmap.ComboColors.Clear();
+ hasCustomColours = true;
+ }
+
+ // Note: the combo index specified in the beatmap is discarded
+ if (pair.Key.StartsWith(@"Combo"))
+ {
+ beatmap.ComboColors.Add(new Color4
+ {
+ R = r / 255f,
+ G = g / 255f,
+ B = b / 255f,
+ A = 1f,
+ });
+ }
+ }
+
+ private void handleHitObjects(string line)
+ {
+ // If the ruleset wasn't specified, assume the osu!standard ruleset.
+ if (parser == null)
+ parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
+
+ var obj = parser.Parse(line);
+
+ if (obj != null)
+ beatmap.HitObjects.Add(obj);
+ }
+
+ private void handleVariables(string line)
+ {
+ var pair = splitKeyVal(line, '=');
+ Variables[pair.Key] = pair.Value;
+ }
+
+ private KeyValuePair splitKeyVal(string line, char separator)
+ {
+ var split = line.Trim().Split(new[] { separator }, 2);
+
+ return new KeyValuePair
+ (
+ split[0].Trim(),
+ split.Length > 1 ? split[1].Trim() : string.Empty
+ );
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
new file mode 100644
index 0000000000..96747a870d
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -0,0 +1,163 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ public abstract class LegacyDecoder : Decoder
+ {
+ public static void Register()
+ {
+ AddDecoder(@"osu file format v14");
+ AddDecoder(@"osu file format v13");
+ AddDecoder(@"osu file format v12");
+ AddDecoder(@"osu file format v11");
+ AddDecoder(@"osu file format v10");
+ AddDecoder(@"osu file format v9");
+ AddDecoder(@"osu file format v8");
+ AddDecoder(@"osu file format v7");
+ AddDecoder(@"osu file format v6");
+ AddDecoder(@"osu file format v5");
+ AddDecoder(@"osu file format v4");
+ AddDecoder(@"osu file format v3");
+ // TODO: differences between versions
+ }
+
+ protected int BeatmapVersion;
+ protected readonly Dictionary Variables = new Dictionary();
+
+ public override Decoder GetStoryboardDecoder() => new LegacyStoryboardDecoder(BeatmapVersion);
+
+ public override Beatmap DecodeBeatmap(StreamReader stream) => new LegacyBeatmap(base.DecodeBeatmap(stream));
+
+ protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected void ParseContent(StreamReader stream)
+ {
+ Section section = Section.None;
+
+ string line;
+ while ((line = stream.ReadLine()) != null)
+ {
+ if (ShouldSkipLine(line))
+ continue;
+
+ // It's already set in ParseBeatmap... why do it again?
+ //if (line.StartsWith(@"osu file format v"))
+ //{
+ // Beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
+ // continue;
+ //}
+
+ if (line.StartsWith(@"[") && line.EndsWith(@"]"))
+ {
+ if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
+ throw new InvalidDataException($@"Unknown osu section {line}");
+ continue;
+ }
+
+ ProcessSection(section, line);
+ }
+ }
+
+ protected virtual bool ShouldSkipLine(string line)
+ {
+ if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//"))
+ return true;
+ return false;
+ }
+
+ protected abstract void ProcessSection(Section section, string line);
+
+ ///
+ /// Decodes any beatmap variables present in a line into their real values.
+ ///
+ /// The line which may contains variables.
+ protected void DecodeVariables(ref string line)
+ {
+ while (line.IndexOf('$') >= 0)
+ {
+ string origLine = line;
+ string[] split = line.Split(',');
+ for (int i = 0; i < split.Length; i++)
+ {
+ var item = split[i];
+ if (item.StartsWith("$") && Variables.ContainsKey(item))
+ split[i] = Variables[item];
+ }
+
+ line = string.Join(",", split);
+ if (line == origLine)
+ break;
+ }
+ }
+
+ protected enum Section
+ {
+ None,
+ General,
+ Editor,
+ Metadata,
+ Difficulty,
+ Events,
+ TimingPoints,
+ Colours,
+ HitObjects,
+ Variables,
+ }
+
+ internal enum LegacySampleBank
+ {
+ None = 0,
+ Normal = 1,
+ Soft = 2,
+ Drum = 3
+ }
+
+ internal enum EventType
+ {
+ Background = 0,
+ Video = 1,
+ Break = 2,
+ Colour = 3,
+ Sprite = 4,
+ Sample = 5,
+ Animation = 6
+ }
+
+ internal enum LegacyOrigins
+ {
+ TopLeft,
+ Centre,
+ CentreLeft,
+ TopRight,
+ BottomCentre,
+ TopCentre,
+ Custom,
+ CentreRight,
+ BottomLeft,
+ BottomRight
+ };
+
+ internal enum StoryLayer
+ {
+ Background = 0,
+ Fail = 1,
+ Pass = 2,
+ Foreground = 3
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
new file mode 100644
index 0000000000..8da6a0cefb
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -0,0 +1,271 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Globalization;
+using System.IO;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Graphics;
+using osu.Framework.IO.File;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ public class LegacyStoryboardDecoder : LegacyDecoder
+ {
+ private Storyboard storyboard;
+
+ private StoryboardSprite storyboardSprite;
+ private CommandTimelineGroup timelineGroup;
+
+ public LegacyStoryboardDecoder()
+ {
+ }
+
+ public LegacyStoryboardDecoder(int beatmapVersion)
+ {
+ BeatmapVersion = beatmapVersion;
+ }
+
+ protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+ if (storyboard == null)
+ throw new ArgumentNullException(nameof(storyboard));
+
+ this.storyboard = storyboard;
+
+ ParseContent(stream);
+ }
+
+ protected override void ProcessSection(Section section, string line)
+ {
+ switch (section)
+ {
+ case Section.Events:
+ handleEvents(line);
+ break;
+ }
+ }
+
+ private void handleEvents(string line)
+ {
+ var depth = 0;
+ while (line.StartsWith(" ") || line.StartsWith("_"))
+ {
+ ++depth;
+ line = line.Substring(1);
+ }
+
+ DecodeVariables(ref line);
+
+ string[] split = line.Split(',');
+
+ if (depth == 0)
+ {
+ storyboardSprite = null;
+
+ EventType type;
+ if (!Enum.TryParse(split[0], out type))
+ throw new InvalidDataException($@"Unknown event type {split[0]}");
+
+ switch (type)
+ {
+ case EventType.Sprite:
+ {
+ var layer = parseLayer(split[1]);
+ var origin = parseOrigin(split[2]);
+ var path = cleanFilename(split[3]);
+ var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
+ var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
+ storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
+ storyboard.GetLayer(layer).Add(storyboardSprite);
+ }
+ break;
+ case EventType.Animation:
+ {
+ var layer = parseLayer(split[1]);
+ var origin = parseOrigin(split[2]);
+ var path = cleanFilename(split[3]);
+ var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
+ var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
+ var frameCount = int.Parse(split[6]);
+ var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
+ var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
+ storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
+ storyboard.GetLayer(layer).Add(storyboardSprite);
+ }
+ break;
+ case EventType.Sample:
+ {
+ var time = double.Parse(split[1], CultureInfo.InvariantCulture);
+ var layer = parseLayer(split[2]);
+ var path = cleanFilename(split[3]);
+ var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
+ storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
+ }
+ break;
+ }
+ }
+ else
+ {
+ if (depth < 2)
+ timelineGroup = storyboardSprite?.TimelineGroup;
+
+ var commandType = split[0];
+ switch (commandType)
+ {
+ case "T":
+ {
+ var triggerName = split[1];
+ var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
+ var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
+ var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
+ timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
+ }
+ break;
+ case "L":
+ {
+ var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
+ var loopCount = int.Parse(split[2]);
+ timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
+ }
+ break;
+ default:
+ {
+ if (string.IsNullOrEmpty(split[3]))
+ split[3] = split[2];
+
+ var easing = (Easing)int.Parse(split[1]);
+ var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
+ var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
+
+ switch (commandType)
+ {
+ case "F":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
+ }
+ break;
+ case "S":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
+ }
+ break;
+ case "V":
+ {
+ var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
+ var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
+ var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
+ timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
+ }
+ break;
+ case "R":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
+ }
+ break;
+ case "M":
+ {
+ var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
+ var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
+ var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
+ timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
+ timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
+ }
+ break;
+ case "MX":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
+ }
+ break;
+ case "MY":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
+ }
+ break;
+ case "C":
+ {
+ var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
+ var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
+ var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
+ var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
+ var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
+ timelineGroup?.Colour.Add(easing, startTime, endTime,
+ new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
+ new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
+ }
+ break;
+ case "P":
+ {
+ var type = split[4];
+ switch (type)
+ {
+ case "A":
+ timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit);
+ break;
+ case "H":
+ timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
+ break;
+ case "V":
+ timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
+ break;
+ }
+ }
+ break;
+ default:
+ throw new InvalidDataException($@"Unknown command type: {commandType}");
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString();
+
+ private Anchor parseOrigin(string value)
+ {
+ var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
+ switch (origin)
+ {
+ case LegacyOrigins.TopLeft:
+ return Anchor.TopLeft;
+ case LegacyOrigins.TopCentre:
+ return Anchor.TopCentre;
+ case LegacyOrigins.TopRight:
+ return Anchor.TopRight;
+ case LegacyOrigins.CentreLeft:
+ return Anchor.CentreLeft;
+ case LegacyOrigins.Centre:
+ return Anchor.Centre;
+ case LegacyOrigins.CentreRight:
+ return Anchor.CentreRight;
+ case LegacyOrigins.BottomLeft:
+ return Anchor.BottomLeft;
+ case LegacyOrigins.BottomCentre:
+ return Anchor.BottomCentre;
+ case LegacyOrigins.BottomRight:
+ return Anchor.BottomRight;
+ }
+ throw new InvalidDataException($@"Unknown origin: {value}");
+ }
+
+ private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('\"'));
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
deleted file mode 100644
index 11631e9447..0000000000
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ /dev/null
@@ -1,781 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using OpenTK.Graphics;
-using osu.Game.Beatmaps.Timing;
-using osu.Game.Beatmaps.Legacy;
-using osu.Game.Rulesets.Objects.Legacy;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Storyboards;
-using OpenTK;
-using osu.Framework.Graphics;
-using osu.Framework.IO.File;
-
-namespace osu.Game.Beatmaps.Formats
-{
- public class OsuLegacyDecoder : BeatmapDecoder
- {
- public static void Register()
- {
- AddDecoder(@"osu file format v14");
- AddDecoder(@"osu file format v13");
- AddDecoder(@"osu file format v12");
- AddDecoder(@"osu file format v11");
- AddDecoder(@"osu file format v10");
- AddDecoder(@"osu file format v9");
- AddDecoder(@"osu file format v8");
- AddDecoder(@"osu file format v7");
- AddDecoder(@"osu file format v6");
- AddDecoder(@"osu file format v5");
- AddDecoder(@"osu file format v4");
- AddDecoder(@"osu file format v3");
- // TODO: differences between versions
- }
-
- private ConvertHitObjectParser parser;
-
- private readonly Dictionary variables = new Dictionary();
-
- private LegacySampleBank defaultSampleBank;
- private int defaultSampleVolume = 100;
-
- private readonly int beatmapVersion;
-
- public OsuLegacyDecoder()
- {
- }
-
- public OsuLegacyDecoder(string header)
- {
- beatmapVersion = int.Parse(header.Substring(17));
- }
-
- private enum Section
- {
- None,
- General,
- Editor,
- Metadata,
- Difficulty,
- Events,
- TimingPoints,
- Colours,
- HitObjects,
- Variables,
- }
-
- private void handleGeneral(Beatmap beatmap, string line)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var pair = splitKeyVal(line, ':');
-
- var metadata = beatmap.BeatmapInfo.Metadata;
- switch (pair.Key)
- {
- case @"AudioFilename":
- metadata.AudioFile = pair.Value;
- break;
- case @"AudioLeadIn":
- beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
- break;
- case @"PreviewTime":
- metadata.PreviewTime = int.Parse(pair.Value);
- break;
- case @"Countdown":
- beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
- break;
- case @"SampleSet":
- defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
- break;
- case @"SampleVolume":
- defaultSampleVolume = int.Parse(pair.Value);
- break;
- case @"StackLeniency":
- beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"Mode":
- beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
-
- switch (beatmap.BeatmapInfo.RulesetID)
- {
- case 0:
- parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
- break;
- case 1:
- parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
- break;
- case 2:
- parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
- break;
- case 3:
- parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
- break;
- }
- break;
- case @"LetterboxInBreaks":
- beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
- break;
- case @"SpecialStyle":
- beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
- break;
- case @"WidescreenStoryboard":
- beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
- break;
- }
- }
-
- private void handleEditor(Beatmap beatmap, string line)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var pair = splitKeyVal(line, ':');
-
- switch (pair.Key)
- {
- case @"Bookmarks":
- beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
- break;
- case @"DistanceSpacing":
- beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"BeatDivisor":
- beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
- break;
- case @"GridSize":
- beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
- break;
- case @"TimelineZoom":
- beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- }
- }
-
- private void handleMetadata(Beatmap beatmap, string line)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var pair = splitKeyVal(line, ':');
-
- var metadata = beatmap.BeatmapInfo.Metadata;
- switch (pair.Key)
- {
- case @"Title":
- metadata.Title = pair.Value;
- break;
- case @"TitleUnicode":
- metadata.TitleUnicode = pair.Value;
- break;
- case @"Artist":
- metadata.Artist = pair.Value;
- break;
- case @"ArtistUnicode":
- metadata.ArtistUnicode = pair.Value;
- break;
- case @"Creator":
- metadata.AuthorString = pair.Value;
- break;
- case @"Version":
- beatmap.BeatmapInfo.Version = pair.Value;
- break;
- case @"Source":
- beatmap.BeatmapInfo.Metadata.Source = pair.Value;
- break;
- case @"Tags":
- beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
- break;
- case @"BeatmapID":
- beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
- break;
- case @"BeatmapSetID":
- beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
- metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
- break;
- }
- }
-
- private void handleDifficulty(Beatmap beatmap, string line)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var pair = splitKeyVal(line, ':');
-
- var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
- switch (pair.Key)
- {
- case @"HPDrainRate":
- difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"CircleSize":
- difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"OverallDifficulty":
- difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"ApproachRate":
- difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"SliderMultiplier":
- difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- case @"SliderTickRate":
- difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
- break;
- }
- }
-
- ///
- /// Decodes any beatmap variables present in a line into their real values.
- ///
- /// The line which may contains variables.
- private void decodeVariables(ref string line)
- {
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- while (line.IndexOf('$') >= 0)
- {
- string origLine = line;
- string[] split = line.Split(',');
- for (int i = 0; i < split.Length; i++)
- {
- var item = split[i];
- if (item.StartsWith("$") && variables.ContainsKey(item))
- split[i] = variables[item];
- }
-
- line = string.Join(",", split);
- if (line == origLine) break;
- }
- }
-
- private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup)
- {
- if (line == null)
- throw new ArgumentNullException(nameof(line));
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
-
- var depth = 0;
- while (line.StartsWith(" ") || line.StartsWith("_"))
- {
- ++depth;
- line = line.Substring(1);
- }
-
- decodeVariables(ref line);
-
- string[] split = line.Split(',');
-
- if (depth == 0)
- {
- storyboardSprite = null;
-
- EventType type;
- if (!Enum.TryParse(split[0], out type))
- throw new InvalidDataException($@"Unknown event type {split[0]}");
-
- switch (type)
- {
- case EventType.Video:
- case EventType.Background:
- string filename = split[2].Trim('"');
-
- if (type == EventType.Background)
- beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
-
- break;
- case EventType.Break:
- var breakEvent = new BreakPeriod
- {
- StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
- EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
- };
-
- if (!breakEvent.HasEffect)
- return;
-
- beatmap.Breaks.Add(breakEvent);
- break;
- case EventType.Sprite:
- {
- var layer = parseLayer(split[1]);
- var origin = parseOrigin(split[2]);
- var path = cleanFilename(split[3]);
- var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
- var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
- storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
- beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite);
- }
- break;
- case EventType.Animation:
- {
- var layer = parseLayer(split[1]);
- var origin = parseOrigin(split[2]);
- var path = cleanFilename(split[3]);
- var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
- var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
- var frameCount = int.Parse(split[6]);
- var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
- var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
- storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
- beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite);
- }
- break;
- case EventType.Sample:
- {
- var time = double.Parse(split[1], CultureInfo.InvariantCulture);
- var layer = parseLayer(split[2]);
- var path = cleanFilename(split[3]);
- var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
- beatmap.Storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
- }
- break;
- }
- }
- else
- {
- if (depth < 2)
- timelineGroup = storyboardSprite?.TimelineGroup;
-
- var commandType = split[0];
- switch (commandType)
- {
- case "T":
- {
- var triggerName = split[1];
- var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
- var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
- var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
- timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
- }
- break;
- case "L":
- {
- var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
- var loopCount = int.Parse(split[2]);
- timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
- }
- break;
- default:
- {
- if (string.IsNullOrEmpty(split[3]))
- split[3] = split[2];
-
- var easing = (Easing)int.Parse(split[1]);
- var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
- var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
-
- switch (commandType)
- {
- case "F":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
- }
- break;
- case "S":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
- }
- break;
- case "V":
- {
- var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
- var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
- var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
- var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
- timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
- }
- break;
- case "R":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
- }
- break;
- case "M":
- {
- var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
- var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
- var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
- var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
- timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
- timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
- }
- break;
- case "MX":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
- }
- break;
- case "MY":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
- }
- break;
- case "C":
- {
- var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
- var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
- var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
- var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
- var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
- var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
- timelineGroup?.Colour.Add(easing, startTime, endTime,
- new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
- new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
- }
- break;
- case "P":
- {
- var type = split[4];
- switch (type)
- {
- case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break;
- case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break;
- case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break;
- }
- }
- break;
- default:
- throw new InvalidDataException($@"Unknown command type: {commandType}");
- }
- }
- break;
- }
- }
- }
-
- private static string cleanFilename(string path)
- => FileSafety.PathStandardise(path.Trim('\"'));
-
- private static Anchor parseOrigin(string value)
- {
- var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
- switch (origin)
- {
- case LegacyOrigins.TopLeft: return Anchor.TopLeft;
- case LegacyOrigins.TopCentre: return Anchor.TopCentre;
- case LegacyOrigins.TopRight: return Anchor.TopRight;
- case LegacyOrigins.CentreLeft: return Anchor.CentreLeft;
- case LegacyOrigins.Centre: return Anchor.Centre;
- case LegacyOrigins.CentreRight: return Anchor.CentreRight;
- case LegacyOrigins.BottomLeft: return Anchor.BottomLeft;
- case LegacyOrigins.BottomCentre: return Anchor.BottomCentre;
- case LegacyOrigins.BottomRight: return Anchor.BottomRight;
- }
- throw new InvalidDataException($@"Unknown origin: {value}");
- }
-
- private static string parseLayer(string value)
- => Enum.Parse(typeof(StoryLayer), value).ToString();
-
- private void handleTimingPoints(Beatmap beatmap, string line)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- string[] split = line.Split(',');
-
- double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
- double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
- double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
-
- TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
- if (split.Length >= 3)
- timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]);
-
- LegacySampleBank sampleSet = defaultSampleBank;
- if (split.Length >= 4)
- sampleSet = (LegacySampleBank)int.Parse(split[3]);
-
- //SampleBank sampleBank = SampleBank.Default;
- //if (split.Length >= 5)
- // sampleBank = (SampleBank)int.Parse(split[4]);
-
- int sampleVolume = defaultSampleVolume;
- if (split.Length >= 6)
- sampleVolume = int.Parse(split[5]);
-
- bool timingChange = true;
- if (split.Length >= 7)
- timingChange = split[6][0] == '1';
-
- bool kiaiMode = false;
- bool omitFirstBarSignature = false;
- if (split.Length >= 8)
- {
- int effectFlags = int.Parse(split[7]);
- kiaiMode = (effectFlags & 1) > 0;
- omitFirstBarSignature = (effectFlags & 8) > 0;
- }
-
- string stringSampleSet = sampleSet.ToString().ToLower();
- if (stringSampleSet == @"none")
- stringSampleSet = @"normal";
-
- DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
- SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
- EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
-
- if (timingChange)
- {
- beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
- {
- Time = time,
- BeatLength = beatLength,
- TimeSignature = timeSignature
- });
- }
-
- if (speedMultiplier != difficultyPoint.SpeedMultiplier)
- {
- beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
- beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
- {
- Time = time,
- SpeedMultiplier = speedMultiplier
- });
- }
-
- if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
- {
- beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
- {
- Time = time,
- SampleBank = stringSampleSet,
- SampleVolume = sampleVolume
- });
- }
-
- if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
- {
- beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
- {
- Time = time,
- KiaiMode = kiaiMode,
- OmitFirstBarLine = omitFirstBarSignature
- });
- }
- }
-
- private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var pair = splitKeyVal(line, ':');
-
- string[] split = pair.Value.Split(',');
-
- if (split.Length != 3)
- throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
-
- byte r, g, b;
- if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
- throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
-
- if (!hasCustomColours)
- {
- beatmap.ComboColors.Clear();
- hasCustomColours = true;
- }
-
- // Note: the combo index specified in the beatmap is discarded
- if (pair.Key.StartsWith(@"Combo"))
- {
- beatmap.ComboColors.Add(new Color4
- {
- R = r / 255f,
- G = g / 255f,
- B = b / 255f,
- A = 1f,
- });
- }
- }
-
- private void handleVariables(string line)
- {
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var pair = splitKeyVal(line, '=');
- variables[pair.Key] = pair.Value;
- }
-
- protected override Beatmap ParseFile(StreamReader stream)
- {
- return new LegacyBeatmap(base.ParseFile(stream));
- }
-
- public override Beatmap Decode(StreamReader stream)
- {
- return new LegacyBeatmap(base.Decode(stream));
- }
-
- protected override void ParseFile(StreamReader stream, Beatmap beatmap)
- {
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
-
- beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion;
-
- Section section = Section.None;
- bool hasCustomColours = false;
- StoryboardSprite storyboardSprite = null;
- CommandTimelineGroup timelineGroup = null;
-
- string line;
- while ((line = stream.ReadLine()) != null)
- {
- if (string.IsNullOrWhiteSpace(line))
- continue;
-
- if (line.StartsWith("//"))
- continue;
-
- if (line.StartsWith(@"osu file format v"))
- {
- beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
- continue;
- }
-
- if (line.StartsWith(@"[") && line.EndsWith(@"]"))
- {
- if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
- throw new InvalidDataException($@"Unknown osu section {line}");
- continue;
- }
-
- switch (section)
- {
- case Section.General:
- handleGeneral(beatmap, line);
- break;
- case Section.Editor:
- handleEditor(beatmap, line);
- break;
- case Section.Metadata:
- handleMetadata(beatmap, line);
- break;
- case Section.Difficulty:
- handleDifficulty(beatmap, line);
- break;
- case Section.Events:
- handleEvents(beatmap, line, ref storyboardSprite, ref timelineGroup);
- break;
- case Section.TimingPoints:
- handleTimingPoints(beatmap, line);
- break;
- case Section.Colours:
- handleColours(beatmap, line, ref hasCustomColours);
- break;
- case Section.HitObjects:
-
- // If the ruleset wasn't specified, assume the osu!standard ruleset.
- if (parser == null)
- parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
-
- var obj = parser.Parse(line);
-
- if (obj != null)
- beatmap.HitObjects.Add(obj);
-
- break;
- case Section.Variables:
- handleVariables(line);
- break;
- }
- }
-
- foreach (var hitObject in beatmap.HitObjects)
- hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
- }
-
- private KeyValuePair splitKeyVal(string line, char separator)
- {
- if (line == null)
- throw new ArgumentNullException(nameof(line));
-
- var split = line.Trim().Split(new[] { separator }, 2);
-
- return new KeyValuePair
- (
- split[0].Trim(),
- split.Length > 1 ? split[1].Trim() : string.Empty
- );
- }
-
- internal enum LegacySampleBank
- {
- None = 0,
- Normal = 1,
- Soft = 2,
- Drum = 3
- }
-
- internal enum EventType
- {
- Background = 0,
- Video = 1,
- Break = 2,
- Colour = 3,
- Sprite = 4,
- Sample = 5,
- Animation = 6
- }
-
- internal enum LegacyOrigins
- {
- TopLeft,
- Centre,
- CentreLeft,
- TopRight,
- BottomCentre,
- TopCentre,
- Custom,
- CentreRight,
- BottomLeft,
- BottomRight
- };
-
- internal enum StoryLayer
- {
- Background = 0,
- Fail = 1,
- Pass = 2,
- Foreground = 3
- }
- }
-}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 2a8178882e..736cc2a0b0 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
{
@@ -31,17 +32,19 @@ namespace osu.Game.Beatmaps
Mods.ValueChanged += mods => applyRateAdjustments();
beatmap = new AsyncLazy(populateBeatmap);
- background = new AsyncLazy(populateBackground);
+ background = new AsyncLazy(populateBackground, b => b == null || !b.IsDisposed);
track = new AsyncLazy
/// The type of the custom action.
- public abstract class DatabasedKeyBindingInputManager : KeyBindingInputManager
+ public class DatabasedKeyBindingInputManager : KeyBindingContainer
where T : struct
{
private readonly RulesetInfo ruleset;
@@ -31,7 +31,7 @@ namespace osu.Game.Input.Bindings
/// A reference to identify the current . Used to lookup mappings. Null for global mappings.
/// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts.
/// Specify how to deal with multiple matches of s and s.
- protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
+ public DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
: base(simultaneousMode)
{
this.ruleset = ruleset;
diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs
index 95791391f0..9e2988417a 100644
--- a/osu.Game/Input/KeyBindingStore.cs
+++ b/osu.Game/Input/KeyBindingStore.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Input
}
}
- public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings);
+ public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings);
private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null)
{
diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs
new file mode 100644
index 0000000000..6023e88994
--- /dev/null
+++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs
@@ -0,0 +1,307 @@
+//
+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("20171209034410_AddRulesetInfoShortName")]
+ partial class AddRulesetInfoShortName
+ {
+ 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")
+ .IsUnique();
+
+ b.HasIndex("MD5Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ 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")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ 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.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ 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/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs
new file mode 100644
index 0000000000..94519c0b97
--- /dev/null
+++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs
@@ -0,0 +1,35 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+using System.Collections.Generic;
+
+namespace osu.Game.Migrations
+{
+ public partial class AddRulesetInfoShortName : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "ShortName",
+ table: "RulesetInfo",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_RulesetInfo_ShortName",
+ table: "RulesetInfo",
+ column: "ShortName",
+ unique: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_RulesetInfo_ShortName",
+ table: "RulesetInfo");
+
+ migrationBuilder.DropColumn(
+ name: "ShortName",
+ table: "RulesetInfo");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index e3f1cf798b..cd4d3c2854 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -247,10 +247,15 @@ namespace osu.Game.Migrations
b.Property("Name");
+ b.Property("ShortName");
+
b.HasKey("ID");
b.HasIndex("Available");
+ b.HasIndex("ShortName")
+ .IsUnique();
+
b.ToTable("RulesetInfo");
});
diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs
similarity index 90%
rename from osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs
rename to osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs
index 9e412a9b8b..1b30853421 100644
--- a/osu.Game/Online/API/Requests/GetBeatmapSetsResponse.cs
+++ b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs
@@ -10,7 +10,7 @@ using System;
namespace osu.Game.Online.API.Requests
{
- public class GetBeatmapSetsResponse : BeatmapMetadata // todo: this is a bit wrong...
+ public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong...
{
[JsonProperty(@"covers")]
private BeatmapSetOnlineCovers covers { get; set; }
@@ -45,7 +45,7 @@ namespace osu.Game.Online.API.Requests
}
[JsonProperty(@"beatmaps")]
- private IEnumerable beatmaps { get; set; }
+ private IEnumerable beatmaps { get; set; }
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{
@@ -65,11 +65,11 @@ namespace osu.Game.Online.API.Requests
Ranked = ranked,
LastUpdated = lastUpdated,
},
- Beatmaps = beatmaps.Select(b => b.ToBeatmap(rulesets)).ToList(),
+ Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
};
}
- private class GetBeatmapSetsBeatmapResponse : BeatmapMetadata
+ private class APIResponseBeatmap : BeatmapMetadata
{
[JsonProperty(@"id")]
private int onlineBeatmapID { get; set; }
diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs
index e0fdc9adf2..1e6ceaafc6 100644
--- a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs
+++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Online.API.Requests
{
- public class GetBeatmapSetRequest : APIRequest
+ public class GetBeatmapSetRequest : APIRequest
{
private readonly int beatmapSetId;
diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs
index a66799f404..173562e04d 100644
--- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs
+++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace osu.Game.Online.API.Requests
{
- public class GetUserBeatmapsRequest : APIRequest>
+ public class GetUserBeatmapsRequest : APIRequest>
{
private readonly long userId;
private readonly int offset;
@@ -24,7 +24,6 @@ namespace osu.Game.Online.API.Requests
public enum BeatmapSetType
{
- MostPlayed,
Favourite,
RankedAndApproved,
Unranked,
diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs
new file mode 100644
index 0000000000..431a14085f
--- /dev/null
+++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs
@@ -0,0 +1,48 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using Newtonsoft.Json;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using System.Collections.Generic;
+
+namespace osu.Game.Online.API.Requests
+{
+ public class GetUserMostPlayedBeatmapsRequest : APIRequest>
+ {
+ private readonly long userId;
+ private readonly int offset;
+
+ public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0)
+ {
+ this.userId = userId;
+ this.offset = offset;
+ }
+
+ protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}";
+ }
+
+ public class MostPlayedBeatmap
+ {
+ [JsonProperty("beatmap_id")]
+ public int BeatmapID;
+
+ [JsonProperty("count")]
+ public int PlayCount;
+
+ [JsonProperty]
+ private BeatmapInfo beatmap;
+
+ [JsonProperty]
+ private APIResponseBeatmapSet beatmapSet;
+
+ public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets)
+ {
+ BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
+ beatmap.BeatmapSet = setInfo;
+ beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID;
+ beatmap.Metadata = setInfo.Metadata;
+ return beatmap;
+ }
+ }
+}
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
index 56858b3d56..4e6c70124f 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
- public class SearchBeatmapSetsRequest : APIRequest>
+ public class SearchBeatmapSetsRequest : APIRequest>
{
private readonly string query;
private readonly RulesetInfo ruleset;
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 50639e3427..0ddff5e5aa 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Reflection;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Configuration;
using osu.Framework.Development;
using osu.Framework.Graphics;
@@ -137,6 +138,10 @@ namespace osu.Game
Beatmap = new NonNullableBindable(defaultBeatmap);
BeatmapManager.DefaultBeatmap = defaultBeatmap;
+ // tracks play so loud our samples can't keep up.
+ // this adds a global reduction of track volume for the time being.
+ Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8));
+
Beatmap.ValueChanged += b =>
{
var trackLoaded = lastBeatmap?.TrackLoaded ?? false;
@@ -149,7 +154,7 @@ namespace osu.Game
Debug.Assert(lastBeatmap != null);
Debug.Assert(lastBeatmap.Track != null);
- lastBeatmap.DisposeTrack();
+ lastBeatmap.RecycleTrack();
}
Audio.Track.AddItem(b.Track);
diff --git a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/DownloadButton.cs
index 18a0cfd968..47787d2ced 100644
--- a/osu.Game/Overlays/BeatmapSet/DownloadButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/DownloadButton.cs
@@ -14,10 +14,10 @@ namespace osu.Game.Overlays.BeatmapSet
public DownloadButton(string title, string subtitle)
{
Width = 120;
- RelativeSizeAxes = Axes.Y;
- Child = new Container
+ Add(new Container
{
+ Depth = -1,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 10 },
Children = new Drawable[]
@@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet
Margin = new MarginPadding { Right = 5 },
},
},
- };
+ });
}
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs
index 9fd4ac177c..1b22853656 100644
--- a/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/FavouriteButton.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -15,13 +16,12 @@ namespace osu.Game.Overlays.BeatmapSet
{
public readonly Bindable Favourited = new Bindable();
- public FavouriteButton()
+ [BackgroundDependencyLoader]
+ private void load()
{
- RelativeSizeAxes = Axes.Y;
-
Container pink;
SpriteIcon icon;
- Children = new Drawable[]
+ AddRange(new Drawable[]
{
pink = new Container
{
@@ -51,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet
Size = new Vector2(18),
Shadow = false,
},
- };
+ });
Favourited.ValueChanged += value =>
{
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index af59b21713..4135aef268 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -234,7 +234,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void handleBeatmapAdd(BeatmapSetInfo beatmap)
{
- if (beatmap.OnlineBeatmapSetID == BeatmapSet.OnlineBeatmapSetID)
+ if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID)
downloadButtonsContainer.FadeOut(transition_duration);
}
diff --git a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs b/osu.Game/Overlays/BeatmapSet/HeaderButton.cs
index 3075020fe6..ac5683de00 100644
--- a/osu.Game/Overlays/BeatmapSet/HeaderButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/HeaderButton.cs
@@ -2,44 +2,27 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
-using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+using osu.Framework.Allocation;
namespace osu.Game.Overlays.BeatmapSet
{
- public class HeaderButton : OsuClickableContainer
+ public class HeaderButton : TriangleButton
{
- private readonly Container content;
-
- protected override Container Content => content;
-
public HeaderButton()
{
- CornerRadius = 3;
- Masking = true;
+ Height = 0;
+ RelativeSizeAxes = Axes.Y;
+ }
- InternalChildren = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.FromHex(@"094c5f"),
- },
- new Triangles
- {
- RelativeSizeAxes = Axes.Both,
- ColourLight = OsuColour.FromHex(@"0f7c9b"),
- ColourDark = OsuColour.FromHex(@"094c5f"),
- TriangleScale = 1.5f,
- },
- content = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- };
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ BackgroundColour = OsuColour.FromHex(@"094c5f");
+ Triangles.ColourLight = OsuColour.FromHex(@"0f7c9b");
+ Triangles.ColourDark = OsuColour.FromHex(@"094c5f");
+ Triangles.TriangleScale = 1.5f;
}
}
}
diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs
index 4db6bdf5e4..32f933ff42 100644
--- a/osu.Game/Overlays/Chat/ChatLine.cs
+++ b/osu.Game/Overlays/Chat/ChatLine.cs
@@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Chat
}
- private class MessageSender : ClickableContainer, IHasContextMenu
+ private class MessageSender : OsuClickableContainer, IHasContextMenu
{
private readonly User sender;
diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs
index 9f1028c168..f58ee8f819 100644
--- a/osu.Game/Overlays/Chat/ChatTabControl.cs
+++ b/osu.Game/Overlays/Chat/ChatTabControl.cs
@@ -17,6 +17,7 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using System;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat
{
@@ -259,7 +260,7 @@ namespace osu.Game.Overlays.Chat
};
}
- public class CloseButton : ClickableContainer
+ public class CloseButton : OsuClickableContainer
{
private readonly SpriteIcon icon;
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index a55de951f1..5980cde5fd 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Direct
{
base.Update();
- if (PreviewPlaying && Preview != null)
+ if (PreviewPlaying && Preview != null && Preview.IsLoaded)
{
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 9b52cfd367..46ba000a28 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -105,6 +105,7 @@ namespace osu.Game.Overlays.Direct
public enum DirectSortCriteria
{
+ Relevance,
Title,
Artist,
Creator,
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 6f7fabb910..7994483043 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -43,6 +43,7 @@ namespace osu.Game.Overlays
protected override SearchableListFilterControl CreateFilterControl() => new FilterControl();
private IEnumerable beatmapSets;
+
public IEnumerable BeatmapSets
{
get { return beatmapSets; }
@@ -50,7 +51,7 @@ namespace osu.Game.Overlays
{
if (beatmapSets?.Equals(value) ?? false) return;
- beatmapSets = value;
+ beatmapSets = value?.ToList();
if (beatmapSets == null) return;
@@ -65,12 +66,11 @@ namespace osu.Game.Overlays
}
ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags));
-
- recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
}
}
private ResultCounts resultAmounts;
+
public ResultCounts ResultAmounts
{
get { return resultAmounts; }
@@ -117,7 +117,23 @@ namespace osu.Game.Overlays
},
};
- Filter.Search.Current.ValueChanged += text => { if (text != string.Empty) Header.Tabs.Current.Value = DirectTab.Search; };
+ Filter.Search.Current.ValueChanged += text =>
+ {
+ if (text != string.Empty)
+ {
+ Header.Tabs.Current.Value = DirectTab.Search;
+
+ if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked)
+ Filter.Tabs.Current.Value = DirectSortCriteria.Relevance;
+ }
+ else
+ {
+ Header.Tabs.Current.Value = DirectTab.NewestMaps;
+
+ if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance)
+ Filter.Tabs.Current.Value = DirectSortCriteria.Ranked;
+ }
+ };
((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch);
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels;
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch);
@@ -222,7 +238,11 @@ namespace osu.Game.Overlays
switch (displayStyle)
{
case PanelDisplayStyle.Grid:
- return new DirectGridPanel(b);
+ return new DirectGridPanel(b)
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ };
default:
return new DirectListPanel(b);
}
@@ -269,9 +289,9 @@ namespace osu.Game.Overlays
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return;
getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty,
- ((FilterControl)Filter).Ruleset.Value,
- Filter.DisplayStyleControl.Dropdown.Current.Value,
- Filter.Tabs.Current.Value); //todo: sort direction (?)
+ ((FilterControl)Filter).Ruleset.Value,
+ Filter.DisplayStyleControl.Dropdown.Current.Value,
+ Filter.Tabs.Current.Value); //todo: sort direction (?)
getSetsRequest.Success += response =>
{
@@ -282,7 +302,11 @@ namespace osu.Game.Overlays
var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList();
// may not need scheduling; loads async internally.
- Schedule(() => BeatmapSets = sets);
+ Schedule(() =>
+ {
+ BeatmapSets = sets;
+ recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
+ });
});
};
diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs
index 8ebd4ac545..4a7e4f4e6e 100644
--- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs
+++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding
public override FontAwesome Icon => FontAwesome.fa_osu_hot;
public override string Header => "Global";
- public GlobalKeyBindingsSection(KeyBindingInputManager manager)
+ public GlobalKeyBindingsSection(KeyBindingContainer manager)
{
Add(new DefaultBindingsSubsection(manager));
}
@@ -21,7 +21,7 @@ namespace osu.Game.Overlays.KeyBinding
{
protected override string Header => string.Empty;
- public DefaultBindingsSubsection(KeyBindingInputManager manager)
+ public DefaultBindingsSubsection(KeyBindingContainer manager)
: base(null)
{
Defaults = manager.DefaultKeyBindings;
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
index 30ff0ab026..c670cc0153 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Overlays.KeyBinding
}
}
- public class ResetButton : OsuButton
+ public class ResetButton : TriangleButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 0ead4ea019..77b7c3add2 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI;
using System;
using System.Linq;
using osu.Framework.Graphics.Cursor;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Mods
{
@@ -148,7 +149,7 @@ namespace osu.Game.Overlays.Mods
// the mods from Mod, only multiple if Mod is a MultiMod
- public override Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
+ public virtual Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
[BackgroundDependencyLoader]
private void load(AudioManager audio)
@@ -253,6 +254,7 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.TopCentre,
TextSize = 18,
},
+ new HoverClickSounds()
};
Mod = mod;
diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs
index 638c2a0e47..f776c174d2 100644
--- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs
+++ b/osu.Game/Overlays/Mods/ModButtonEmpty.cs
@@ -3,7 +3,6 @@
using OpenTK;
using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods
{
@@ -12,8 +11,6 @@ namespace osu.Game.Overlays.Mods
///
public class ModButtonEmpty : Container
{
- public virtual Mod SelectedMod => null;
-
public ModButtonEmpty()
{
Size = new Vector2(100f);
diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
index af01cdc451..245b2d36ce 100644
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ b/osu.Game/Overlays/Music/PlaylistList.cs
@@ -35,7 +35,11 @@ namespace osu.Game.Overlays.Music
set { base.Padding = value; }
}
- public IEnumerable BeatmapSets { set { items.Sets = value; } }
+ public IEnumerable BeatmapSets
+ {
+ get { return items.Sets; }
+ set { items.Sets = value; }
+ }
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
public BeatmapSetInfo NextSet => items.NextSet;
@@ -48,7 +52,7 @@ namespace osu.Game.Overlays.Music
}
public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
- public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
+ public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
@@ -81,6 +85,7 @@ namespace osu.Game.Overlays.Music
public IEnumerable Sets
{
+ get { return items.Select(x => x.BeatmapSetInfo).ToList(); }
set
{
items.Clear();
@@ -103,12 +108,11 @@ namespace osu.Game.Overlays.Music
});
}
- public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
+ public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
{
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
- if (itemToRemove == null)
- return false;
- return items.Remove(itemToRemove);
+ if (itemToRemove != null)
+ items.Remove(itemToRemove);
}
public BeatmapSetInfo SelectedSet
@@ -230,6 +234,7 @@ namespace osu.Game.Overlays.Music
private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren
{
public IEnumerable FilterTerms => new string[] { };
+
public bool MatchingFilter
{
set
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index d05ad85726..23bec53014 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
+using System.Threading;
namespace osu.Game.Overlays.Music
{
@@ -29,7 +30,7 @@ namespace osu.Game.Overlays.Music
private readonly Bindable beatmapBacking = new Bindable();
- public IEnumerable BeatmapSets;
+ public IEnumerable BeatmapSets => list.BeatmapSets;
[BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours)
@@ -74,11 +75,10 @@ namespace osu.Game.Overlays.Music
},
};
- beatmaps.BeatmapSetAdded += s => Schedule(() => list.AddBeatmapSet(s));
- beatmaps.BeatmapSetRemoved += s => Schedule(() => list.RemoveBeatmapSet(s));
-
- list.BeatmapSets = BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
+ beatmaps.BeatmapSetAdded += list.AddBeatmapSet;
+ beatmaps.BeatmapSetRemoved += list.RemoveBeatmapSet;
+ list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
beatmapBacking.BindTo(game.Beatmap);
@@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Music
return;
}
- playSpecified(set.Beatmaps[0]);
+ playSpecified(set.Beatmaps.First());
}
public void PlayPrevious()
@@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Music
if (playable != null)
{
- playSpecified(playable.Beatmaps[0]);
+ playSpecified(playable.Beatmaps.First());
list.SelectedSet = playable;
}
}
@@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Music
if (playable != null)
{
- playSpecified(playable.Beatmaps[0]);
+ playSpecified(playable.Beatmaps.First());
list.SelectedSet = playable;
}
}
@@ -149,7 +149,15 @@ namespace osu.Game.Overlays.Music
private void playSpecified(BeatmapInfo info)
{
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
- beatmapBacking.Value.Track.Start();
+
+ var track = beatmapBacking.Value.Track;
+
+ track.Restart();
+
+ // this is temporary until we have blocking (async.Wait()) audio component methods.
+ // then we can call RestartAsync().Wait() or the blocking version above.
+ while (!track.IsRunning)
+ Thread.Sleep(1);
}
}
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index 4f57ea1bcd..b8f33c9a60 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -251,7 +251,7 @@ namespace osu.Game.Overlays
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
- if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled)
+ if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any())
next();
}
else
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index c7bc5c1d93..4e19b3153d 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -382,7 +382,7 @@ namespace osu.Game.Overlays.Profile
}
tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location);
- tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Intrerests);
+ tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests);
tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation);
infoTextRight.NewParagraph();
if (!string.IsNullOrEmpty(user.Twitter))
diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs
index 5bd6d30b42..8190ef3c62 100644
--- a/osu.Game/Overlays/Profile/RankChart.cs
+++ b/osu.Game/Overlays/Profile/RankChart.cs
@@ -81,12 +81,12 @@ namespace osu.Game.Overlays.Profile
{
rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank";
performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty;
- relativeText.Text = $"{user.Country?.FullName} #{user.CountryRank:#,0}";
+ relativeText.Text = user.CountryRank > 0 ? $"{user.Country?.FullName} #{user.CountryRank:#,0}" : $"{user.Country?.FullName}";
}
private void showHistoryRankTexts(int dayIndex)
{
- rankText.Text = $"#{ranks[dayIndex]:#,0}";
+ rankText.Text = ranks[dayIndex] > 0 ? $"#{ranks[dayIndex]:#,0}" : "no rank";
dayIndex++;
relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago";
//plural should be handled in a general way
@@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Profile
{
graph.Colour = colours.Yellow;
// use logarithmic coordinates
- graph.Values = ranks.Select(x => -(float)Math.Log(x));
+ graph.Values = ranks.Select(x => x == 0 ? float.MinValue : -(float)Math.Log(x));
graph.SetStaticBallPosition();
}
diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
new file mode 100644
index 0000000000..bfd8db5d9c
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
@@ -0,0 +1,63 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+namespace osu.Game.Overlays.Profile.Sections
+{
+ ///
+ /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ).
+ ///
+ public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip
+ {
+ private readonly BeatmapInfo beatmap;
+
+ public BeatmapMetadataContainer(BeatmapInfo beatmap)
+ {
+ this.beatmap = beatmap;
+ AutoSizeAxes = Axes.Both;
+ TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}";
+ }
+
+ public string TooltipText { get; }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay)
+ {
+ Action = () =>
+ {
+ if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value);
+ };
+
+ Child = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Current = locale.GetUnicodePreference(
+ $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ",
+ $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "
+ ),
+ TextSize = 15,
+ Font = "Exo2.0-SemiBoldItalic",
+ },
+ new OsuSpriteText
+ {
+ Current = locale.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
+ TextSize = 12,
+ Padding = new MarginPadding { Top = 3 },
+ Font = "Exo2.0-RegularItalic",
+ },
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs
new file mode 100644
index 0000000000..0607549f20
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs
@@ -0,0 +1,124 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Overlays.Profile.Sections
+{
+ public abstract class DrawableProfileRow : Container
+ {
+ private const int fade_duration = 200;
+
+ private Box underscoreLine;
+ private readonly Box coloredBackground;
+ private readonly Container background;
+
+ ///
+ /// A visual element displayed to the left of content.
+ ///
+ protected abstract Drawable CreateLeftVisual();
+
+ protected FillFlowContainer LeftFlowContainer { get; private set; }
+ protected FillFlowContainer RightFlowContainer { get; private set; }
+
+ protected override Container Content { get; }
+
+ protected DrawableProfileRow()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = 60;
+ InternalChildren = new Drawable[]
+ {
+ background = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 3,
+ Alpha = 0,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Offset = new Vector2(0f, 1f),
+ Radius = 1f,
+ Colour = Color4.Black.Opacity(0.2f),
+ },
+ Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ Content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Width = 0.97f,
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(OsuColour colour)
+ {
+ AddRange(new Drawable[]
+ {
+ underscoreLine = new Box
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = 1,
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Children = new[]
+ {
+ CreateLeftVisual(),
+ LeftFlowContainer = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Margin = new MarginPadding { Left = 10 },
+ Direction = FillDirection.Vertical,
+ },
+ }
+ },
+ RightFlowContainer = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Direction = FillDirection.Vertical,
+ },
+ });
+
+ coloredBackground.Colour = underscoreLine.Colour = colour.Gray4;
+ }
+
+ protected override bool OnClick(InputState state) => true;
+
+ protected override bool OnHover(InputState state)
+ {
+ background.FadeIn(fade_duration, Easing.OutQuint);
+ underscoreLine.FadeOut(fade_duration, Easing.OutQuint);
+ return true;
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ background.FadeOut(fade_duration, Easing.OutQuint);
+ underscoreLine.FadeIn(fade_duration, Easing.OutQuint);
+ base.OnHoverLost(state);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs
new file mode 100644
index 0000000000..14ab5d8279
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs
@@ -0,0 +1,105 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using OpenTK;
+
+namespace osu.Game.Overlays.Profile.Sections.Historical
+{
+ public class DrawableMostPlayedRow : DrawableProfileRow
+ {
+ private readonly BeatmapInfo beatmap;
+ private readonly int playCount;
+ private OsuHoverContainer mapperContainer;
+
+ public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount)
+ {
+ this.beatmap = beatmap;
+ this.playCount = playCount;
+ }
+
+ protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fit,
+ RelativeSizeAxes = Axes.Both,
+ OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint)
+ })
+ {
+ Origin = Anchor.CentreLeft,
+ Anchor = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.None,
+ Size = new Vector2(80, 50),
+ };
+
+ [BackgroundDependencyLoader(true)]
+ private void load(UserProfileOverlay profileOverlay)
+ {
+ LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap));
+ LeftFlowContainer.Add(new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = @"mapped by ",
+ TextSize = 12,
+ },
+ mapperContainer = new OsuHoverContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = beatmap.Metadata.AuthorString,
+ TextSize = 12,
+ Font = @"Exo2.0-MediumItalic"
+ }
+ }
+ },
+ }
+ });
+
+ RightFlowContainer.Add(new FillFlowContainer
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Children = new[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ Text = playCount.ToString(),
+ TextSize = 18,
+ Font = @"Exo2.0-SemiBoldItalic"
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ Text = @"times played ",
+ TextSize = 12,
+ Font = @"Exo2.0-RegularItalic"
+ },
+ }
+ });
+
+ if (profileOverlay != null)
+ mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs
new file mode 100644
index 0000000000..e54f012faf
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs
@@ -0,0 +1,51 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.API.Requests;
+using osu.Game.Users;
+
+namespace osu.Game.Overlays.Profile.Sections.Historical
+{
+ public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer
+ {
+ public PaginatedMostPlayedBeatmapContainer(Bindable user)
+ :base(user, "Most Played Beatmaps", "No records. :(")
+ {
+ ItemsPerPage = 5;
+
+ ItemsContainer.Direction = FillDirection.Vertical;
+ }
+
+ protected override void ShowMore()
+ {
+ base.ShowMore();
+
+ var req = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage);
+
+ req.Success += beatmaps =>
+ {
+ ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0);
+ ShowMoreLoading.Hide();
+
+ if (!beatmaps.Any() && VisiblePages == 1)
+ {
+ MissingText.Show();
+ return;
+ }
+
+ MissingText.Hide();
+
+ foreach (var beatmap in beatmaps)
+ {
+ ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount));
+ }
+ };
+
+ Api.Queue(req);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs
index a4d043d20a..ab99abdccd 100644
--- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs
+++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs
@@ -1,7 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Overlays.Profile.Sections.Ranks;
namespace osu.Game.Overlays.Profile.Sections
@@ -14,7 +16,11 @@ namespace osu.Game.Overlays.Profile.Sections
public HistoricalSection()
{
- Child = new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :(");
+ Children = new Drawable[]
+ {
+ new PaginatedMostPlayedBeatmapContainer(User),
+ new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("),
+ };
}
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs
index e6ba5b26ac..c3296dae4f 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
- public class DrawablePerformanceScore : DrawableScore
+ public class DrawablePerformanceScore : DrawableProfileScore
{
private readonly double? weight;
@@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
private void load(OsuColour colour)
{
double pp = Score.PP ?? 0;
- Stats.Add(new OsuSpriteText
+ RightFlowContainer.Add(new OsuSpriteText
{
Text = $"{pp:0}pp",
Anchor = Anchor.TopRight,
@@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
if (weight.HasValue)
{
- Stats.Add(new OsuSpriteText
+ RightFlowContainer.Add(new OsuSpriteText
{
Text = $"weighted: {pp * weight:0}pp ({weight:P0})",
Anchor = Anchor.TopRight,
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
new file mode 100644
index 0000000000..06e4684d4c
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.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 OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Select.Leaderboards;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Overlays.Profile.Sections.Ranks
+{
+ public abstract class DrawableProfileScore : DrawableProfileRow
+ {
+ private readonly FillFlowContainer metadata;
+ private readonly ScoreModsContainer modsContainer;
+ protected readonly Score Score;
+
+ protected DrawableProfileScore(Score score)
+ {
+ Score = score;
+
+ RelativeSizeAxes = Axes.X;
+ Height = 60;
+ Children = new Drawable[]
+ {
+ modsContainer = new ScoreModsContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Width = 60,
+ Margin = new MarginPadding { Right = 160 }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(OsuColour colour)
+ {
+ RightFlowContainer.Add(new OsuSpriteText
+ {
+ Text = $"accuracy: {Score.Accuracy:P2}",
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Colour = colour.GrayA,
+ TextSize = 11,
+ Font = "Exo2.0-RegularItalic",
+ Depth = -1,
+ });
+
+ LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap));
+ LeftFlowContainer.Add(new OsuSpriteText
+ {
+ Text = Score.Date.LocalDateTime.ToShortDateString(),
+ TextSize = 11,
+ Colour = OsuColour.Gray(0xAA),
+ });
+
+ foreach (Mod mod in Score.Mods)
+ modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) });
+ }
+
+ protected override Drawable CreateLeftVisual() => new DrawableRank(Score.Rank)
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = 60,
+ FillMode = FillMode.Fit,
+ };
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs
deleted file mode 100644
index 35f4778047..0000000000
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using OpenTK;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Screens.Select.Leaderboards;
-using osu.Framework.Localisation;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
-using OpenTK.Graphics;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Cursor;
-using osu.Framework.Input;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Game.Graphics.Containers;
-
-namespace osu.Game.Overlays.Profile.Sections.Ranks
-{
- public abstract class DrawableScore : Container
- {
- private const int fade_duration = 200;
-
- protected readonly FillFlowContainer Stats;
- private readonly FillFlowContainer metadata;
- private readonly ScoreModsContainer modsContainer;
- protected readonly Score Score;
- private readonly Box underscoreLine;
- private readonly Box coloredBackground;
- private readonly Container background;
-
- protected DrawableScore(Score score)
- {
- Score = score;
-
- RelativeSizeAxes = Axes.X;
- Height = 60;
- Children = new Drawable[]
- {
- background = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- CornerRadius = 3,
- Alpha = 0,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Offset = new Vector2(0f, 1f),
- Radius = 1f,
- Colour = Color4.Black.Opacity(0.2f),
- },
- Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both }
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Width = 0.97f,
- Children = new Drawable[]
- {
- underscoreLine = new Box
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- RelativeSizeAxes = Axes.X,
- Height = 1,
- },
- new DrawableRank(score.Rank)
- {
- RelativeSizeAxes = Axes.Y,
- Width = 60,
- FillMode = FillMode.Fit,
- },
- Stats = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Direction = FillDirection.Vertical,
- },
- metadata = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Margin = new MarginPadding { Left = 70 },
- Direction = FillDirection.Vertical,
- Child = new OsuSpriteText
- {
- Text = score.Date.LocalDateTime.ToShortDateString(),
- TextSize = 11,
- Colour = OsuColour.Gray(0xAA),
- Depth = -1,
- },
- },
- modsContainer = new ScoreModsContainer
- {
- AutoSizeAxes = Axes.Y,
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Width = 60,
- Margin = new MarginPadding { Right = 160 }
- }
- }
- },
- };
- }
-
- [BackgroundDependencyLoader(true)]
- private void load(OsuColour colour, LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay)
- {
- coloredBackground.Colour = underscoreLine.Colour = colour.Gray4;
-
- Stats.Add(new OsuSpriteText
- {
- Text = $"accuracy: {Score.Accuracy:P2}",
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Colour = colour.GrayA,
- TextSize = 11,
- Font = "Exo2.0-RegularItalic",
- Depth = -1,
- });
-
- metadata.Add(new MetadataContainer(Score.Beatmap.Metadata.Title, Score.Beatmap.Metadata.Artist)
- {
- AutoSizeAxes = Axes.Both,
- Action = () =>
- {
- if (Score.Beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(Score.Beatmap.OnlineBeatmapSetID.Value);
- },
- Child = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Current = locale.GetUnicodePreference(
- $"{Score.Beatmap.Metadata.TitleUnicode ?? Score.Beatmap.Metadata.Title} [{Score.Beatmap.Version}] ",
- $"{Score.Beatmap.Metadata.Title ?? Score.Beatmap.Metadata.TitleUnicode} [{Score.Beatmap.Version}] "
- ),
- TextSize = 15,
- Font = "Exo2.0-SemiBoldItalic",
- },
- new OsuSpriteText
- {
- Current = locale.GetUnicodePreference(Score.Beatmap.Metadata.ArtistUnicode, Score.Beatmap.Metadata.Artist),
- TextSize = 12,
- Padding = new MarginPadding { Top = 3 },
- Font = "Exo2.0-RegularItalic",
- },
- },
- },
- });
-
- foreach (Mod mod in Score.Mods)
- modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) });
- }
-
- protected override bool OnClick(InputState state) => true;
-
- protected override bool OnHover(InputState state)
- {
- background.FadeIn(fade_duration, Easing.OutQuint);
- underscoreLine.FadeOut(fade_duration, Easing.OutQuint);
- return true;
- }
-
- protected override void OnHoverLost(InputState state)
- {
- background.FadeOut(fade_duration, Easing.OutQuint);
- underscoreLine.FadeIn(fade_duration, Easing.OutQuint);
- base.OnHoverLost(state);
- }
-
- private class MetadataContainer : OsuHoverContainer, IHasTooltip
- {
- public string TooltipText { get; set; }
-
- public MetadataContainer(string title, string artist)
- {
- TooltipText = $"{artist} - {title}";
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs
index 537b208b39..56b2950f89 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs
@@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
- public class DrawableTotalScore : DrawableScore
+ public class DrawableTotalScore : DrawableProfileScore
{
public DrawableTotalScore(Score score)
: base(score)
@@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
[BackgroundDependencyLoader]
private void load()
{
- Stats.Add(new OsuSpriteText
+ RightFlowContainer.Add(new OsuSpriteText
{
Text = Score.TotalScore.ToString("#,###"),
Anchor = Anchor.TopRight,
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
index dc30934990..472800860c 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
foreach (OnlineScore score in scores)
{
- DrawableScore drawableScore;
+ DrawableProfileScore drawableScore;
switch (type)
{
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs
index 07a8e7464a..9875ee8004 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs
@@ -17,6 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
Children = new Drawable[]
{
+ new SettingsCheckbox
+ {
+ LabelText = "Show converted beatmaps",
+ Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps),
+ },
new SettingsSlider
{
LabelText = "Display beatmaps from",
@@ -33,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
LabelText = "Random beatmap selection",
Bindable = config.GetBindable(OsuSetting.SelectionRandomType),
- },
+ }
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 5ebac37cc8..392bc6f1bd 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
@@ -97,6 +97,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue;
}
}
+
+ public SensitivitySetting()
+ {
+ KeyboardStep = 0.01f;
+ }
}
private class SensitivitySlider : OsuSliderBar
@@ -105,8 +110,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
public SensitivitySlider()
{
- KeyboardStep = 0.01f;
-
Current.ValueChanged += newValue =>
{
if (!isDragging && Sensitivity != null)
@@ -133,4 +136,4 @@ namespace osu.Game.Overlays.Settings.Sections.Input
public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x");
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index 4c82a9ae4b..4f4f381ae1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -12,9 +12,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
public class GeneralSettings : SettingsSubsection
{
- private OsuButton importButton;
- private OsuButton deleteButton;
- private OsuButton restoreButton;
+ private TriangleButton importButton;
+ private TriangleButton deleteButton;
+ private TriangleButton restoreButton;
protected override string Header => "General";
diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs
index 5320cef850..19493f6c70 100644
--- a/osu.Game/Overlays/Settings/SettingsButton.cs
+++ b/osu.Game/Overlays/Settings/SettingsButton.cs
@@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
- public class SettingsButton : OsuButton
+ public class SettingsButton : TriangleButton
{
public SettingsButton()
{
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index d9aac58c54..da50c2b444 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -159,8 +159,6 @@ namespace osu.Game.Overlays.Settings
public string TooltipText => "Revert to default";
- public override bool HandleInput => true;
-
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true;
diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs
index b39c8ab7cf..4b8366f0fc 100644
--- a/osu.Game/Overlays/Settings/SidebarButton.cs
+++ b/osu.Game/Overlays/Settings/SidebarButton.cs
@@ -12,14 +12,14 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
- public class SidebarButton : Container
+ public class SidebarButton : OsuButton
{
private readonly SpriteIcon drawableIcon;
private readonly SpriteText headerText;
- private readonly Box backgroundBox;
private readonly Box selectionIndicator;
private readonly Container text;
public Action Action;
@@ -61,17 +61,14 @@ namespace osu.Game.Overlays.Settings
public SidebarButton()
{
+ BackgroundColour = OsuColour.Gray(60);
+ Background.Alpha = 0;
+
Height = Sidebar.DEFAULT_WIDTH;
RelativeSizeAxes = Axes.X;
- Children = new Drawable[]
+
+ AddRange(new Drawable[]
{
- backgroundBox = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
- Colour = OsuColour.Gray(60),
- Alpha = 0,
- },
text = new Container
{
Width = Sidebar.DEFAULT_WIDTH,
@@ -101,7 +98,7 @@ namespace osu.Game.Overlays.Settings
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
}
- };
+ });
}
[BackgroundDependencyLoader]
@@ -113,20 +110,19 @@ namespace osu.Game.Overlays.Settings
protected override bool OnClick(InputState state)
{
Action?.Invoke(section);
- backgroundBox.FlashColour(Color4.White, 400);
- return true;
+ return base.OnClick(state);
}
protected override bool OnHover(InputState state)
{
- backgroundBox.FadeTo(0.4f, 200);
+ Background.FadeTo(0.4f, 200);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
- backgroundBox.FadeTo(0, 200);
+ Background.FadeTo(0, 200);
base.OnHoverLost(state);
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index cb17216679..c039f9d311 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -13,6 +13,7 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Toolbar
{
@@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar
private readonly SpriteText tooltip2;
protected FillFlowContainer Flow;
- public ToolbarButton()
+ public ToolbarButton() : base(HoverSampleSet.Loud)
{
Width = WIDTH;
RelativeSizeAxes = Axes.Y;
@@ -195,4 +196,4 @@ namespace osu.Game.Overlays.Toolbar
};
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
new file mode 100644
index 0000000000..3184b84e98
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -0,0 +1,109 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Logging;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
+
+namespace osu.Game.Rulesets.Edit
+{
+ public abstract class HitObjectComposer : CompositeDrawable
+ {
+ private readonly Ruleset ruleset;
+
+ protected ICompositionTool CurrentTool { get; private set; }
+
+ protected HitObjectComposer(Ruleset ruleset)
+ {
+ this.ruleset = ruleset;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame)
+ {
+ RulesetContainer rulesetContainer;
+ try
+ {
+ rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value);
+ }
+ catch (Exception e)
+ {
+ Logger.Log($"Could not load this beatmap sucessfully ({e})!", LoggingTarget.Runtime, LogLevel.Error);
+ return;
+ }
+
+ RadioButtonCollection toolboxCollection;
+ InternalChild = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ Name = "Sidebar",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Right = 10 },
+ Children = new Drawable[]
+ {
+ new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderColour = Color4.White,
+ BorderThickness = 2,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ rulesetContainer
+ }
+ }
+ },
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Absolute, 200),
+ }
+ };
+
+ rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock());
+
+ toolboxCollection.Items =
+ new[] { new RadioButton("Select", () => setCompositionTool(null)) }
+ .Concat(
+ CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t)))
+ )
+ .ToList();
+
+ toolboxCollection.Items[0].Select();
+ }
+
+ private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
+
+ protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true);
+
+ protected abstract IReadOnlyList CompositionTools { get; }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs
new file mode 100644
index 0000000000..70e4d3a0c5
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Game.Screens.Play.ReplaySettings;
+
+namespace osu.Game.Rulesets.Edit
+{
+ public class ToolboxGroup : ReplayGroup
+ {
+ protected override string Title => "toolbox";
+
+ public ToolboxGroup()
+ {
+ RelativeSizeAxes = Axes.X;
+ Width = 1;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
new file mode 100644
index 0000000000..dd182dcbdb
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Edit.Tools
+{
+ public class HitObjectCompositionTool : ICompositionTool
+ where T : HitObject
+ {
+ public string Name => typeof(T).Name;
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs
new file mode 100644
index 0000000000..eba873f0cf
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Edit.Tools
+{
+ public interface ICompositionTool
+ {
+ string Name { get; }
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index d4f9c7191a..0d7d617405 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
string[] split = str.Split(':');
- var bank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
- var addbank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
+ var bank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
+ var addbank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
// Let's not implement this for now, because this doesn't fit nicely into the bank structure
//string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs
index b0f62e5271..02c969f648 100644
--- a/osu.Game/Rulesets/Replays/ReplayFrame.cs
+++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Replays
{
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
- public bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
+ public virtual bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
public float? MouseX;
public float? MouseY;
@@ -68,4 +68,4 @@ namespace osu.Game.Rulesets.Replays
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index ed2fdf4157..64a2157069 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -9,7 +9,9 @@ using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets
@@ -21,13 +23,12 @@ namespace osu.Game.Rulesets
public virtual IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { };
public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast()
- // Get all mod types as an IEnumerable
- .SelectMany(GetModsFor)
- // Confine all mods of each mod type into a single IEnumerable
- .Where(mod => mod != null)
- // Filter out all null mods
- .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
- // Resolve MultiMods as their .Mods property
+ // Confine all mods of each mod type into a single IEnumerable
+ .SelectMany(GetModsFor)
+ // Filter out all null mods
+ .Where(mod => mod != null)
+ // Resolve MultiMods as their .Mods property
+ .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
public abstract IEnumerable GetModsFor(ModType type);
@@ -49,6 +50,10 @@ namespace osu.Game.Rulesets
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
+ public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
+
+ public virtual HitObjectComposer CreateHitObjectComposer() => null;
+
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
public abstract string Description { get; }
@@ -60,6 +65,11 @@ namespace osu.Game.Rulesets
///
public virtual int LegacyID => -1;
+ ///
+ /// A unique short name to reference this ruleset in online requests.
+ ///
+ public abstract string ShortName { get; }
+
///
/// A list of available variant ids.
///
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index 17f07158df..7407ede0d0 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets
public string Name { get; set; }
+ public string ShortName { get; set; }
+
public string InstantiationInfo { get; set; }
public bool Available { get; set; }
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 7aba62e4f1..5cdf46ee46 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -83,7 +83,11 @@ namespace osu.Game.Rulesets
{
try
{
- r.CreateInstance();
+ var instance = r.CreateInstance();
+
+ r.Name = instance.Description;
+ r.ShortName = instance.ShortName;
+
r.Available = true;
}
catch
@@ -117,6 +121,7 @@ namespace osu.Game.Rulesets
private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo
{
Name = ruleset.Description,
+ ShortName = ruleset.ShortName,
InstantiationInfo = ruleset.GetType().AssemblyQualifiedName,
ID = ruleset.LegacyID
};
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
new file mode 100644
index 0000000000..4f603049db
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Scoring
+{
+ public abstract class PerformanceCalculator
+ {
+ public abstract double Calculate(Dictionary categoryDifficulty = null);
+ }
+
+ public abstract class PerformanceCalculator : PerformanceCalculator
+ where TObject : HitObject
+ {
+ private readonly Dictionary attributes = new Dictionary();
+ protected IDictionary Attributes => attributes;
+
+ protected readonly Beatmap Beatmap;
+ protected readonly Score Score;
+
+ protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+ {
+ Beatmap = CreateBeatmapConverter().Convert(beatmap);
+ Score = score;
+
+ var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
+ diffCalc.Calculate(attributes);
+ }
+
+ protected abstract BeatmapConverter CreateBeatmapConverter();
+ }
+}
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index ec26f6f310..69bf6bba29 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -55,6 +55,11 @@ namespace osu.Game.Rulesets.UI
public abstract IEnumerable Objects { get; }
+ ///
+ /// The playfield.
+ ///
+ public Playfield Playfield { get; protected set; }
+
protected readonly Ruleset Ruleset;
///
@@ -135,11 +140,6 @@ namespace osu.Game.Rulesets.UI
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this);
- ///
- /// The playfield.
- ///
- public Playfield Playfield { get; private set; }
-
protected override Container Content => content;
private Container content;
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 8c4d6de1fe..5cd79cff29 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Timing;
@@ -16,11 +18,24 @@ using OpenTK.Input;
namespace osu.Game.Rulesets.UI
{
- public abstract class RulesetInputManager : DatabasedKeyBindingInputManager, ICanAttachKeyCounter, IHasReplayHandler
+ public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler
where T : struct
{
- protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique)
+ public class RulesetKeyBindingContainer : DatabasedKeyBindingInputManager
{
+ public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ : base(ruleset, variant, unique)
+ {
+ }
+ }
+
+ protected readonly KeyBindingContainer KeyBindingContainer;
+
+ protected override Container Content => KeyBindingContainer;
+
+ protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ {
+ InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique);
}
#region Action mapping (for replays)
@@ -41,10 +56,10 @@ namespace osu.Game.Rulesets.UI
List newActions = replayState.PressedActions;
foreach (var released in lastPressedActions.Except(newActions))
- PropagateReleased(KeyBindingInputQueue, released);
+ KeyBindingContainer.TriggerReleased(released);
foreach (var pressed in newActions.Except(lastPressedActions))
- PropagatePressed(KeyBindingInputQueue, pressed);
+ KeyBindingContainer.TriggerPressed(pressed);
lastPressedActions = newActions;
}
@@ -203,7 +218,7 @@ namespace osu.Game.Rulesets.UI
Add(receptor);
keyCounter.SetReceptor(receptor);
- keyCounter.AddRange(DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b)));
+ keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b)));
}
public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
index 229d06ef09..df95a5c384 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
@@ -40,7 +40,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
return;
}
- timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
+ // Todo: This should be handled more gracefully
+ timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
}
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs
index 2349c261cf..6bc7356f26 100644
--- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs
+++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs
@@ -6,49 +6,99 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Screens.Edit.Screens.Compose
{
public class Compose : EditorScreen
{
+ private const float vertical_margins = 10;
+ private const float horizontal_margins = 20;
+
+ private readonly Container composerContainer;
+
public Compose()
{
ScrollableTimeline timeline;
- Children = new[]
+ Children = new Drawable[]
{
- new Container
+ new GridContainer
{
- Name = "Timeline",
- RelativeSizeAxes = Axes.X,
- Height = 110,
- Children = new Drawable[]
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
{
- new Box
+ new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black.Opacity(0.5f)
- },
- new Container
- {
- Name = "Content",
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Horizontal = 17, Vertical = 10 },
- Children = new Drawable[]
+ new Container
{
- new Container
+ Name = "Timeline",
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Right = 115 },
- Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black.Opacity(0.5f)
+ },
+ new Container
+ {
+ Name = "Timeline content",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Right = 115 },
+ Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
+ }
+ }
+ }
}
}
+ },
+ new Drawable[]
+ {
+ composerContainer = new Container
+ {
+ Name = "Composer content",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
+ }
}
- }
- }
+ },
+ RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) }
+ },
};
timeline.Beatmap.BindTo(Beatmap);
+ Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ composerContainer.Clear();
+
+ var ruleset = newBeatmap.BeatmapInfo.Ruleset?.CreateInstance();
+ if (ruleset == null)
+ {
+ Logger.Log("Beatmap doesn't have a ruleset assigned.");
+ // ExitRequested?.Invoke();
+ return;
+ }
+
+ var composer = ruleset.CreateHitObjectComposer();
+ if (composer == null)
+ {
+ Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition.");
+ // ExitRequested?.Invoke();
+ return;
+ }
+
+ composerContainer.Child = composer;
}
}
}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs
new file mode 100644
index 0000000000..10b6c07f3d
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs
@@ -0,0 +1,123 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons
+{
+ public class DrawableRadioButton : TriangleButton
+ {
+ ///
+ /// Invoked when this has been selected.
+ ///
+ public Action Selected;
+
+ private Color4 defaultBackgroundColour;
+ private Color4 defaultBubbleColour;
+ private Color4 selectedBackgroundColour;
+ private Color4 selectedBubbleColour;
+
+ private readonly Drawable bubble;
+ private readonly RadioButton button;
+
+ public DrawableRadioButton(RadioButton button)
+ {
+ this.button = button;
+
+ Text = button.Text;
+ Action = button.Action;
+
+ RelativeSizeAxes = Axes.X;
+
+ bubble = new CircularContainer
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Scale = new Vector2(0.5f),
+ X = 10,
+ Masking = true,
+ Blending = BlendingMode.Additive,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ defaultBackgroundColour = colours.Gray3;
+ defaultBubbleColour = defaultBackgroundColour.Darken(0.5f);
+ selectedBackgroundColour = colours.BlueDark;
+ selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f);
+
+ Triangles.Alpha = 0;
+
+ Content.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Radius = 2,
+ Offset = new Vector2(0, 1),
+ Colour = Color4.Black.Opacity(0.5f)
+ };
+
+ Add(bubble);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ button.Selected.ValueChanged += v =>
+ {
+ updateSelectionState();
+ if (v)
+ Selected?.Invoke(button);
+ };
+
+ updateSelectionState();
+ }
+
+ private void updateSelectionState()
+ {
+ if (!IsLoaded)
+ return;
+
+ BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour;
+ bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour;
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ if (button.Selected)
+ return true;
+
+ if (!Enabled)
+ return true;
+
+ button.Selected.Value = true;
+
+ return base.OnClick(state);
+ }
+
+ protected override SpriteText CreateText() => new OsuSpriteText
+ {
+ Depth = -1,
+ Origin = Anchor.CentreLeft,
+ Anchor = Anchor.CentreLeft,
+ X = 40f
+ };
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs
new file mode 100644
index 0000000000..055362d9e1
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs
@@ -0,0 +1,51 @@
+// 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.Configuration;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons
+{
+ public class RadioButton
+ {
+ ///
+ /// Whether this is selected.
+ ///
+ ///
+ public readonly BindableBool Selected;
+
+ ///
+ /// The text that should be displayed in this button.
+ ///
+ public string Text;
+
+ ///
+ /// The that should be invoked when this button is selected.
+ ///
+ public Action Action;
+
+ public RadioButton(string text, Action action)
+ {
+ Text = text;
+ Action = action;
+ Selected = new BindableBool();
+ }
+
+ public RadioButton(string text)
+ : this(text, null)
+ {
+ Text = text;
+ Action = null;
+ }
+
+ ///
+ /// Selects this .
+ ///
+ public void Select() => Selected.Value = true;
+
+ ///
+ /// Deselects this .
+ ///
+ public void Deselect() => Selected.Value = false;
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs
new file mode 100644
index 0000000000..5f1def4a2e
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs
@@ -0,0 +1,61 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using OpenTK;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons
+{
+ public class RadioButtonCollection : CompositeDrawable
+ {
+ private IReadOnlyList items;
+ public IReadOnlyList Items
+ {
+ get { return items; }
+ set
+ {
+ if (ReferenceEquals(items, value))
+ return;
+ items = value;
+
+ buttonContainer.Clear();
+ items.ForEach(addButton);
+ }
+ }
+
+ private readonly FlowContainer buttonContainer;
+
+ public RadioButtonCollection()
+ {
+ AutoSizeAxes = Axes.Y;
+
+ InternalChild = buttonContainer = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 5)
+ };
+ }
+
+ private RadioButton currentlySelected;
+ private void addButton(RadioButton button)
+ {
+ button.Selected.ValueChanged += v =>
+ {
+ if (v)
+ {
+ currentlySelected?.Deselect();
+ currentlySelected = button;
+ }
+ else
+ currentlySelected = null;
+ };
+
+ buttonContainer.Add(new DrawableRadioButton(button));
+ }
+ }
+}
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index ca541ea552..ec2e8e0cb1 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -1,8 +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 System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shaders;
using osu.Game.Screens.Menu;
using OpenTK;
using osu.Framework.Screens;
@@ -33,14 +37,28 @@ namespace osu.Game.Screens
logo.FadeInFromZero(5000, Easing.OutQuint);
}
+ private OsuScreen loadScreen;
+ private ShaderPrecompiler precompiler;
+
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
- if (showDisclaimer)
- LoadComponentAsync(new Disclaimer(), d => Push(d));
- else
- LoadComponentAsync(new Intro(), d => Push(d));
+ LoadComponentAsync(precompiler = new ShaderPrecompiler(loadIfReady), Add);
+ LoadComponentAsync(loadScreen = showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(), s => loadIfReady());
+ }
+
+ private void loadIfReady()
+ {
+ if (ChildScreen == loadScreen) return;
+
+ if (loadScreen.LoadState != LoadState.Ready)
+ return;
+
+ if (!precompiler.FinishedCompiling)
+ return;
+
+ Push(loadScreen);
}
protected override void LogoSuspending(OsuLogo logo)
@@ -54,5 +72,49 @@ namespace osu.Game.Screens
{
showDisclaimer = game.IsDeployedBuild;
}
+
+ ///
+ /// Compiles a set of shaders before continuing. Attempts to draw some frames between compilation by limiting to one compile per draw frame.
+ ///
+ public class ShaderPrecompiler : Drawable
+ {
+ private readonly Action onLoaded;
+ private readonly List loadTargets = new List();
+
+ public bool FinishedCompiling { get; private set; }
+
+ public ShaderPrecompiler(Action onLoaded)
+ {
+ this.onLoaded = onLoaded;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ShaderManager manager)
+ {
+ loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED));
+ loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR));
+ loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE));
+
+ loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
+
+ loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED));
+ loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
+ }
+
+ private Shader currentLoadTarget;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // if our target is null we are done.
+ if (loadTargets.All(s => s.Loaded))
+ {
+ FinishedCompiling = true;
+ Expire();
+ onLoaded?.Invoke();
+ }
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs
index ccd61643ce..5e55166c19 100644
--- a/osu.Game/Screens/Menu/Button.cs
+++ b/osu.Game/Screens/Menu/Button.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
- sampleHover = audio.Sample.Get(@"Menu/hover");
+ sampleHover = audio.Sample.Get(@"Menu/button-hover");
if (!string.IsNullOrEmpty(sampleName))
sampleClick = audio.Sample.Get($@"Menu/{sampleName}");
}
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 5a4a5f07b5..ce7856c5a9 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -117,13 +117,13 @@ namespace osu.Game.Screens.Menu
},
};
- buttonsPlay.Add(new Button(@"solo", @"select-6", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
- buttonsPlay.Add(new Button(@"multi", @"select-5", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M));
- buttonsPlay.Add(new Button(@"chart", @"select-5", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
+ buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
+ buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M));
+ buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
- buttonsTopLevel.Add(new Button(@"play", @"select-1", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), onPlay, WEDGE_WIDTH, Key.P));
- buttonsTopLevel.Add(new Button(@"osu!editor", @"select-5", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
- buttonsTopLevel.Add(new Button(@"osu!direct", string.Empty, FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
+ buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), onPlay, WEDGE_WIDTH, Key.P));
+ buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
+ buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), onExit, 0, Key.Q));
buttonFlow.AddRange(buttonsPlay);
@@ -134,7 +134,7 @@ namespace osu.Game.Screens.Menu
private void load(AudioManager audio, OsuGame game = null)
{
toolbar = game?.Toolbar;
- sampleBack = audio.Sample.Get(@"Menu/select-4");
+ sampleBack = audio.Sample.Get(@"Menu/button-back-select");
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@@ -180,19 +180,21 @@ namespace osu.Game.Screens.Menu
State = MenuState.TopLevel;
}
- private void onOsuLogo()
+ private bool onOsuLogo()
{
switch (state)
{
+ default:
+ return true;
case MenuState.Initial:
State = MenuState.TopLevel;
- return;
+ return true;
case MenuState.TopLevel:
buttonsTopLevel.First().TriggerOnClick();
- return;
+ return false;
case MenuState.Play:
buttonsPlay.First().TriggerOnClick();
- return;
+ return false;
}
}
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 252f2d37b5..9ca12702e5 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -48,7 +48,10 @@ namespace osu.Game.Screens.Menu
private readonly Triangles triangles;
- public Action Action;
+ ///
+ /// Return value decides whether the logo should play its own sample for the click action.
+ ///
+ public Func Action;
public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f;
@@ -248,8 +251,8 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(TextureStore textures, AudioManager audio)
{
- sampleClick = audio.Sample.Get(@"Menu/select-2");
- sampleBeat = audio.Sample.Get(@"Menu/heartbeat");
+ sampleClick = audio.Sample.Get(@"Menu/osu-logo-select");
+ sampleBeat = audio.Sample.Get(@"Menu/osu-logo-heartbeat");
logo.Texture = textures.Get(@"Menu/logo");
ripple.Texture = textures.Get(@"Menu/logo");
@@ -354,13 +357,12 @@ namespace osu.Game.Screens.Menu
{
if (!interactive) return false;
- sampleClick.Play();
+ if (Action?.Invoke() ?? true)
+ sampleClick.Play();
flashLayer.ClearTransforms();
flashLayer.Alpha = 0.4f;
flashLayer.FadeOut(1500, Easing.OutExpo);
-
- Action?.Invoke();
return true;
}
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index f5ff9ea036..4a27c7f1ea 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -13,6 +13,8 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
+using osu.Framework.Input;
+using OpenTK.Input;
namespace osu.Game.Screens
{
@@ -70,7 +72,21 @@ namespace osu.Game.Screens
if (osuGame != null)
Ruleset.BindTo(osuGame.Ruleset);
- sampleExit = audio.Sample.Get(@"UI/melodic-1");
+ sampleExit = audio.Sample.Get(@"UI/screen-back");
+ }
+
+ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+ {
+ if (args.Repeat || !IsCurrentScreen) return false;
+
+ switch (args.Key)
+ {
+ case Key.Escape:
+ Exit();
+ return true;
+ }
+
+ return base.OnKeyDown(state, args);
}
protected override void OnResuming(Screen last)
diff --git a/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs
index e44a738d55..c07eebdef8 100644
--- a/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs
+++ b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs
@@ -16,8 +16,6 @@ namespace osu.Game.Screens.Play.HUD
public bool ReplayLoaded;
- public override bool HandleInput => true;
-
public readonly PlaybackSettings PlaybackSettings;
//public readonly CollectionSettings CollectionSettings;
//public readonly DiscussionSettings DiscussionSettings;
@@ -35,7 +33,7 @@ namespace osu.Game.Screens.Play.HUD
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Margin = new MarginPadding { Top = 100, Right = 10 },
- Children = new []
+ Children = new[]
{
//CollectionSettings = new CollectionSettings(),
//DiscussionSettings = new DiscussionSettings(),
diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs
index d21be71785..2f077f5c0a 100644
--- a/osu.Game/Screens/Play/KeyCounterCollection.cs
+++ b/osu.Game/Screens/Play/KeyCounterCollection.cs
@@ -141,8 +141,6 @@ namespace osu.Game.Screens.Play
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
- public override bool HandleInput => true;
-
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyDown(state, args));
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyUp(state, args));
diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs
index eed5cd1c20..5f5eeb63a0 100644
--- a/osu.Game/Screens/Play/PauseContainer.cs
+++ b/osu.Game/Screens/Play/PauseContainer.cs
@@ -22,8 +22,6 @@ namespace osu.Game.Screens.Play
{
public bool IsPaused { get; private set; }
- public bool AllowExit => IsPaused && pauseOverlay.Alpha == 1;
-
public Func CheckCanPause;
private const double pause_cooldown = 1000;
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index a19305778c..b5b09504da 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -245,7 +245,7 @@ namespace osu.Game.Screens.Play
private void initializeStoryboard(bool asyncLoad)
{
- var beatmap = Beatmap.Value.Beatmap;
+ var beatmap = Beatmap.Value;
storyboard = beatmap.Storyboard.CreateDrawable(Beatmap.Value);
storyboard.Masking = true;
@@ -310,7 +310,7 @@ namespace osu.Game.Screens.Play
if (!loadedSuccessfully)
return;
- (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1500, Easing.OutQuint);
+ (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1000, Easing.OutQuint);
dimLevel.ValueChanged += dimLevel_ValueChanged;
showStoryboard.ValueChanged += showStoryboard_ValueChanged;
@@ -357,7 +357,7 @@ namespace osu.Game.Screens.Play
protected override bool OnExiting(Screen next)
{
- if (!AllowPause || HasFailed || !ValidForResume || pauseContainer?.AllowExit != false || RulesetContainer?.HasReplayLoaded != false)
+ if (!AllowPause || HasFailed || !ValidForResume || pauseContainer?.IsPaused != false || RulesetContainer?.HasReplayLoaded != false)
{
// In the case of replays, we may have changed the playback rate.
applyRateFromMods();
@@ -388,7 +388,7 @@ namespace osu.Game.Screens.Play
initializeStoryboard(true);
var beatmap = Beatmap.Value;
- var storyboardVisible = showStoryboard && beatmap.Beatmap.Storyboard.HasDrawable;
+ var storyboardVisible = showStoryboard && beatmap.Storyboard.HasDrawable;
storyboardContainer.FadeColour(new Color4(opacity, opacity, opacity, 1), 800);
storyboardContainer.FadeTo(storyboardVisible && opacity > 0 ? 1 : 0);
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index b0a636dfb3..578b02d90a 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -34,7 +34,6 @@ namespace osu.Game.Screens.Select
public IEnumerable Beatmaps
{
get { return groups.Select(g => g.BeatmapSet); }
-
set
{
scrollableContent.Clear(false);
@@ -45,14 +44,14 @@ namespace osu.Game.Screens.Select
Task.Run(() =>
{
- newGroups = value.Select(createGroup).ToList();
+ newGroups = value.Select(createGroup).Where(g => g != null).ToList();
criteria.Filter(newGroups);
}).ContinueWith(t =>
{
Schedule(() =>
{
foreach (var g in newGroups)
- if (g != null) addGroup(g);
+ addGroup(g);
computeYPositions();
BeatmapsChanged?.Invoke();
@@ -95,47 +94,31 @@ namespace osu.Game.Screens.Select
});
}
- public void AddBeatmap(BeatmapSetInfo beatmapSet)
- {
- Schedule(() =>
- {
- var group = createGroup(beatmapSet);
-
- if (group == null)
- return;
-
- addGroup(group);
- computeYPositions();
- if (selectedGroup == null)
- selectGroup(group);
- });
- }
-
public void RemoveBeatmap(BeatmapSetInfo beatmapSet)
{
Schedule(() => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID)));
}
- public void UpdateBeatmap(BeatmapInfo beatmap)
+ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet)
{
- // todo: this method should not run more than once for the same BeatmapSetInfo.
- var set = manager.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID);
-
// todo: this method should be smarter as to not recreate panels that haven't changed, etc.
- var group = groups.Find(b => b.BeatmapSet.ID == set.ID);
+ var oldGroup = groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID);
- if (group == null)
- return;
+ var newGroup = createGroup(beatmapSet);
- int i = groups.IndexOf(group);
- groups.RemoveAt(i);
-
- var newGroup = createGroup(set);
+ int index = groups.IndexOf(oldGroup);
+ if (index >= 0)
+ groups.RemoveAt(index);
if (newGroup != null)
- groups.Insert(i, newGroup);
+ {
+ if (index >= 0)
+ groups.Insert(index, newGroup);
+ else
+ addGroup(newGroup);
+ }
- bool hadSelection = selectedGroup == group;
+ bool hadSelection = selectedGroup == oldGroup;
if (hadSelection && newGroup == null)
selectedGroup = null;
@@ -146,8 +129,10 @@ namespace osu.Game.Screens.Select
if (hadSelection && newGroup != null)
{
var newSelection =
- newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID) ??
- newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, group.BeatmapPanels.IndexOf(selectedPanel))];
+ newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID);
+
+ if(newSelection == null && oldGroup != null && selectedPanel != null)
+ newSelection = newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, oldGroup.BeatmapPanels.IndexOf(selectedPanel))];
selectGroup(newGroup, newSelection);
}
@@ -305,8 +290,6 @@ namespace osu.Game.Screens.Select
if (newCriteria != null)
criteria = newCriteria;
- if (!IsLoaded) return;
-
Action perform = delegate
{
filterTask = null;
@@ -378,6 +361,10 @@ namespace osu.Game.Screens.Select
private void addGroup(BeatmapGroup group)
{
+ // prevent duplicates by concurrent independent actions trying to add a group
+ if (groups.Any(g => g.BeatmapSet.ID == group.BeatmapSet.ID))
+ return;
+
groups.Add(group);
panels.Add(group.Header);
panels.AddRange(group.BeatmapPanels);
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index d7c509d979..a9a778fe17 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -261,6 +261,7 @@ namespace osu.Game.Screens.Select
description.Text = null;
source.Text = null;
tags.Text = null;
+
advanced.Beatmap = new BeatmapInfo
{
StarDifficulty = 0,
@@ -306,36 +307,16 @@ namespace osu.Game.Screens.Select
private class MetadataSection : Container
{
- private readonly TextFlowContainer textFlow;
-
- public string Text
- {
- set
- {
- if (string.IsNullOrEmpty(value))
- {
- this.FadeOut(transition_duration);
- return;
- }
-
- this.FadeIn(transition_duration);
- textFlow.Clear();
- textFlow.AddText(value, s => s.TextSize = 14);
- }
- }
-
- public Color4 TextColour
- {
- get { return textFlow.Colour; }
- set { textFlow.Colour = value; }
- }
+ private readonly FillFlowContainer textContainer;
+ private TextFlowContainer textFlow;
public MetadataSection(string title)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+ Alpha = 0;
- InternalChild = new FillFlowContainer
+ InternalChild = textContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@@ -361,6 +342,44 @@ namespace osu.Game.Screens.Select
},
};
}
+
+ public string Text
+ {
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ this.FadeOut(transition_duration);
+ return;
+ }
+
+ setTextAsync(value);
+ }
+ }
+
+ private void setTextAsync(string text)
+ {
+ LoadComponentAsync(new TextFlowContainer(s => s.TextSize = 14)
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Colour = textFlow.Colour,
+ Text = text
+ }, loaded =>
+ {
+ textFlow?.Expire();
+ textContainer.Add(textFlow = loaded);
+
+ // fade in if we haven't yet.
+ this.FadeIn(transition_duration);
+ });
+ }
+
+ public Color4 TextColour
+ {
+ get { return textFlow.Colour; }
+ set { textFlow.Colour = value; }
+ }
}
private class DimmedLoadingAnimation : VisibilityContainer
diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs
index 997e0baec3..19bcad367e 100644
--- a/osu.Game/Screens/Select/Details/UserRatings.cs
+++ b/osu.Game/Screens/Select/Details/UserRatings.cs
@@ -31,15 +31,15 @@ namespace osu.Game.Screens.Select.Details
const int rating_range = 10;
- var ratings = Metrics.Ratings.ToList().GetRange(1, rating_range); // adjust for API returning weird empty data at 0.
+ var ratings = Metrics.Ratings.Skip(1).Take(rating_range); // adjust for API returning weird empty data at 0.
- var negativeCount = ratings.GetRange(0, rating_range / 2).Sum();
+ var negativeCount = ratings.Take(rating_range / 2).Sum();
var totalCount = ratings.Sum();
negativeRatings.Text = negativeCount.ToString();
positiveRatings.Text = (totalCount - negativeCount).ToString();
ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount;
- graph.Values = ratings.GetRange(0, rating_range).Select(r => (float)r);
+ graph.Values = ratings.Take(rating_range).Select(r => (float)r);
}
}
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index e83613125b..1b86cec613 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -15,6 +15,7 @@ using osu.Game.Screens.Select.Filter;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Input;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Configuration;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Select
@@ -60,6 +61,7 @@ namespace osu.Game.Screens.Select
Group = group,
Sort = sort,
SearchText = searchTextBox.Text,
+ AllowConvertedBeatmaps = showConverted,
Ruleset = ruleset
};
@@ -163,17 +165,24 @@ namespace osu.Game.Screens.Select
private readonly Bindable ruleset = new Bindable();
+ private Bindable showConverted;
+
[BackgroundDependencyLoader(permitNulls: true)]
- private void load(OsuColour colours, OsuGame osu)
+ private void load(OsuColour colours, OsuGame osu, OsuConfigManager config)
{
sortTabs.AccentColour = colours.GreenLight;
+ showConverted = config.GetBindable(OsuSetting.ShowConvertedBeatmaps);
+ showConverted.ValueChanged += val => updateCriteria();
+
if (osu != null)
ruleset.BindTo(osu.Ruleset);
- ruleset.ValueChanged += val => FilterChanged?.Invoke(CreateCriteria());
+ ruleset.ValueChanged += val => updateCriteria();
ruleset.TriggerChange();
}
+ private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
+
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnMouseMove(InputState state) => true;
@@ -182,4 +191,4 @@ namespace osu.Game.Screens.Select
protected override bool OnDragStart(InputState state) => true;
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs
index 6c1fb1703d..c1355bfa63 100644
--- a/osu.Game/Screens/Select/FilterCriteria.cs
+++ b/osu.Game/Screens/Select/FilterCriteria.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Screens.Select
public SortMode Sort;
public string SearchText;
public RulesetInfo Ruleset;
+ public bool AllowConvertedBeatmaps;
public void Filter(List groups)
{
@@ -23,7 +24,7 @@ namespace osu.Game.Screens.Select
{
var set = g.BeatmapSet;
- bool hasCurrentMode = set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0));
+ bool hasCurrentMode = AllowConvertedBeatmaps || set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0));
bool match = hasCurrentMode;
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index e0a3693371..bba6ddf577 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -4,6 +4,8 @@
using System.Linq;
using OpenTK.Input;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@@ -42,9 +44,13 @@ namespace osu.Game.Screens.Select
beatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s));
}
+ private SampleChannel sampleConfirm;
+
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, AudioManager audio)
{
+ sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection");
+
Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
@@ -128,6 +134,8 @@ namespace osu.Game.Screens.Select
Beatmap.Value.Track.Looping = false;
Beatmap.Disabled = true;
+ sampleConfirm?.Play();
+
LoadComponentAsync(player = new PlayerLoader(new Player()), l => Push(player));
}
}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 24a7b3db90..cf08ab273e 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Linq;
using System.Threading;
using OpenTK;
using OpenTK.Input;
@@ -203,15 +204,30 @@ namespace osu.Game.Screens.Select
Push(new Editor());
}
- private void onBeatmapRestored(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b));
- private void onBeatmapHidden(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b));
+ private void onBeatmapRestored(BeatmapInfo beatmap)
+ {
+ Schedule(() =>
+ {
+ var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID);
+ carousel.UpdateBeatmapSet(beatmapSet);
+ });
+ }
+
+ private void onBeatmapHidden(BeatmapInfo beatmap)
+ {
+ Schedule(() =>
+ {
+ var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID);
+ carousel.UpdateBeatmapSet(beatmapSet);
+ });
+ }
private void carouselBeatmapsLoaded()
{
if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false)
carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false);
else
- carousel.SelectNext();
+ carousel.SelectNextRandom();
}
private void carouselRaisedStart(InputState state = null)
@@ -255,18 +271,15 @@ namespace osu.Game.Screens.Select
UpdateBeatmap(Beatmap.Value);
};
- selectionChangedDebounce?.Cancel();
-
if (beatmap?.Equals(beatmapNoDebounce) == true)
return;
+ selectionChangedDebounce?.Cancel();
+
beatmapNoDebounce = beatmap;
if (beatmap == null)
- {
- if (!Beatmap.IsDefault)
- performLoad();
- }
+ performLoad();
else
{
if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID)
@@ -296,7 +309,14 @@ namespace osu.Game.Screens.Select
carousel.Filter(criteria, debounce);
}
- private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => addBeatmapSet(s));
+ private void onBeatmapSetAdded(BeatmapSetInfo s)
+ {
+ Schedule(() =>
+ {
+ carousel.UpdateBeatmapSet(s);
+ carousel.SelectBeatmap(s.Beatmaps.First());
+ });
+ }
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s));
@@ -332,7 +352,11 @@ namespace osu.Game.Screens.Select
logo.FadeIn(logo_transition, Easing.OutQuint);
logo.ScaleTo(0.4f, logo_transition, Easing.OutQuint);
- logo.Action = () => carouselRaisedStart();
+ logo.Action = () =>
+ {
+ carouselRaisedStart();
+ return false;
+ };
}
protected override void LogoExiting(OsuLogo logo)
@@ -413,7 +437,7 @@ namespace osu.Game.Screens.Select
if (backgroundModeBeatmap != null)
{
backgroundModeBeatmap.Beatmap = beatmap;
- backgroundModeBeatmap.BlurTo(background_blur, 1000);
+ backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint);
backgroundModeBeatmap.FadeTo(1, 250);
}
@@ -435,8 +459,6 @@ namespace osu.Game.Screens.Select
}
}
- private void addBeatmapSet(BeatmapSetInfo beatmapSet) => carousel.AddBeatmap(beatmapSet);
-
private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
{
carousel.RemoveBeatmap(beatmapSet);
diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs
index e540782fc1..3e7ab56c99 100644
--- a/osu.Game/Screens/Tournament/Drawings.cs
+++ b/osu.Game/Screens/Tournament/Drawings.cs
@@ -193,21 +193,21 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[]
{
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
Text = "Begin random",
Action = teamsContainer.StartScrolling,
},
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
Text = "Stop random",
Action = teamsContainer.StopScrolling,
},
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
@@ -232,7 +232,7 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[]
{
- new OsuButton
+ new TriangleButton
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs
index 59cbe74650..4eca910c1e 100644
--- a/osu.Game/Storyboards/Storyboard.cs
+++ b/osu.Game/Storyboards/Storyboard.cs
@@ -5,10 +5,11 @@ using osu.Game.Beatmaps;
using osu.Game.Storyboards.Drawables;
using System.Collections.Generic;
using System.Linq;
+using System;
namespace osu.Game.Storyboards
{
- public class Storyboard
+ public class Storyboard : IDisposable
{
private readonly Dictionary layers = new Dictionary();
public IEnumerable Layers => layers.Values;
@@ -59,5 +60,29 @@ namespace osu.Game.Storyboards
}
return drawable;
}
+
+ #region Disposal
+
+ ~Storyboard()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private bool isDisposed;
+
+ protected virtual void Dispose(bool isDisposing)
+ {
+ if (isDisposed)
+ return;
+ isDisposed = true;
+ }
+
+ #endregion
}
}
diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..6da14e9b12
--- /dev/null
+++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
@@ -0,0 +1,395 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Caching;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Tests.Visual
+{
+ public abstract class TestCasePerformancePoints : OsuTestCase
+ {
+ protected TestCasePerformancePoints(Ruleset ruleset)
+ {
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f,
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new BeatmapList(ruleset)
+ }
+ }
+ },
+ null,
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f,
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new StarRatingGrid()
+ }
+ }
+ },
+ null,
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f,
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new PerformanceList()
+ }
+ }
+ },
+ }
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 20),
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 20)
+ }
+ };
+ }
+
+ private class BeatmapList : CompositeDrawable
+ {
+ private readonly Container beatmapDisplays;
+ private readonly Ruleset ruleset;
+
+ public BeatmapList(Ruleset ruleset)
+ {
+ this.ruleset = ruleset;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = beatmapDisplays = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 4)
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps)
+ {
+ var sets = beatmaps.GetAllUsableBeatmapSets();
+ var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID < 0 || b.RulesetID == ruleset.LegacyID);
+
+ allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b)));
+ }
+
+ private class BeatmapDisplay : CompositeDrawable, IHasTooltip
+ {
+ private readonly OsuSpriteText text;
+ private readonly BeatmapInfo beatmap;
+
+ private BeatmapManager beatmaps;
+ private OsuGameBase osuGame;
+
+ private bool isSelected;
+
+ public string TooltipText => text.Text;
+
+ public BeatmapDisplay(BeatmapInfo beatmap)
+ {
+ this.beatmap = beatmap;
+
+ AutoSizeAxes = Axes.Both;
+ InternalChild = text = new OsuSpriteText();
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ if (osuGame.Beatmap.Value.BeatmapInfo.ID == beatmap.ID)
+ return false;
+
+ osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
+ isSelected = true;
+ return true;
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ if (isSelected)
+ return false;
+ this.FadeColour(Color4.Yellow, 100);
+ return true;
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ if (isSelected)
+ return;
+ this.FadeColour(Color4.White, 100);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
+ {
+ this.osuGame = osuGame;
+ this.beatmaps = beatmaps;
+
+ var working = beatmaps.GetWorkingBeatmap(beatmap);
+ text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]";
+
+ osuGame.Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ if (isSelected)
+ this.FadeColour(Color4.White, 100);
+ isSelected = false;
+ }
+ }
+ }
+
+ private class PerformanceList : CompositeDrawable
+ {
+ private readonly FillFlowContainer scores;
+ private APIAccess api;
+
+ public PerformanceList()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = scores = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 4)
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame, APIAccess api)
+ {
+ this.api = api;
+
+ if (!api.IsLoggedIn)
+ {
+ InternalChild = new SpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = "Please login to see online scores",
+ };
+ }
+
+ osuGame.Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private GetScoresRequest lastRequest;
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ lastRequest?.Cancel();
+ scores.Clear();
+
+ if (!api.IsLoggedIn)
+ return;
+
+ lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo);
+ lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
+ api.Queue(lastRequest);
+ }
+
+ private class PerformanceDisplay : CompositeDrawable
+ {
+ private readonly OsuSpriteText text;
+
+ private readonly Score score;
+ private readonly Beatmap beatmap;
+
+ public PerformanceDisplay(Score score, Beatmap beatmap)
+ {
+ this.score = score;
+ this.beatmap = beatmap;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = text = new OsuSpriteText();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
+ var calculator = ruleset.CreatePerformanceCalculator(beatmap, score);
+ if (calculator == null)
+ return;
+
+ var attributes = new Dictionary();
+ double performance = calculator.Calculate(attributes);
+
+ text.Text = $"{score.User.Username} -> online: {score.PP:n2}pp | local: {performance:n2}pp";
+ }
+ }
+ }
+
+ private class StarRatingGrid : CompositeDrawable
+ {
+ private readonly FillFlowContainer modFlow;
+ private readonly OsuSpriteText totalText;
+ private readonly FillFlowContainer categoryTexts;
+
+ public StarRatingGrid()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ modFlow = new FillFlowContainer
+ {
+ Name = "Checkbox flow",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(4, 4)
+ },
+ new FillFlowContainer
+ {
+ Name = "Information display",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(0, 4),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ totalText = new OsuSpriteText { TextSize = 24 },
+ categoryTexts = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical
+ }
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame)
+ {
+ osuGame.Beatmap.ValueChanged += beatmapChanged;
+ }
+
+ private Cached informationCache = new Cached();
+
+ private Ruleset ruleset;
+ private WorkingBeatmap beatmap;
+
+ private void beatmapChanged(WorkingBeatmap newBeatmap)
+ {
+ beatmap = newBeatmap;
+
+ modFlow.Clear();
+
+ ruleset = newBeatmap.BeatmapInfo.Ruleset.CreateInstance();
+ foreach (var mod in ruleset.GetAllMods())
+ {
+ var checkBox = new OsuCheckbox
+ {
+ RelativeSizeAxes = Axes.None,
+ Width = 50,
+ LabelText = mod.ShortenedName
+ };
+
+ checkBox.Current.ValueChanged += v => informationCache.Invalidate();
+ modFlow.Add(checkBox);
+ }
+
+ informationCache.Invalidate();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (ruleset == null)
+ return;
+
+ if (!informationCache.IsValid)
+ {
+ totalText.Text = string.Empty;
+ categoryTexts.Clear();
+
+ var allMods = ruleset.GetAllMods().ToList();
+ Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
+
+ var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
+ if (diffCalc != null)
+ {
+ var categories = new Dictionary();
+ double totalSr = diffCalc.Calculate(categories);
+
+ totalText.Text = $"Star rating: {totalSr:n2}";
+ foreach (var kvp in categories)
+ categoryTexts.Add(new OsuSpriteText { Text = $"{kvp.Key}: {kvp.Value:n2}" });
+ }
+
+ informationCache.Validate();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs
index f3a6d1efc3..106f0fa8f3 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -8,7 +8,6 @@ using System.Text;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.Formats;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
@@ -50,7 +49,11 @@ namespace osu.Game.Tests.Visual
string instantiation = ruleset?.AssemblyQualifiedName;
foreach (var r in rulesets.AvailableRulesets.Where(rs => instantiation == null || rs.InstantiationInfo == instantiation))
- AddStep(r.Name, () => loadPlayerFor(r));
+ {
+ Player p = null;
+ AddStep(r.Name, () => p = loadPlayerFor(r));
+ AddUntilStep(() => p.IsLoaded);
+ }
}
protected virtual Beatmap CreateBeatmap()
@@ -59,12 +62,12 @@ namespace osu.Game.Tests.Visual
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
using (var reader = new StreamReader(stream))
- beatmap = BeatmapDecoder.GetDecoder(reader).Decode(reader);
+ beatmap = Game.Beatmaps.Formats.Decoder.GetDecoder(reader).DecodeBeatmap(reader);
return beatmap;
}
- private void loadPlayerFor(RulesetInfo r)
+ private Player loadPlayerFor(RulesetInfo r)
{
var beatmap = CreateBeatmap();
@@ -78,7 +81,11 @@ namespace osu.Game.Tests.Visual
if (Player != null)
Remove(Player);
- LoadScreen(CreatePlayer(working, instance));
+ var player = CreatePlayer(working, instance);
+
+ LoadComponentAsync(player, LoadScreen);
+
+ return player;
}
protected virtual Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) => new Player
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index 2adb809334..a6fa8637fd 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Users
public bool Active;
[JsonProperty(@"interests")]
- public string Intrerests;
+ public string Interests;
[JsonProperty(@"occupation")]
public string Occupation;
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 1f235e3893..d056afcf54 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -17,10 +17,11 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Users
{
- public class UserPanel : ClickableContainer, IHasContextMenu
+ public class UserPanel : OsuClickableContainer, IHasContextMenu
{
private readonly User user;
private const float height = 100;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 2aefde2916..53ad323134 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -1,5 +1,6 @@
-
+
+
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
Debug
@@ -61,7 +62,6 @@
false
- 6
none
@@ -195,9 +195,6 @@
-
- osu.licenseheader
-
@@ -269,8 +266,13 @@
+
+
+
+
+
20171019041408_InitialCreate.cs
@@ -280,18 +282,27 @@
20171025071459_AddMissingIndexRules.cs
-
+
20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs
+
+
+ 20171209034410_AddRulesetInfoShortName.cs
+
-
+
+
+
+
+
+
@@ -303,16 +314,19 @@
-
-
-
+
+
+
+
+
+
@@ -366,7 +380,7 @@
-
+
@@ -478,7 +492,7 @@
-
+
@@ -556,6 +570,10 @@
+
+
+
+
@@ -629,6 +647,7 @@
+
@@ -784,6 +803,7 @@
+
@@ -809,6 +829,9 @@
+
+
+