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..cc013fc406 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit fe49ccb3c8f8661d653752d225ae1dc183944bb4
+Subproject commit cc013fc4063dda0843f38c1c73568a413abcf229
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..b2f7fdabfc 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -6,9 +6,9 @@ using osu.Game.Rulesets.Catch.Objects;
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 +16,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
int comboIndex = 0;
int colourIndex = 0;
- CatchBaseHit lastObj = null;
+ CatchHitObject lastObj = null;
foreach (var obj in beatmap.HitObjects)
{
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/Objects/CatchBaseHit.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
similarity index 55%
rename from osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs
rename to osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 2f33cf1093..cb4e6453ce 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchBaseHit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -1,14 +1,18 @@
// 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 CatchBaseHit : HitObject, IHasXPosition, IHasCombo
+ 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;
@@ -20,5 +24,14 @@ namespace osu.Game.Rulesets.Catch.Objects
/// The next fruit starts a new combo. Used for explodey.
///
public virtual bool LastInCombo { get; set; }
+
+ public float Scale { get; set; } = 1;
+
+ 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/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..538f6930ed
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
@@ -0,0 +1,50 @@
+// 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;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherArea),
+ };
+
+ public TestCaseCatcherArea()
+ {
+ AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
+ }
+
+ private void createCatcher(float size)
+ {
+ Child = new CatchInputManager(catchRuleset)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new CatcherArea(new BeatmapDifficulty { CircleSize = size })
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.BottomLeft
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ catchRuleset = rulesets.GetRuleset(2);
+ }
+ }
+}
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..6fd0793500 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;
@@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI
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.CanCatch(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..203db1bb8c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -0,0 +1,240 @@
+// 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;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherArea : Container
+ {
+ public const float CATCHER_SIZE = 172;
+
+ private readonly Catcher catcher;
+
+ public Container ExplodingFruitTarget
+ {
+ set { catcher.ExplodingFruitTarget = value; }
+ }
+
+ public CatcherArea(BeatmapDifficulty difficulty = null)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = CATCHER_SIZE;
+ Child = catcher = new Catcher(difficulty)
+ {
+ AdditiveTarget = this,
+ };
+ }
+
+ public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
+ {
+ fruit.RelativePositionAxes = Axes.None;
+ fruit.Position = new Vector2(catcher.ToLocalSpace(absolutePosition).X - catcher.DrawSize.X / 2, 0);
+
+ fruit.Anchor = Anchor.TopCentre;
+ fruit.Origin = Anchor.BottomCentre;
+ fruit.Scale *= 0.7f;
+ fruit.LifetimeEnd = double.MaxValue;
+
+ catcher.Add(fruit);
+ }
+
+ public bool CanCatch(CatchHitObject obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X * Math.Abs(catcher.Scale.X) / DrawSize.X / 2;
+
+ 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;
+
+ if (dashing)
+ Schedule(addAdditiveSprite);
+ }
+ }
+
+ private void addAdditiveSprite()
+ {
+ if (!dashing || AdditiveTarget == null) 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.RelativePositionAxes = RelativePositionAxes;
+ additive.Blending = BlendingMode.Additive;
+
+ AdditiveTarget.Add(additive);
+
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
+
+ Scheduler.AddDelayed(addAdditiveSprite, 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.
+ };
+
+ 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);
+
+ if (((CatchHitObject)fruit.HitObject).LastInCombo)
+ explode();
+ }
+
+ 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;
+
+ double dashModifier = Dashing ? 1 : 0.5;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * Math.Sign(currentDirection), Scale.Y);
+ X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * 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..4605a1f50e 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,23 @@
-
+
-
+
+
-
+
-
- osu.licenseheader
-
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index f6d30ad3fa..d5a799b4ed 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -138,8 +138,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Pattern newPattern = conversion.Generate();
lastPattern = newPattern;
- var stairPatternGenerator = (HitObjectPatternGenerator)conversion;
- lastStair = stairPatternGenerator.StairType;
+ var stairPatternGenerator = conversion as HitObjectPatternGenerator;
+ lastStair = stairPatternGenerator?.StairType ?? lastStair;
return newPattern.HitObjects;
}
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index 67bc347535..e0763284a6 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania
{
}
- public override double Calculate(Dictionary categoryDifficulty = null) => 0;
+ public override double Calculate(Dictionary categoryDifficulty = null) => 0;
protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs
index 164309c227..dfc9993bde 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs
@@ -176,22 +176,10 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModAutoplay : ModAutoplay
{
- private int availableColumns;
-
- public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
- {
- // Todo: This shouldn't be done, we should be getting a ManiaBeatmap which should store AvailableColumns
- // But this is dependent on a _lot_ of refactoring
- var maniaRulesetContainer = (ManiaRulesetContainer)rulesetContainer;
- availableColumns = maniaRulesetContainer.AvailableColumns;
-
- base.ApplyToRulesetContainer(rulesetContainer);
- }
-
protected override Score CreateReplayScore(Beatmap beatmap) => new Score
{
User = new User { Username = "osu!topus!" },
- Replay = new ManiaAutoGenerator(beatmap, availableColumns).Generate(),
+ Replay = new ManiaAutoGenerator(beatmap).Generate(),
};
}
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 64982532a7..153fee3ab6 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using System;
+using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
@@ -13,15 +13,11 @@ namespace osu.Game.Rulesets.Mania.Replays
{
internal class ManiaAutoGenerator : AutoGenerator
{
- private const double release_delay = 20;
+ public const double RELEASE_DELAY = 20;
- private readonly int availableColumns;
-
- public ManiaAutoGenerator(Beatmap beatmap, int availableColumns)
+ public ManiaAutoGenerator(Beatmap beatmap)
: base(beatmap)
{
- this.availableColumns = availableColumns;
-
Replay = new Replay { User = new User { Username = @"Autoplay" } };
}
@@ -30,104 +26,52 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate()
{
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- Replay.Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None));
+ Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
- double[] holdEndTimes = new double[availableColumns];
- for (int i = 0; i < availableColumns; i++)
- holdEndTimes[i] = double.NegativeInfinity;
+ var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
- // Notes are handled row-by-row
- foreach (var objGroup in Beatmap.HitObjects.GroupBy(h => h.StartTime))
+ int activeColumns = 0;
+ foreach (var group in pointGroups)
{
- double groupTime = objGroup.Key;
-
- int activeColumns = 0;
-
- // Get the previously held-down active columns
- for (int i = 0; i < availableColumns; i++)
+ foreach (var point in group)
{
- if (holdEndTimes[i] > groupTime)
- activeColumns |= 1 << i;
+ if (point is HitPoint)
+ activeColumns |= 1 << point.Column;
+ if (point is ReleasePoint)
+ activeColumns ^= 1 << point.Column;
}
- // Add on the group columns, keeping track of the held notes for the next rows
- foreach (var obj in objGroup)
- {
- var holdNote = obj as HoldNote;
- if (holdNote != null)
- holdEndTimes[obj.Column] = Math.Max(holdEndTimes[obj.Column], holdNote.EndTime);
-
- activeColumns |= 1 << obj.Column;
- }
-
- Replay.Frames.Add(new ReplayFrame(groupTime, activeColumns, null, ReplayButtonState.None));
-
- // Add the release frames. We can't do this with the loop above because we need activeColumns to be fully populated
- foreach (var obj in objGroup.GroupBy(h => (h as IHasEndTime)?.EndTime ?? h.StartTime + release_delay).OrderBy(h => h.Key))
- {
- var groupEndTime = obj.Key;
-
- int activeColumnsAtEnd = 0;
- for (int i = 0; i < availableColumns; i++)
- {
- if (holdEndTimes[i] > groupEndTime)
- activeColumnsAtEnd |= 1 << i;
- }
-
- Replay.Frames.Add(new ReplayFrame(groupEndTime, activeColumnsAtEnd, 0, ReplayButtonState.None));
- }
+ Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns));
}
- Replay.Frames = Replay.Frames
- // Pick the maximum activeColumns for all frames at the same time
- .GroupBy(f => f.Time)
- .Select(g => new ReplayFrame(g.First().Time, maxMouseX(g), 0, ReplayButtonState.None))
- // The addition of release frames above maybe result in unordered frames, but we need them ordered
- .OrderBy(f => f.Time)
- .ToList();
-
return Replay;
}
- ///
- /// Finds the maximum by count of bits from a grouping of s.
- ///
- /// The grouping to search.
- /// The maximum by count of bits.
- private float maxMouseX(IGrouping group)
+ private IEnumerable generateActionPoints()
{
- int currentCount = -1;
- int currentMax = 0;
-
- foreach (var val in group)
+ foreach (var obj in Beatmap.HitObjects)
{
- int newCount = countBits((int)(val.MouseX ?? 0));
- if (newCount > currentCount)
- {
- currentCount = newCount;
- currentMax = (int)(val.MouseX ?? 0);
- }
+ yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
+ yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
}
-
- return currentMax;
}
- ///
- /// Counts the number of bits set in a value.
- ///
- /// The value to count.
- /// The number of set bits.
- private int countBits(int value)
+ private interface IActionPoint
{
- int count = 0;
- while (value > 0)
- {
- if ((value & 1) > 0)
- count++;
- value >>= 1;
- }
+ double Time { get; set; }
+ int Column { get; set; }
+ }
- return count;
+ private struct HitPoint : IActionPoint
+ {
+ public double Time { get; set; }
+ public int Column { get; set; }
+ }
+
+ private struct ReleasePoint : IActionPoint
+ {
+ public double Time { get; set; }
+ public int Column { get; set; }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index e352997f2c..12534d6eb4 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -2,29 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Input;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays
{
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
{
- public ManiaFramedReplayInputHandler(Replay replay)
+ private readonly ManiaRulesetContainer container;
+
+ public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
: base(replay)
{
+ this.container = container;
}
+ private ManiaPlayfield playfield;
public override List GetPendingStates()
{
var actions = new List();
- int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
+ if (playfield == null)
+ playfield = (ManiaPlayfield)container.Playfield;
+ int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
int counter = 0;
while (activeColumns > 0)
{
if ((activeColumns & 1) > 0)
- actions.Add(ManiaAction.Key1 + counter);
+ actions.Add(playfield.Columns.ElementAt(counter).Action);
counter++;
activeColumns >>= 1;
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
new file mode 100644
index 0000000000..d1bc7da911
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.Mania.Replays
+{
+ public class ManiaReplayFrame : ReplayFrame
+ {
+ public override bool IsImportant => MouseX > 0;
+
+ public ManiaReplayFrame(double time, int activeColumns)
+ : base(time, activeColumns, null, ReplayButtonState.None)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
new file mode 100644
index 0000000000..805553eafc
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
@@ -0,0 +1,173 @@
+// 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.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCaseAutoGeneration : OsuTestCase
+ {
+ [Test]
+ public void TestSingleNote()
+ {
+ // | |
+ // | - |
+ // | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
+ }
+
+ [Test]
+ public void TestSingleHoldNote()
+ {
+ // | |
+ // | * |
+ // | * |
+ // | * |
+ // | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
+ }
+
+ [Test]
+ public void TestSingleNoteChord()
+ {
+ // | | |
+ // | - | - |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteChord()
+ {
+ // | | |
+ // | * | * |
+ // | * | * |
+ // | * | * |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
+ Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
+ }
+
+ [Test]
+ public void TestSingleNoteStair()
+ {
+ // | | |
+ // | | - |
+ // | - | |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
+ Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 1 has not been released");
+ Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 2 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteStair()
+ {
+ // | | |
+ // | | * |
+ // | * | * |
+ // | * | * |
+ // | * | |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
+ Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
+ Assert.AreEqual(3, generated.Frames[2].MouseX, "Keys 1 and 2 have not been pressed");
+ Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 1 has not been released");
+ Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteWithReleasePress()
+ {
+ // | | |
+ // | * | - |
+ // | * | |
+ // | * | |
+ // | | |
+
+ var beatmap = new Beatmap();
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
+ beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
+ Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
+ Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
+ Assert.AreEqual(2, generated.Frames[2].MouseX, "Key 1 has not been released or key 2 has not been pressed");
+ Assert.AreEqual(0, generated.Frames[3].MouseX, "Keys 1 and 2 have not been released");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..8aa8c6b799
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/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.Mania.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new ManiaRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 08acd46c57..cbbcb84b31 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -124,6 +124,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
- protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
+ protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
}
}
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index 6f45a64d92..ec6f59b5be 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -1,6 +1,6 @@
-
+
Debug
AnyCPU
@@ -21,7 +21,6 @@
prompt
4
false
- 6
pdbonly
@@ -72,6 +71,7 @@
+
@@ -80,8 +80,10 @@
+
+
@@ -96,9 +98,6 @@
-
- osu.licenseheader
-
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 112fcb1a30..39ec753fe1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -45,6 +45,18 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; }
}
+ ///
+ /// The position of the cursor at the point of completion of this if it was hit
+ /// with as few movements as possible. This is set and used by difficulty calculation.
+ ///
+ internal Vector2? LazyEndPosition;
+
+ ///
+ /// The distance travelled by the cursor upon completion of this if it was hit
+ /// with as few movements as possible. This is set and used by difficulty calculation.
+ ///
+ internal float LazyTravelDistance;
+
public List RepeatSamples { get; set; } = new List();
public int RepeatCount { get; set; } = 1;
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
index 537874f643..3d185ab694 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
(h as Slider)?.Curve?.Calculate();
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override double Calculate(Dictionary categoryDifficulty = null)
{
- OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects);
+ OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate);
Skill[] skills =
{
new Aim(),
@@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
if (categoryDifficulty != null)
{
- categoryDifficulty.Add("Aim", aimRating.ToString("0.00"));
- categoryDifficulty.Add("Speed", speedRating.ToString("0.00"));
+ categoryDifficulty.Add("Aim", aimRating);
+ categoryDifficulty.Add("Speed", speedRating);
}
return starRating;
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
index c6ecc3a506..f8e9423e29 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
@@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
/// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as
/// which contains extra data required for difficulty calculation.
///
- public OsuDifficultyBeatmap(List objects)
+ public OsuDifficultyBeatmap(List objects, double timeRate)
{
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator.
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
- difficultyObjects = createDifficultyObjectEnumerator(objects);
+ difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
}
///
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- private IEnumerator createDifficultyObjectEnumerator(List objects)
+ private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate)
{
// We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
OsuHitObject[] triangle = new OsuHitObject[3];
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
triangle[1] = triangle[0];
triangle[0] = objects[i];
- yield return new OsuDifficultyHitObject(triangle);
+ yield return new OsuDifficultyHitObject(triangle, timeRate);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
index bdeb62df3e..972677a6f1 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Linq;
+using OpenTK;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
@@ -33,13 +35,17 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
private const int normalized_radius = 52;
+ private readonly double timeRate;
+
private readonly OsuHitObject[] t;
///
/// Initializes the object calculating extra data required for difficulty calculation.
///
- public OsuDifficultyHitObject(OsuHitObject[] triangle)
+ public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
{
+ this.timeRate = timeRate;
+
t = triangle;
BaseObject = t[0];
setDistances();
@@ -57,14 +63,53 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
- Distance = (t[0].StackedPosition - t[1].StackedPosition).Length * scalingFactor;
+ Vector2 lastCursorPosition = t[1].StackedPosition;
+ float lastTravelDistance = 0;
+
+ var lastSlider = t[1] as Slider;
+ if (lastSlider != null)
+ {
+ computeSliderCursorPosition(lastSlider);
+ lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
+ lastTravelDistance = lastSlider.LazyTravelDistance;
+ }
+
+ Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
}
private void setTimingValues()
{
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
- DeltaTime = Math.Max(40, t[0].StartTime - t[1].StartTime);
+ DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
TimeUntilHit = 450; // BaseObject.PreEmpt;
}
+
+ private void computeSliderCursorPosition(Slider slider)
+ {
+ if (slider.LazyEndPosition != null)
+ return;
+ slider.LazyEndPosition = slider.StackedPosition;
+
+ float approxFollowCircleRadius = (float)(slider.Radius * 3);
+ var computeVertex = new Action(t =>
+ {
+ var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value;
+ float dist = diff.Length;
+
+ if (dist > approxFollowCircleRadius)
+ {
+ // The cursor would be outside the follow circle, we need to move it
+ diff.Normalize(); // Obtain direction of diff
+ dist -= approxFollowCircleRadius;
+ slider.LazyEndPosition += diff * dist;
+ slider.LazyTravelDistance += dist;
+ }
+ });
+
+ var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t);
+ foreach (var time in scoringTimes)
+ computeVertex(time);
+ computeVertex(slider.EndTime);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index c87328d87c..fdf2a458b7 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -14,6 +14,8 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Osu.Scoring;
namespace osu.Game.Rulesets.Osu
{
@@ -114,6 +116,8 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
+ public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
+
public override string Description => "osu!";
public override SettingsSubsection CreateSettings() => new OsuSettings();
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
new file mode 100644
index 0000000000..cd6b6c5e27
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -0,0 +1,199 @@
+// 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.Mods;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Scoring
+{
+ public class OsuPerformanceCalculator : PerformanceCalculator
+ {
+ private readonly int countHitCircles;
+ private readonly int beatmapMaxCombo;
+
+ private Mod[] mods;
+ private double realApproachRate;
+ private double accuracy;
+ private int scoreMaxCombo;
+ private int count300;
+ private int count100;
+ private int count50;
+ private int countMiss;
+
+ public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+ : base(ruleset, beatmap, score)
+ {
+ countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
+
+ beatmapMaxCombo = Beatmap.HitObjects.Count;
+ beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.RepeatCount + s.Ticks.Count());
+ }
+
+ public override double Calculate(Dictionary categoryRatings = null)
+ {
+ mods = Score.Mods;
+ accuracy = Score.Accuracy;
+ scoreMaxCombo = Score.MaxCombo;
+ count300 = Convert.ToInt32(Score.Statistics["300"]);
+ count100 = Convert.ToInt32(Score.Statistics["100"]);
+ count50 = Convert.ToInt32(Score.Statistics["50"]);
+ countMiss = Convert.ToInt32(Score.Statistics["x"]);
+
+ // Don't count scores made with supposedly unranked mods
+ if (mods.Any(m => !m.Ranked))
+ return 0;
+
+ // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done
+ // locally for now as doing so would modify animations and other things unexpectedly
+ // DO NOT MODIFY THIS
+ double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate;
+ if (mods.Any(m => m is OsuModHardRock))
+ ar = Math.Min(10, ar * 1.4);
+ if (mods.Any(m => m is OsuModEasy))
+ ar = Math.Max(0, ar / 2);
+ double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
+ realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
+
+ // Custom multipliers for NoFail and SpunOut.
+ double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+
+ if (mods.Any(m => m is OsuModNoFail))
+ multiplier *= 0.90f;
+
+ if (mods.Any(m => m is OsuModSpunOut))
+ multiplier *= 0.95f;
+
+ double aimValue = computeAimValue();
+ double speedValue = computeSpeedValue();
+ double accuracyValue = computeAccuracyValue();
+ double totalValue =
+ Math.Pow(
+ Math.Pow(aimValue, 1.1f) +
+ Math.Pow(speedValue, 1.1f) +
+ Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f
+ ) * multiplier;
+
+ if (categoryRatings != null)
+ {
+ categoryRatings.Add("Aim", aimValue);
+ categoryRatings.Add("Speed", speedValue);
+ categoryRatings.Add("Accuracy", accuracyValue);
+ }
+
+ return totalValue;
+ }
+
+ private double computeAimValue()
+ {
+ double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+
+ // Longer maps are worth more
+ double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+
+ aimValue *= lengthBonus;
+
+ // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
+ aimValue *= Math.Pow(0.97f, countMiss);
+
+ // Combo scaling
+ if (beatmapMaxCombo > 0)
+ aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+
+ double approachRateFactor = 1.0f;
+ if (realApproachRate > 10.33f)
+ approachRateFactor += 0.45f * (realApproachRate - 10.33f);
+ else if (realApproachRate < 8.0f)
+ {
+ // HD is worth more with lower ar!
+ if (mods.Any(h => h is OsuModHidden))
+ approachRateFactor += 0.02f * (8.0f - realApproachRate);
+ else
+ approachRateFactor += 0.01f * (8.0f - realApproachRate);
+ }
+
+ aimValue *= approachRateFactor;
+
+ if (mods.Any(h => h is OsuModHidden))
+ aimValue *= 1.18f;
+
+ if (mods.Any(h => h is OsuModFlashlight))
+ {
+ // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
+ aimValue *= 1.45f * lengthBonus;
+ }
+
+ // Scale the aim value with accuracy _slightly_
+ aimValue *= 0.5f + accuracy / 2.0f;
+ // It is important to also consider accuracy difficulty when doing that
+ aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
+
+ return aimValue;
+ }
+
+ private double computeSpeedValue()
+ {
+ double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
+
+ // Longer maps are worth more
+ speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
+ (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
+
+ // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
+ speedValue *= Math.Pow(0.97f, countMiss);
+
+ // Combo scaling
+ if (beatmapMaxCombo > 0)
+ speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
+
+ // Scale the speed value with accuracy _slightly_
+ speedValue *= 0.5f + accuracy / 2.0f;
+ // It is important to also consider accuracy difficulty when doing that
+ speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
+
+ return speedValue;
+ }
+
+ private double computeAccuracyValue()
+ {
+ // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
+ double betterAccuracyPercentage;
+ int amountHitObjectsWithAccuracy = countHitCircles;
+
+ if (amountHitObjectsWithAccuracy > 0)
+ betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
+ else
+ betterAccuracyPercentage = 0;
+
+ // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points
+ if (betterAccuracyPercentage < 0)
+ betterAccuracyPercentage = 0;
+
+ // Lots of arbitrary values from testing.
+ // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
+ double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
+
+ // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+ accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
+
+ if (mods.Any(m => m is OsuModHidden))
+ accuracyValue *= 1.02f;
+ if (mods.Any(m => m is OsuModFlashlight))
+ accuracyValue *= 1.02f;
+
+ return accuracyValue;
+ }
+
+ private double totalHits => count300 + count100 + count50 + countMiss;
+ private double totalSuccessfulHits => count300 + count100 + count50;
+
+ protected override BeatmapConverter CreateBeatmapConverter() => new OsuBeatmapConverter();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..25a6110459
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/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.Osu.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new OsuRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 3c90749777..5a0581f48b 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -1,6 +1,6 @@
-
+
Debug
AnyCPU
@@ -22,7 +22,6 @@
prompt
4
false
- 6
pdbonly
@@ -85,9 +84,11 @@
+
+
@@ -103,9 +104,6 @@
-
- osu.licenseheader
-
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
index e881942fbf..e74c12fa5d 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
@@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using System.Collections.Generic;
-using System.Globalization;
using System;
namespace osu.Game.Rulesets.Taiko
@@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko
{
}
- public override double Calculate(Dictionary categoryDifficulty = null)
+ public override double Calculate(Dictionary categoryDifficulty = null)
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
@@ -53,8 +52,8 @@ namespace osu.Game.Rulesets.Taiko
if (categoryDifficulty != null)
{
- categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture));
- categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture));
+ categoryDifficulty.Add("Strain", starRating);
+ categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
}
return starRating;
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
new file mode 100644
index 0000000000..96d5b20b6e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/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.Taiko.Tests
+{
+ [Ignore("getting CI working")]
+ public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
+ {
+ public TestCasePerformancePoints()
+ : base(new TaikoRuleset(new RulesetInfo()))
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index bf627d205a..72e9e6a061 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -1,6 +1,6 @@
-
+
Debug
AnyCPU
@@ -21,7 +21,6 @@
prompt
4
false
- 6
pdbonly
@@ -83,6 +82,7 @@
+
@@ -95,9 +95,6 @@
-
- osu.licenseheader
-
diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
index 22a2d717e4..badb98e6b7 100644
--- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual
Add(container = new ExampleContainer());
- AddStep(@"Add button", () => container.Add(new OsuButton
+ AddStep(@"Add button", () => container.Add(new TriangleButton
{
RelativeSizeAxes = Axes.X,
Text = @"Button",
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 312a564f71..b4242052d5 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -1,5 +1,6 @@
-
+
+
Debug
AnyCPU
@@ -19,7 +20,6 @@
4
false
false
- 6
true
@@ -45,9 +45,6 @@
-
- osu.licenseheader
-
diff --git a/osu.Game.props b/osu.Game.props
new file mode 100644
index 0000000000..60a5e97944
--- /dev/null
+++ b/osu.Game.props
@@ -0,0 +1,13 @@
+
+
+
+
+
+ 7
+
+
+
+ osu.licenseheader
+
+
+
\ No newline at end of file
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/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/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/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 2a8178882e..8c96074352 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -31,7 +31,7 @@ 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
-
- osu.licenseheader
-
@@ -271,6 +268,9 @@
+
+
+
20171019041408_InitialCreate.cs
@@ -280,7 +280,7 @@
20171025071459_AddMissingIndexRules.cs
-
+
20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs
@@ -366,7 +366,7 @@
-
+
@@ -629,6 +629,7 @@
+
@@ -784,6 +785,7 @@
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index fdfbf25144..76929dcbb3 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -34,6 +34,7 @@
HINT
WARNING
WARNING
+ HINT
WARNING
WARNING
DO_NOT_SHOW
@@ -44,13 +45,16 @@
WARNING
ERROR
HINT
+ HINT
HINT
WARNING
WARNING
+ HINT
DO_NOT_SHOW
HINT
HINT
HINT
+ HINT
WARNING
WARNING
WARNING
@@ -149,6 +153,7 @@
WARNING
WARNING
WARNING
+ HINT
WARNING
WARNING
HINT