diff --git a/appveyor.yml b/appveyor.yml
index 4dcaa7b45e..be1727e7d7 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,7 +2,5 @@ clone_depth: 1
version: '{branch}-{build}'
image: Previous Visual Studio 2017
test: off
-install:
- - cmd: git submodule update --init --recursive --depth=5
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
new file mode 100644
index 0000000000..d36298175b
--- /dev/null
+++ b/appveyor_deploy.yml
@@ -0,0 +1,10 @@
+clone_depth: 1
+version: '{build}'
+image: Previous Visual Studio 2017
+test: off
+skip_non_tags: true
+build_script:
+ - cmd: PowerShell -Version 2.0 .\build.ps1
+deploy:
+ - provider: Environment
+ name: nuget
diff --git a/osu.Android.props b/osu.Android.props
index 93a9a073a4..4167d07698 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -1,5 +1,7 @@
+ Debug
+ AnyCPU
bin\$(Configuration)
4
2.0
@@ -35,7 +37,7 @@
None
True
prompt
- true
+ true
false
SdkOnly
False
@@ -60,7 +62,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 406c0af28d..720ef1db42 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -82,11 +83,11 @@ namespace osu.Game.Rulesets.Catch.Tests
remove { }
}
- public Drawable GetDrawableComponent(string componentName)
+ public Drawable GetDrawableComponent(ISkinComponent component)
{
- switch (componentName)
+ switch (component.LookupName)
{
- case "Play/Catch/fruit-catcher-idle":
+ case "Gameplay/catch/fruit-catcher-idle":
return new CatcherCustomSkin();
}
@@ -99,8 +100,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public Texture GetTexture(string componentName) =>
throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration =>
- throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
}
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 71e05083be..5428b4eeb8 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Catch
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
+ public const string SHORT_NAME = "fruits";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
@@ -117,7 +119,7 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch";
- public override string ShortName => "fruits";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
new file mode 100644
index 0000000000..8bf53e53e3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch
+{
+ public class CatchSkinComponent : GameplaySkinComponent
+ {
+ public CatchSkinComponent(CatchSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
new file mode 100644
index 0000000000..7e482d4045
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Catch
+{
+ public enum CatchSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 19a1b59752..a25d9cb67e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 00734810b3..51deae6e85 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -71,11 +71,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
switch (state)
{
case ArmedState.Miss:
- this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire();
+ this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
case ArmedState.Hit:
- this.FadeOut().Expire();
+ this.FadeOut();
break;
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
similarity index 87%
rename from osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
rename to osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
index 837662f5fe..ff793a372e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
-namespace osu.Game.Rulesets.Catch.Objects
+namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchHitWindows : HitWindows
{
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 99b22b2d56..18785d65ea 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index c0c1952064..e3c6c93d01 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle")
+ InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
index 20ac5eaa39..f260357df5 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
@@ -12,6 +13,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
+ [HeadlessTest]
public class TestSceneAutoGeneration : OsuTestScene
{
[Test]
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
new file mode 100644
index 0000000000..26a1b1b1ec
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
@@ -0,0 +1,62 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestSceneHitExplosion : OsuTestScene
+ {
+ private ScrollingTestContainer scrolling;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableNote),
+ typeof(DrawableManiaHitObject),
+ };
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Child = scrolling = new ScrollingTestContainer(ScrollingDirection.Down)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.Y,
+ Y = -0.25f,
+ Size = new Vector2(Column.COLUMN_WIDTH, NotePiece.NOTE_HEIGHT),
+ };
+
+ int runcount = 0;
+
+ AddRepeatStep("explode", () =>
+ {
+ runcount++;
+
+ if (runcount % 15 > 12)
+ return;
+
+ scrolling.AddRange(new Drawable[]
+ {
+ new HitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+ }, 100);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index 031abb08e2..8dae5e6d84 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@@ -11,6 +11,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
@@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
Child = new FillFlowContainer
{
+ Clock = new FramedClock(new ManualClock()),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
@@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject)
{
- var note = new Note { StartTime = 999999999 };
+ var note = new Note { StartTime = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -77,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject)
{
- var note = new HoldNote { StartTime = 999999999, Duration = 5000 };
+ var note = new HoldNote { StartTime = 0, Duration = 5000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -133,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Width = 1.25f,
- Colour = Color4.Black.Opacity(0.5f)
+ Colour = Color4.Green.Opacity(0.5f)
},
content = new Container { RelativeSizeAxes = Axes.Both }
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
index 395e6daf0a..e7fd601abe 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
@@ -114,8 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new BarLine
{
StartTime = Time.Current + 2000,
- ControlPoint = new TimingControlPoint(),
- BeatIndex = major ? 0 : 1
+ Major = major,
};
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index b591f9da22..f5412dcfc5 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
+ Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 4a9c22d339..37cba1fd3c 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -11,7 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Difficulty
{
@@ -32,12 +34,15 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (beatmap.HitObjects.Count == 0)
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
+ HitWindows hitWindows = new ManiaHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
return new ManiaDifficultyAttributes
{
StarRating = difficultyValue(skills) * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 0de86c2149..0c4e7d4858 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
+ public const string SHORT_NAME = "mania";
+
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
@@ -163,7 +165,7 @@ namespace osu.Game.Rulesets.Mania
public override string Description => "osu!mania";
- public override string ShortName => "mania";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
new file mode 100644
index 0000000000..69bd4b0ecf
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class ManiaSkinComponent : GameplaySkinComponent
+ {
+ public ManiaSkinComponent(ManiaSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
new file mode 100644
index 0000000000..6d85816e5a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania
+{
+ public enum ManiaSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
deleted file mode 100644
index 4c644a8f09..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Beatmaps.ControlPoints;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class BarLine : ManiaHitObject
- {
- ///
- /// The control point which this bar line is part of.
- ///
- public TimingControlPoint ControlPoint;
-
- ///
- /// The index of the beat which this bar line represents within the control point.
- /// This is a "major" bar line if % == 0.
- ///
- public int BeatIndex;
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index e9c352c97e..be21610525 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -4,6 +4,7 @@
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// Visualises a . Although this derives DrawableManiaHitObject,
/// this does not handle input/sound like a normal hit object.
///
- public class DrawableBarLine : DrawableManiaHitObject
+ public class DrawableBarLine : DrawableHitObject
{
///
/// Height of major bar line triangles.
@@ -40,9 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Colour = new Color4(255, 204, 33, 255),
});
- bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
-
- if (isMajor)
+ if (barLine.Major)
{
AddInternal(new EquilateralTriangle
{
@@ -65,10 +64,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
- if (!isMajor && barLine.BeatIndex % 2 == 1)
+ if (!barLine.Major)
Alpha = 0.2f;
}
+ protected override void UpdateInitialTransforms()
+ {
+ }
+
protected override void UpdateStateTransforms(ArmedState state)
{
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index fc3b6885d7..c5c157608f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -209,6 +210,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
// Factor in the release lenience
timeOffset /= release_window_lenience;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index e5b114ca81..5bfa07bd14 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -51,11 +51,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (state)
{
case ArmedState.Miss:
- this.FadeOut(150, Easing.In).Expire();
+ this.FadeOut(150, Easing.In);
break;
case ArmedState.Hit:
- this.FadeOut(150, Easing.OutQuint).Expire();
+ this.FadeOut(150, Easing.OutQuint);
break;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index dccff7f6ac..31221c05ee 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler
{
+ public const float CORNER_RADIUS = NotePiece.NOTE_HEIGHT / 2;
+
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject)
@@ -37,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
- Colour = colour.NewValue.Lighten(1f).Opacity(0.6f),
+ Colour = colour.NewValue.Lighten(1f).Opacity(0.2f),
Radius = 10,
};
}, true);
@@ -52,6 +55,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
index bb33693783..4521af7dfb 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
@@ -18,8 +18,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
///
internal class NotePiece : Container, IHasAccentColour
{
- public const float NOTE_HEIGHT = 10;
- private const float head_colour_height = 6;
+ public const float NOTE_HEIGHT = 12;
private readonly IBindable direction = new Bindable();
@@ -39,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
colouredBox = new Box
{
RelativeSizeAxes = Axes.X,
- Height = head_colour_height,
- Alpha = 0.2f
+ Height = NOTE_HEIGHT / 2,
+ Alpha = 0.1f
}
};
}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 5e9f46d9c7..0c82cf7bbc 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -99,5 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects
}
public override Judgement CreateJudgement() => new HoldNoteJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
index c133ee73b1..d0125f8793 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
@@ -3,6 +3,7 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public class HoldNoteTick : ManiaHitObject
{
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 70720a926b..995e1516cb 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -3,7 +3,9 @@
using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
deleted file mode 100644
index 5f2ceab48b..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class ManiaHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Perfect, (44.8, 38.8, 27.8) },
- { HitResult.Great, (128, 98, 68) },
- { HitResult.Good, (194, 164, 134) },
- { HitResult.Ok, (254, 224, 194) },
- { HitResult.Meh, (302, 272, 242) },
- { HitResult.Miss, (376, 346, 316) },
- };
-
- public override bool IsHitResultAllowed(HitResult result) => true;
-
- public override void SetDifficulty(double difficulty)
- {
- Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
similarity index 58%
rename from osu.Game.Rulesets.Taiko/Objects/BarLine.cs
rename to osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
index a07012fd71..549f0f9214 100644
--- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-namespace osu.Game.Rulesets.Taiko.Objects
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
{
- public class BarLine : TaikoHitObject
+ public class ManiaHitWindows : HitWindows
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 5caf08fb1e..49894a644c 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 91dd236ab1..3d2a070b0f 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
@@ -11,6 +11,8 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
{
- private const float column_width = 45;
+ public const float COLUMN_WIDTH = 80;
private const float special_column_width = 70;
///
@@ -41,10 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI
Index = index;
RelativeSizeAxes = Axes.Y;
- Width = column_width;
-
- Masking = true;
- CornerRadius = 5;
+ Width = COLUMN_WIDTH;
background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
@@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.UI
explosionContainer = new Container
{
Name = "Hit explosions",
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
}
}
},
@@ -90,6 +89,12 @@ namespace osu.Game.Rulesets.Mania.UI
Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
+ explosionContainer.Padding = new MarginPadding
+ {
+ Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0,
+ Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0
+ };
+
keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
}
@@ -108,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.UI
isSpecial = value;
- Width = isSpecial ? special_column_width : column_width;
+ Width = isSpecial ? special_column_width : COLUMN_WIDTH;
}
}
@@ -163,9 +168,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
- explosionContainer.Add(new HitExplosion(judgedObject)
+ explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)
{
- Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
+ Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre,
+ Origin = Anchor.Centre
});
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index 5ee78aa496..57241da564 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
- Alpha = 0.3f
},
backgroundOverlay = new Box
{
@@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
if (!IsLoaded)
return;
- background.Colour = AccentColour;
+ background.Colour = AccentColour.Darken(5);
var brightPoint = AccentColour.Opacity(0.6f);
var dimPoint = AccentColour.Opacity(0);
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index a0d713067d..386bcbb724 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
@@ -17,7 +18,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
- private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private readonly IBindable direction = new Bindable();
@@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
- Height = hit_target_height,
+ Height = NotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
Colour = Color4.Black
},
hitTargetLine = new Container
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index f26526fe70..29863fba2e 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -2,14 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -19,8 +16,8 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -45,33 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
- // Generate the bar lines
- double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints;
- var barLines = new List();
-
- for (int i = 0; i < timingPoints.Count; i++)
- {
- TimingControlPoint point = timingPoints[i];
-
- // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
- double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
-
- int index = 0;
-
- for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
- {
- barLines.Add(new BarLine
- {
- StartTime = t,
- ControlPoint = point,
- BeatIndex = index
- });
- }
- }
-
- BarLines = barLines;
+ BarLines = new BarLineGenerator(Beatmap).BarLines;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index 48470add8b..ccbff226a9 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -1,16 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
-using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -18,51 +16,112 @@ namespace osu.Game.Rulesets.Mania.UI
{
public override bool RemoveWhenNotAlive => true;
- private readonly CircularContainer circle;
+ private readonly CircularContainer largeFaint;
+ private readonly CircularContainer mainGlow1;
- public HitExplosion(DrawableHitObject judgedObject)
+ public HitExplosion(Color4 objectColour, bool isSmall = false)
{
- bool isTick = judgedObject is DrawableHoldNoteTick;
-
- Origin = Anchor.Centre;
-
RelativeSizeAxes = Axes.X;
- Y = NotePiece.NOTE_HEIGHT / 2;
Height = NotePiece.NOTE_HEIGHT;
// scale roughly in-line with visual appearance of notes
- Scale = new Vector2(isTick ? 0.4f : 0.8f);
+ Scale = new Vector2(1f, 0.6f);
- InternalChild = circle = new CircularContainer
+ if (isSmall)
+ Scale *= 0.5f;
+
+ const float angle_variangle = 15; // should be less than 45
+
+ const float roundness = 80;
+
+ const float initial_height = 10;
+
+ var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
+
+ InternalChildren = new Drawable[]
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.1f),
- EdgeEffect = new EdgeEffectParameters
+ largeFaint = new CircularContainer
{
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour.Value, Color4.White, 0, 1),
- Radius = 100,
- },
- Child = new Box
- {
- Alpha = 0,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- AlwaysPresent = true
+ Masking = true,
+ // we want our size to be very small so the glow dominates it.
+ Size = new Vector2(0.8f),
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ },
+ },
+ mainGlow1 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
}
};
}
protected override void LoadComplete()
{
+ const double duration = 200;
+
base.LoadComplete();
- circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint);
- this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint);
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+ mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint);
+
+ this.FadeOut(duration, Easing.Out);
Expire(true);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 5ab07416a6..12faa499ad 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index a28de7ea58..98a4b7d0b6 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png
deleted file mode 100755
index db2f4a5730..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png
deleted file mode 100755
index 75f9ba5ea6..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png
deleted file mode 100755
index ebf59c18ba..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png
deleted file mode 100644
index bdb2bcbc41..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit0@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png
deleted file mode 100644
index 7db8eb3124..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png
deleted file mode 100644
index 206840e467..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit100k@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png
deleted file mode 100644
index 2c7c07852f..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png
deleted file mode 100644
index 1ce746e3a4..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300g@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png
deleted file mode 100755
index b0db9c00af..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png
deleted file mode 100644
index 94c09d263a..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit50@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png
deleted file mode 100755
index 6674616472..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png
deleted file mode 100755
index 1f98c1697e..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png
deleted file mode 100644
index 626fd91e38..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-nd@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png
deleted file mode 100644
index 76fd9ab168..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb-spec@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png
deleted file mode 100644
index 0a24a72808..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb0@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png
deleted file mode 100644
index e99f076947..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb1@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png
deleted file mode 100644
index cd36a0ae16..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb2@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png
deleted file mode 100644
index f494bd3f51..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb3@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png
deleted file mode 100644
index a5b19887d6..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb4@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png
deleted file mode 100644
index 4bb01f0e88..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb5@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png
deleted file mode 100644
index 859e0aa4c1..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb6@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png
deleted file mode 100644
index 90efda0994..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb7@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png
deleted file mode 100644
index fcdf4ed4a4..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb8@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png
deleted file mode 100644
index c990cf0fe6..0000000000
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/sliderb9@2x.png and /dev/null differ
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
index 29e5146ff1..38aac50df6 100644
--- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -26,12 +26,12 @@ namespace osu.Game.Rulesets.Osu.Tests
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load(AudioManager audio, SkinManager skinManager)
{
var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll");
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
- defaultSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/default_skin"), audio, false);
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
new file mode 100644
index 0000000000..685a51d208
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -0,0 +1,128 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing.Input;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneCursorTrail : OsuTestScene
+ {
+ [Test]
+ public void TestSmoothCursorTrail()
+ {
+ Container scalingContainer = null;
+
+ createTest(() => scalingContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new CursorTrail()
+ });
+
+ AddStep("set large scale", () => scalingContainer.Scale = new Vector2(10));
+ }
+
+ [Test]
+ public void TestLegacySmoothCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(false)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ [Test]
+ public void TestLegacyDisjointCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(true)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ private void createTest(Func createContent) => AddStep("create trail", () =>
+ {
+ Clear();
+
+ Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Child = new MovingCursorInputManager { Child = createContent?.Invoke() }
+ });
+ });
+
+ [Cached(typeof(ISkinSource))]
+ private class LegacySkinContainer : Container, ISkinSource
+ {
+ private readonly bool disjoint;
+
+ public LegacySkinContainer(bool disjoint)
+ {
+ this.disjoint = disjoint;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName)
+ {
+ switch (componentName)
+ {
+ case "cursortrail":
+ var tex = new Texture(Texture.WhitePixel.TextureGL);
+
+ if (disjoint)
+ tex.ScaleAdjust = 1 / 25f;
+ return tex;
+
+ case "cursormiddle":
+ return disjoint ? null : Texture.WhitePixel;
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 1000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index 82a8d0e5e6..433ec6bd25 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
- new DrawableOsuJudgement(new JudgementResult(null) { Type = result }, null)
+ new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index ebb6cd3a5a..aa170eae1e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -6,23 +6,68 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing.Input;
using osu.Game.Rulesets.Osu.UI.Cursor;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneGameplayCursor : SkinnableTestScene
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) };
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuCursorContainer),
+ typeof(CursorTrail)
+ };
[BackgroundDependencyLoader]
private void load()
{
- SetContents(() => new OsuCursorContainer
+ SetContents(() => new MovingCursorInputManager
{
- RelativeSizeAxes = Axes.Both,
- Masking = true,
+ Child = new ClickingCursorContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ }
});
}
+
+ private class ClickingCursorContainer : OsuCursorContainer
+ {
+ protected override void Update()
+ {
+ base.Update();
+
+ double currentTime = Time.Current;
+
+ if (((int)(currentTime / 1000)) % 2 == 0)
+ OnPressed(OsuAction.LeftButton);
+ else
+ OnReleased(OsuAction.LeftButton);
+ }
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 5000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index 84a73c7cfc..863d0eda09 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -13,8 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
- Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(),
- drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
+ Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
+
+ double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
+ Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
return drawableHitObject;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index 731b0a84e9..02c65db6ad 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
@@ -119,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests
this.identifier = identifier;
}
- public Drawable GetDrawableComponent(string componentName)
+ public Drawable GetDrawableComponent(ISkinComponent component)
{
if (!enabled) return null;
@@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default;
+ public IBindable GetConfig(TLookup lookup) => null;
public event Action SourceChanged;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index c197933233..b0d261a1cc 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -13,6 +13,8 @@ using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -34,8 +36,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
+ HitWindows hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
+ double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
index c7661bddb1..15444b847b 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Judgements
{
@@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public ComboResult ComboType;
- public OsuJudgementResult(Judgement judgement)
- : base(judgement)
+ public OsuJudgementResult(HitObject hitObject, Judgement judgement)
+ : base(hitObject, judgement)
{
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 445f81c6d4..1eb37f8119 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Mods
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Texture = textures.Get("Play/osu/blinds-panel");
+ Texture = textures.Get("Gameplay/osu/blinds-panel");
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 5625028707..649b01c132 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
@@ -38,7 +39,12 @@ namespace osu.Game.Rulesets.Osu.Mods
if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
continue;
- requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime);
+ if (osuHit is DrawableHitCircle && osuHit.IsHovered)
+ {
+ Debug.Assert(osuHit.HitObject.HitWindows != null);
+ requiresHit |= osuHit.HitObject.HitWindows.CanBeHit(relativetime);
+ }
+
requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index 523e911434..89ffddf4cb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
Origin = Anchor.Centre;
- Child = new SkinnableDrawable("Play/osu/followpoint", _ => new Container
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container
{
Masking = true,
AutoSizeAxes = Axes.Both,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 0af278f6a4..83646c561d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -9,8 +10,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
-using osuTK;
using osu.Game.Rulesets.Scoring;
+using osuTK;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
- mainContent = new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
+ mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@@ -85,8 +86,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true);
}
+ public override double LifetimeStart
+ {
+ get => base.LifetimeStart;
+ set
+ {
+ base.LifetimeStart = value;
+ ApproachCircle.LifetimeStart = value;
+ }
+ }
+
+ public override double LifetimeEnd
+ {
+ get => base.LifetimeEnd;
+ set
+ {
+ base.LifetimeEnd = value;
+ ApproachCircle.LifetimeEnd = value;
+ }
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -99,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (result == HitResult.None)
{
- Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
+ Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
return;
}
@@ -119,6 +142,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
+ Debug.Assert(HitObject.HitWindows != null);
+
switch (state)
{
case ArmedState.Idle:
@@ -127,22 +154,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true);
hitArea.HitAction = null;
-
- // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
- LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss);
break;
case ArmedState.Miss:
ApproachCircle.FadeOut(50);
this.FadeOut(100);
- Expire();
break;
case ArmedState.Hit:
ApproachCircle.FadeOut(50);
// todo: temporary / arbitrary
- this.Delay(800).Expire();
+ this.Delay(800).FadeOut();
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index b4f5642f45..c46343c73c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -41,6 +41,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
- protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ base.UpdateStateTransforms(state);
+
+ switch (state)
+ {
+ case ArmedState.Idle:
+ // Manually set to reduce the number of future alive objects to a bare minimum.
+ LifetimeStart = HitObject.StartTime - HitObject.TimePreempt;
+ break;
+ }
+ }
+
+ protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 1db1eec33e..84d2a4af9b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -9,8 +9,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
-using osuTK;
using osu.Game.Rulesets.Scoring;
+using osuTK;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
- InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon
+ InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.ChevronRight,
@@ -74,6 +74,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
switch (state)
{
case ArmedState.Idle:
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 1749ea1f60..08b43b0345 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -166,12 +167,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.SkinChanged(skin, allowFallback);
- Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
- sliderPathRadius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
+ sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
updatePathRadius();
- Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
- Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
+ Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
+ Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
}
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
@@ -201,6 +202,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
Ball.FadeIn();
Ball.ScaleTo(HitObject.Scale);
@@ -218,10 +221,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
- this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
+ this.FadeOut(fade_out_time, Easing.OutQuint);
}
-
- Expire(true);
}
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 653e73ac3f..9d4d9958a1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -8,9 +8,9 @@ using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Origin = Anchor.Centre;
- InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new CircularContainer
+ InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
{
Masking = true,
Origin = Anchor.Centre,
@@ -75,6 +75,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
switch (state)
{
case ArmedState.Idle:
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index a0bd301fdb..d1b9ee6cb4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -13,8 +13,8 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
-using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -215,14 +215,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ base.UpdateStateTransforms(state);
+
var sequence = this.Delay(Spinner.Duration).FadeOut(160);
switch (state)
{
- case ArmedState.Idle:
- Expire(true);
- break;
-
case ArmedState.Hit:
sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
break;
@@ -231,8 +229,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
sequence.ScaleTo(Scale * 0.8f, 320, Easing.In);
break;
}
-
- Expire();
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
index 5813197336..1b474f265c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
@@ -31,13 +31,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private class SkinnableApproachCircle : SkinnableSprite
{
public SkinnableApproachCircle()
- : base("Play/osu/approachcircle")
+ : base("Gameplay/osu/approachcircle")
{
}
- protected override Drawable CreateDefault(string name)
+ protected override Drawable CreateDefault(ISkinComponent component)
{
- var drawable = base.CreateDefault(name);
+ var drawable = base.CreateDefault(component);
// account for the sprite being used for the default approach circle being taken from stable,
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index a59cfc1123..210d5ff839 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Texture = textures.Get(@"Play/osu/disc"),
+ Texture = textures.Get(@"Gameplay/osu/disc"),
},
new TrianglesPiece
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
index 1d21347cba..6381ddca69 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
@@ -3,7 +3,6 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -20,12 +19,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingParameters.Additive;
Alpha = 0;
- Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece
+ Child = new TrianglesPiece
{
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
- }, s => s.GetTexture("Play/osu/hitcircle") == null);
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
index 1e3af567fe..038a2299e9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingParameters.Additive;
Alpha = 0;
- Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer
+ Child = new CircularContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
@@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both
}
- }, s => s.GetTexture("Play/osu/hitcircle") == null);
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
index a36d9e96c8..30937313fd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -22,14 +21,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite
+ Child = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Texture = textures.Get(name),
+ Texture = textures.Get("Gameplay/osu/ring-glow"),
Blending = BlendingParameters.Additive,
Alpha = 0.5f
- }, s => s.GetTexture("Play/osu/hitcircle") == null);
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
index e8dc63abca..62c4ba5ee3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new Drawable[]
{
- new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer
+ new CircularContainer
{
Masking = true,
Origin = Anchor.Centre,
@@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.5f),
},
Child = new Box()
- }, s => s.GetTexture("Play/osu/hitcircle") == null),
- number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
+ },
+ number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
index 575f2c92c5..c97b74756a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container
+ InternalChild = new Container
{
Masking = true,
CornerRadius = Size.X / 2,
@@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
RelativeSizeAxes = Axes.Both
}
}
- });
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 329aed7b81..ef7b077480 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
@@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Alpha = 0,
- Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new DefaultFollowCircle()),
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
new CircularContainer
{
@@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Child = new Container
{
RelativeSizeAxes = Axes.Both,
- Child = new SkinnableDrawable("Play/osu/sliderball", _ => new DefaultSliderBall()),
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()),
}
}
};
@@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both;
- float radius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
InternalChild = new CircularContainer
{
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index b52bfcd181..2cf877b000 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -7,6 +7,8 @@ using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
deleted file mode 100644
index add8fd53c7..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Osu.Objects
-{
- public class OsuHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Great, (160, 100, 40) },
- { HitResult.Good, (280, 200, 120) },
- { HitResult.Meh, (400, 300, 200) },
- { HitResult.Miss, (400, 400, 400) },
- };
-
- public override void SetDifficulty(double difficulty)
- {
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
index 63713541b4..a794e57c9e 100644
--- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -28,5 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index d3279652c7..2805494021 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -229,5 +230,7 @@ namespace osu.Game.Rulesets.Osu.Objects
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 4f2af64161..7e540a577b 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -5,6 +5,7 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -23,5 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 85439699dd..af7cf5b144 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -5,6 +5,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -30,5 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 8a2fd3b7aa..2e7b763966 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -31,5 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 49676933e1..ceb9ed9343 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
+ public const string SHORT_NAME = "osu";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
@@ -161,11 +163,11 @@ namespace osu.Game.Rulesets.Osu
public override string Description => "osu!";
- public override string ShortName => "osu";
+ public override string ShortName => SHORT_NAME;
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
public override int? LegacyID => 0;
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
new file mode 100644
index 0000000000..1d223f231b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu
+{
+ public class OsuSkinComponent : GameplaySkinComponent
+ {
+ public OsuSkinComponent(OsuSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => OsuRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
new file mode 100644
index 0000000000..8dd48eace0
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu
+{
+ public enum OsuSkinComponents
+ {
+ HitCircle,
+ FollowPoint,
+ Cursor,
+ CursorTrail,
+ SliderScorePoint,
+ ApproachCircle,
+ ReverseArrow,
+ HitCircleText,
+ SliderFollowCircle,
+ SliderBall
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index 690263c6a0..24320b6579 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -6,11 +6,13 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Replays
@@ -36,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Replays
///
private readonly double reactionTime;
+ private readonly HitWindows defaultHitWindows;
+
///
/// What easing to use when moving between hitobjects
///
@@ -50,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Replays
{
// Already superhuman, but still somewhat realistic
reactionTime = ApplyModsToRate(100);
+
+ defaultHitWindows = new OsuHitWindows();
+ defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
}
#endregion
@@ -91,21 +98,49 @@ namespace osu.Game.Rulesets.Osu.Replays
{
double endTime = (prev as IHasEndTime)?.EndTime ?? prev.StartTime;
+ HitWindows hitWindows = null;
+
+ switch (h)
+ {
+ case HitCircle hitCircle:
+ hitWindows = hitCircle.HitWindows;
+ break;
+
+ case Slider slider:
+ hitWindows = slider.TailCircle.HitWindows;
+ break;
+
+ case Spinner _:
+ hitWindows = defaultHitWindows;
+ break;
+ }
+
+ Debug.Assert(hitWindows != null);
+
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
- if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
+ if (h.StartTime - hitWindows.WindowFor(HitResult.Miss) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
- if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000)
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+
+ if (!(h is Spinner))
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
- else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
+ else if (h.StartTime - hitWindows.WindowFor(HitResult.Meh) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
- if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000)
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+
+ if (!(h is Spinner))
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
- else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good) > endTime + h.HitWindows.HalfWindowFor(HitResult.Good) + 50)
+ else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
- if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000)
+ AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+
+ if (!(h is Spinner))
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
new file mode 100644
index 0000000000..a6491bb3f3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Scoring
+{
+ public class OsuHitWindows : HitWindows
+ {
+ private static readonly DifficultyRange[] osu_ranges =
+ {
+ new DifficultyRange(HitResult.Great, 80, 50, 20),
+ new DifficultyRange(HitResult.Good, 140, 100, 60),
+ new DifficultyRange(HitResult.Meh, 200, 150, 100),
+ new DifficultyRange(HitResult.Miss, 200, 200, 200),
+ };
+
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Meh:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override DifficultyRange[] GetRanges() => osu_ranges;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index cf0565c6da..affe18a30d 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
-using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -22,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
private float hpDrainRate;
- private readonly Dictionary comboResultCounts = new Dictionary();
-
protected override void ApplyBeatmap(Beatmap beatmap)
{
base.ApplyBeatmap(beatmap);
@@ -31,22 +27,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
}
- protected override void Reset(bool storeResults)
- {
- base.Reset(storeResults);
- comboResultCounts.Clear();
- }
-
- protected override void ApplyResult(JudgementResult result)
- {
- base.ApplyResult(result);
-
- var osuResult = (OsuJudgementResult)result;
-
- if (result.Type != HitResult.None)
- comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1;
- }
-
protected override double HealthAdjustmentFactorFor(JudgementResult result)
{
switch (result.Type)
@@ -71,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
}
}
- protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
+ protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
public override HitWindows CreateHitWindows() => new OsuHitWindows();
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs
new file mode 100644
index 0000000000..1885c76fcc
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacyCursorTrail : CursorTrail
+ {
+ private const double disjoint_trail_time_separation = 1000 / 60.0;
+
+ private bool disjointTrail;
+ private double lastTrailTime;
+
+ public LegacyCursorTrail()
+ {
+ Blending = BlendingParameters.Additive;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ Texture = skin.GetTexture("cursortrail");
+ disjointTrail = skin.GetTexture("cursormiddle") == null;
+
+ if (Texture != null)
+ {
+ // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
+ Texture.ScaleAdjust *= 1.6f;
+ }
+ }
+
+ protected override double FadeDuration => disjointTrail ? 150 : 500;
+
+ protected override bool InterpolateMovements => !disjointTrail;
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (!disjointTrail)
+ return base.OnMouseMove(e);
+
+ if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
+ {
+ lastTrailTime = Time.Current;
+ return base.OnMouseMove(e);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
index a7906ddd24..83d507f64b 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
+ new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
index ec838c596d..81c02199d0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, DrawableHitObject drawableObject)
{
- animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White;
+ animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
InternalChildren = new[]
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
similarity index 53%
rename from osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs
rename to osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index 904064e2f0..479c250eab 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -3,21 +3,19 @@
using System;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning
{
- public class OsuLegacySkin : ISkin
+ public class OsuLegacySkinTransformer : ISkin
{
private readonly ISkin source;
- private Lazy configuration;
-
private Lazy hasHitCircle;
///
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
///
private const float legacy_circle_radius = 64 - 5;
- public OsuLegacySkin(ISkinSource source)
+ public OsuLegacySkinTransformer(ISkinSource source)
{
this.source = source;
@@ -37,32 +35,23 @@ namespace osu.Game.Rulesets.Osu.Skinning
private void sourceChanged()
{
- // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source.
- configuration = new Lazy(() =>
- {
- var config = new SkinConfiguration();
- if (hasHitCircle.Value)
- config.SliderPathRadius = legacy_circle_radius;
-
- // defaults should only be applied for non-beatmap skins (which are parsed via this constructor).
- config.CustomColours["SliderBall"] =
- source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null)
- ?? new Color4(2, 170, 255, 255);
-
- return config;
- });
-
hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null);
}
- public Drawable GetDrawableComponent(string componentName)
+ public Drawable GetDrawableComponent(ISkinComponent component)
{
- switch (componentName)
- {
- case "Play/osu/sliderfollowcircle":
- return this.GetAnimation(componentName, true, true);
+ if (!(component is OsuSkinComponent osuComponent))
+ return null;
- case "Play/osu/sliderball":
+ switch (osuComponent.Component)
+ {
+ case OsuSkinComponents.FollowPoint:
+ return this.GetAnimation(component.LookupName, true, false);
+
+ case OsuSkinComponents.SliderFollowCircle:
+ return this.GetAnimation("sliderfollowcircle", true, true);
+
+ case OsuSkinComponents.SliderBall:
var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
if (sliderBallContent != null)
@@ -80,30 +69,35 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
- case "Play/osu/hitcircle":
+ case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
return null;
- case "Play/osu/cursor":
+ case OsuSkinComponents.Cursor:
if (source.GetTexture("cursor") != null)
return new LegacyCursor();
return null;
- case "Play/osu/number-text":
+ case OsuSkinComponents.CursorTrail:
+ if (source.GetTexture("cursortrail") != null)
+ return new LegacyCursorTrail();
- string font = GetValue(config => config.HitCircleFont);
- var overlap = GetValue(config => config.HitCircleOverlap);
+ return null;
+
+ case OsuSkinComponents.HitCircleText:
+ var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default";
+ var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0;
return !hasFont(font)
? null
: new LegacySpriteText(source, font)
{
- // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size
- Scale = new Vector2(0.96f),
- Spacing = new Vector2(-overlap * 0.89f, 0)
+ // stable applies a blanket 0.8x scale to hitcircle fonts
+ Scale = new Vector2(0.8f),
+ Spacing = new Vector2(-overlap, 0)
};
}
@@ -114,8 +108,28 @@ namespace osu.Game.Rulesets.Osu.Skinning
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
- => configuration.Value is TConfiguration conf ? query.Invoke(conf) : source.GetValue(query);
+ public IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ case OsuSkinColour colour:
+ return source.GetConfig(new SkinCustomColourLookup(colour));
+
+ case OsuSkinConfiguration osuLookup:
+ switch (osuLookup)
+ {
+ case OsuSkinConfiguration.SliderPathRadius:
+ if (hasHitCircle.Value)
+ return SkinUtils.As(new BindableFloat(legacy_circle_radius));
+
+ break;
+ }
+
+ break;
+ }
+
+ return source.GetConfig(lookup);
+ }
private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null;
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
new file mode 100644
index 0000000000..4e6d3ef0e4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinColour
+ {
+ SliderTrackOverride,
+ SliderBorder,
+ SliderBall
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
new file mode 100644
index 0000000000..e7b686d27d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -0,0 +1,14 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinConfiguration
+ {
+ HitCirclePrefix,
+ HitCircleOverlap,
+ SliderBorderSize,
+ SliderPathRadius,
+ CursorExpand,
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 05eb0ffdbf..b32dfd483f 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
+using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL.Vertices;
@@ -20,30 +21,15 @@ using osuTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
- internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
+ public class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
{
- private int currentIndex;
-
- private IShader shader;
- private Texture texture;
-
- private Vector2 size => texture.Size * Scale;
-
- private double timeOffset;
-
- private float time;
-
- public override bool IsPresent => true;
-
private const int max_sprites = 2048;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
-
- private Vector2? lastPosition;
-
- private readonly InputResampler resampler = new InputResampler();
-
- protected override DrawNode CreateDrawNode() => new TrailDrawNode(this);
+ private int currentIndex;
+ private IShader shader;
+ private double timeOffset;
+ private float time;
public CursorTrail()
{
@@ -60,14 +46,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
-
[BackgroundDependencyLoader]
- private void load(ShaderManager shaders, TextureStore textures)
+ private void load(ShaderManager shaders)
{
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
- texture = textures.Get(@"Cursor/cursortrail");
- Scale = new Vector2(1 / texture.ScaleAdjust);
}
protected override void LoadComplete()
@@ -76,6 +58,42 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
resetTime();
}
+ private Texture texture = Texture.WhitePixel;
+
+ public Texture Texture
+ {
+ get => texture;
+ set
+ {
+ if (texture == value)
+ return;
+
+ texture = value;
+ Invalidate(Invalidation.DrawNode);
+ }
+ }
+
+ private readonly Cached partSizeCache = new Cached();
+
+ private Vector2 partSize => partSizeCache.IsValid
+ ? partSizeCache.Value
+ : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy);
+
+ public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
+ {
+ if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0)
+ partSizeCache.Invalidate();
+
+ return base.Invalidate(invalidation, source, shallPropagate);
+ }
+
+ ///
+ /// The amount of time to fade the cursor trail pieces.
+ ///
+ protected virtual double FadeDuration => 300;
+
+ public override bool IsPresent => true;
+
protected override void Update()
{
base.Update();
@@ -84,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
const int fade_clock_reset_threshold = 1000000;
- time = (float)(Time.Current - timeOffset) / 300f;
+ time = (float)((Time.Current - timeOffset) / FadeDuration);
if (time > fade_clock_reset_threshold)
resetTime();
}
@@ -101,6 +119,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
timeOffset = Time.Current;
}
+ ///
+ /// Whether to interpolate mouse movements and add trail pieces at intermediate points.
+ ///
+ protected virtual bool InterpolateMovements => true;
+
+ private Vector2? lastPosition;
+ private readonly InputResampler resampler = new InputResampler();
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
Vector2 pos = e.ScreenSpaceMousePosition;
@@ -116,33 +144,43 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Trace.Assert(lastPosition.HasValue);
- // ReSharper disable once PossibleInvalidOperationException
- Vector2 pos1 = lastPosition.Value;
- Vector2 diff = pos2 - pos1;
- float distance = diff.Length;
- Vector2 direction = diff / distance;
-
- float interval = size.X / 2 * 0.9f;
-
- for (float d = interval; d < distance; d += interval)
+ if (InterpolateMovements)
{
- lastPosition = pos1 + direction * d;
- addPosition(lastPosition.Value);
+ // ReSharper disable once PossibleInvalidOperationException
+ Vector2 pos1 = lastPosition.Value;
+ Vector2 diff = pos2 - pos1;
+ float distance = diff.Length;
+ Vector2 direction = diff / distance;
+
+ float interval = partSize.X / 2.5f;
+
+ for (float d = interval; d < distance; d += interval)
+ {
+ lastPosition = pos1 + direction * d;
+ addPart(lastPosition.Value);
+ }
+ }
+ else
+ {
+ lastPosition = pos2;
+ addPart(lastPosition.Value);
}
}
return base.OnMouseMove(e);
}
- private void addPosition(Vector2 pos)
+ private void addPart(Vector2 screenSpacePosition)
{
- parts[currentIndex].Position = pos;
+ parts[currentIndex].Position = screenSpacePosition;
parts[currentIndex].Time = time;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % max_sprites;
}
+ protected override DrawNode CreateDrawNode() => new TrailDrawNode(this);
+
private struct TrailPart
{
public Vector2 Position;
@@ -177,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader = Source.shader;
texture = Source.texture;
- size = Source.size;
+ size = Source.partSize;
time = Source.time;
for (int i = 0; i < Source.parts.Length; ++i)
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index eb1977a13d..41a02deaca 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private bool cursorExpand;
- private Bindable cursorScale;
+ private Bindable cursorScale;
private Bindable autoCursorScale;
private readonly IBindable beatmap = new Bindable();
@@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
- cursorExpand = skin.GetValue(s => s.CursorExpand ?? true);
+ cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true;
}
[BackgroundDependencyLoader]
@@ -49,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Child = scaleTarget = new SkinnableDrawable("Play/osu/cursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
+ Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
this.beatmap.BindTo(beatmap);
this.beatmap.ValueChanged += _ => calculateScale();
- cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
+ cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
cursorScale.ValueChanged += _ => calculateScale();
autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
@@ -70,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void calculateScale()
{
- float scale = (float)cursorScale.Value;
+ float scale = cursorScale.Value;
if (autoCursorScale.Value && beatmap.Value != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
+ scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
}
scaleTarget.Scale = new Vector2(scale);
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 893c7875fa..a944ff88c6 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -6,9 +6,12 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
@@ -22,17 +25,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Bindable showTrail = new Bindable(true);
- private readonly CursorTrail cursorTrail;
+ private readonly Drawable cursorTrail;
public OsuCursorContainer()
{
InternalChild = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- cursorTrail = new CursorTrail { Depth = 1 }
- }
+ Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling)
};
}
@@ -98,5 +98,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint);
}
+
+ private class DefaultCursorTrail : CursorTrail
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get(@"Cursor/cursortrail");
+ Scale = new Vector2(1 / Texture.ScaleAdjust);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index ea7eee8bb8..df12ebc514 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -70,13 +70,7 @@ namespace osu.Game.Rulesets.Osu.UI
base.Add(h);
}
- private void addApproachCircleProxy(Drawable d)
- {
- var proxy = d.CreateProxy();
- proxy.LifetimeStart = d.LifetimeStart;
- proxy.LifetimeEnd = d.LifetimeEnd;
- approachCircles.Add(proxy);
- }
+ private void addApproachCircleProxy(Drawable d) => approachCircles.Add(d.CreateProxy());
public override void PostProcess()
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
new file mode 100644
index 0000000000..f27e329e8e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
@@ -0,0 +1,74 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneSwellJudgements : PlayerTestScene
+ {
+ protected new TestPlayer Player => (TestPlayer)base.Player;
+
+ public TestSceneSwellJudgements()
+ : base(new TaikoRuleset())
+ {
+ }
+
+ [Test]
+ public void TestZeroTickTimeOffsets()
+ {
+ AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
+ AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0));
+ }
+
+ protected override bool Autoplay => true;
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo },
+ HitObjects =
+ {
+ new Swell
+ {
+ StartTime = 1000,
+ Duration = 1000,
+ }
+ }
+ };
+
+ return beatmap;
+ }
+
+ protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer();
+
+ protected class TestPlayer : Player
+ {
+ public readonly List Results = new List();
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public TestPlayer()
+ : base(false, false)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScoreProcessor.NewJudgement += r => Results.Add(r);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index 3c84d900a6..3aa461e779 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -14,13 +14,13 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
using osuTK;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests
{
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
}
private void addStrongHitJudgement(bool kiai)
@@ -159,13 +159,13 @@ namespace osu.Game.Rulesets.Taiko.Tests
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
{
- ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss });
+ ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
}
private void addBarLine(bool major, double delay = scroll_time)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index c8f3e18911..32d49ea39c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -8,10 +8,12 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -29,12 +31,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (beatmap.HitObjects.Count == 0)
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
+ HitWindows hitWindows = new TaikoHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
return new TaikoDifficultyAttributes
{
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
Skills = skills
};
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index bf89f7e15b..1a5a797f28 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
using osuTK;
using osu.Game.Rulesets.Objects.Drawables;
@@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
///
/// A line that scrolls alongside hit objects in the playfield and visualises control points.
///
- public class DrawableBarLine : DrawableHitObject
+ public class DrawableBarLine : DrawableHitObject
{
///
/// The width of the line tracker.
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
index 4d3a1a3f8a..f5b75a781b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index f4407a7b54..8e16a21199 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
case ArmedState.Hit:
case ArmedState.Miss:
- this.Delay(HitObject.Duration).FadeOut(100).Expire();
+ this.Delay(HitObject.Duration).FadeOut(100);
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index cef9a53deb..25b6141a0e 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (state)
{
case ArmedState.Hit:
- this.ScaleTo(0, 100, Easing.OutQuint).Expire();
+ this.ScaleTo(0, 100, Easing.OutQuint);
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index fa45067210..4b25ff0ecc 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
@@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -94,18 +97,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void UpdateStateTransforms(ArmedState state)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
switch (state)
{
case ArmedState.Idle:
validActionPressed = false;
UnproxyContent();
- this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
break;
case ArmedState.Miss:
- this.FadeOut(100)
- .Expire();
+ this.FadeOut(100);
break;
case ArmedState.Hit:
@@ -124,9 +127,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
.Then()
.MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
- this.FadeOut(800)
- .Expire();
-
+ this.FadeOut(800);
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 094ad1230f..07af7fe7e0 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -208,8 +208,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
this.FadeOut(transition_duration, Easing.Out);
bodyContainer.ScaleTo(1.4f, transition_duration);
-
- Expire();
}
break;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
index 8b27d78101..ce875ebba8 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@@ -14,7 +15,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
- public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type);
+ protected override void UpdateInitialTransforms() => this.FadeOut();
+
+ public void TriggerResult(HitResult type)
+ {
+ HitObject.StartTime = Time.Current;
+ ApplyResult(r => r.Type = type);
+ }
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 5424ccb4de..423f65b2d3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected override string SampleNamespace => "Taiko";
+ protected override string SampleNamespace => "taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 1d25735fe3..4e02c76a8b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -6,6 +6,7 @@ using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -86,5 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
index 8448036f76..c466ca7c8a 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -25,5 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public double HitWindow => TickSpacing / 2;
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
index 2a03c23934..d660149528 100644
--- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class StrongHitObject : TaikoHitObject
{
public override Judgement CreateJudgement() => new TaikoStrongJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index befa728570..f96c033dce 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -4,6 +4,7 @@
using System;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -33,5 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
index c2ae784b2a..68212e8f12 100644
--- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -9,5 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class SwellTick : TaikoHitObject
{
public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => null;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 3592d73004..6f4fbd0651 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -4,7 +4,9 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
deleted file mode 100644
index f232919cbf..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class TaikoHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Great, (100, 70, 40) },
- { HitResult.Good, (240, 160, 100) },
- { HitResult.Miss, (270, 190, 140) },
- };
-
- public override bool IsHitResultAllowed(HitResult result)
- {
- switch (result)
- {
- case HitResult.Great:
- case HitResult.Good:
- case HitResult.Miss:
- return true;
-
- default:
- return false;
- }
- }
-
- public override void SetDifficulty(double difficulty)
- {
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs
new file mode 100644
index 0000000000..9d273392ff
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Scoring
+{
+ public class TaikoHitWindows : HitWindows
+ {
+ private static readonly DifficultyRange[] taiko_ranges =
+ {
+ new DifficultyRange(HitResult.Great, 50, 35, 20),
+ new DifficultyRange(HitResult.Good, 120, 80, 50),
+ new DifficultyRange(HitResult.Miss, 135, 95, 70),
+ };
+
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override DifficultyRange[] GetRanges() => taiko_ranges;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index 68ddf2db19..75a27ff639 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -3,7 +3,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 6d0a5eb1e1..7fdb823388 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko
public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
+ public const string SHORT_NAME = "taiko";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
@@ -116,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko
public override string Description => "osu!taiko";
- public override string ShortName => "taiko";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
new file mode 100644
index 0000000000..e6e4bc0dd7
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko
+{
+ public class TaikoSkinComponent : GameplaySkinComponent
+ {
+ public TaikoSkinComponent(TaikoSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
new file mode 100644
index 0000000000..04aca534c6
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Taiko
+{
+ public enum TaikoSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index b03bea578e..5caa9e4626 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -5,19 +5,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
-using System.Linq;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
@@ -38,49 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- loadBarLines();
- }
-
- private void loadBarLines()
- {
- TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
- double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
-
- if (timingPoints.Count == 0)
- return;
-
- int currentIndex = 0;
- int currentBeat = 0;
- double time = timingPoints[currentIndex].Time;
-
- while (time <= lastHitTime)
- {
- int nextIndex = currentIndex + 1;
-
- if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time)
- {
- currentIndex = nextIndex;
- time = timingPoints[currentIndex].Time;
- currentBeat = 0;
- }
-
- var currentPoint = timingPoints[currentIndex];
-
- var barLine = new BarLine
- {
- StartTime = time,
- };
-
- barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
-
- bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
- Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
-
- time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
- currentBeat++;
- }
+ new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index d6866c7d25..5234ae1f69 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -132,10 +132,10 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
- rim.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer");
- rimHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-outer-hit");
- centre.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner");
- centreHit.Texture = textures.Get(@"Play/Taiko/taiko-drum-inner-hit");
+ rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer");
+ rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit");
+ centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner");
+ centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit");
rimHit.Colour = colours.Blue;
centreHit.Colour = colours.Pink;
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index a725c58462..4859abbb8e 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -9,8 +9,8 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Resources;
using osuTK;
diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index 0d6ed67767..9b4a90e9a9 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -119,6 +119,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestOldFormatLinkWithBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" });
+
+ Assert.AreEqual("This is a tricky (one)!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(12, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." });
+
+ Assert.AreEqual("This is another loose bracket ).", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(8, result.Links[0].Index);
+ Assert.AreEqual(23, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatWithBackslashes()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." });
+ Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(29, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestOldFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" });
+
+ Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(21, result.Links[0].Length);
+ }
+
[Test]
public void TestNewFormatLink()
{
@@ -131,6 +178,42 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestNewFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" });
+
+ Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(41, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestNewFormatLinkWithBackslashesInside()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" });
+
+ Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(27, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestNewFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" });
+
+ Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(36, result.Links[0].Length);
+ }
+
[Test]
public void TestMarkdownFormatLink()
{
@@ -143,6 +226,53 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(11, result.Links[0].Length);
}
+ [Test]
+ public void TestMarkdownFormatLinkWithBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" });
+
+ Assert.AreEqual("This is a tricky [one]!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(12, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatLinkWithEscapedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." });
+
+ Assert.AreEqual("This is another loose bracket ].", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(8, result.Links[0].Index);
+ Assert.AreEqual(23, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatWithBackslashes()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." });
+ Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(29, result.Links[0].Length);
+ }
+
+ [Test]
+ public void TestMarkdownFormatLinkWithEscapedAndBalancedBrackets()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" });
+
+ Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
+ Assert.AreEqual(10, result.Links[0].Index);
+ Assert.AreEqual(21, result.Links[0].Length);
+ }
+
[Test]
public void TestChannelLink()
{
diff --git a/osu.Game.Tests/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini
index 0e5737b4ea..7f7f0b32a6 100644
--- a/osu.Game.Tests/Resources/skin.ini
+++ b/osu.Game.Tests/Resources/skin.ini
@@ -1,5 +1,6 @@
[General]
Name: test skin
+TestLookup: TestValue
[Colours]
Combo1 : 142,199,255
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index 24ef9e4535..8bd846518b 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -41,5 +41,20 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
+
+ [Test]
+ public void TestDecodeGeneral()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin.ini"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var config = decoder.Decode(stream);
+
+ Assert.AreEqual("test skin", config.SkinInfo.Name);
+ Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
new file mode 100644
index 0000000000..bbcc4140a9
--- /dev/null
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -0,0 +1,156 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ public class TestSceneSkinConfigurationLookup : OsuTestScene
+ {
+ private LegacySkin source1;
+ private LegacySkin source2;
+ private SkinRequester requester;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ Add(new SkinProvidingContainer(source1 = new SkinSource())
+ .WithChild(new SkinProvidingContainer(source2 = new SkinSource())
+ .WithChild(requester = new SkinRequester())));
+ });
+
+ [Test]
+ public void TestBasicLookup()
+ {
+ AddStep("Add config values", () =>
+ {
+ source1.Configuration.ConfigDictionary["Lookup"] = "source1";
+ source2.Configuration.ConfigDictionary["Lookup"] = "source2";
+ });
+
+ AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2");
+ }
+
+ [Test]
+ public void TestFloatLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1");
+ AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f);
+ }
+
+ [Test]
+ public void TestBoolLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1");
+ AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true);
+ }
+
+ [Test]
+ public void TestEnumLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2");
+ AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2);
+ }
+
+ [Test]
+ public void TestLookupFailure()
+ {
+ AddAssert("Check lookup failure", () => requester.GetConfig("Lookup") == null);
+ }
+
+ [Test]
+ public void TestLookupNull()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
+
+ AddAssert("Check lookup null", () =>
+ {
+ var bindable = requester.GetConfig("Lookup");
+ return bindable != null && bindable.Value == null;
+ });
+ }
+
+ [Test]
+ public void TestColourLookup()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+ AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
+ }
+
+ [Test]
+ public void TestGlobalLookup()
+ {
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ }
+
+ [Test]
+ public void TestWrongColourType()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+
+ AddAssert("perform incorrect lookup", () =>
+ {
+ try
+ {
+ requester.GetConfig(new SkinCustomColourLookup("Lookup"));
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ });
+ }
+
+ public enum LookupType
+ {
+ Test
+ }
+
+ public enum ValueType
+ {
+ Test1,
+ Test2,
+ Test3
+ }
+
+ public class SkinSource : LegacySkin
+ {
+ public SkinSource()
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ }
+ }
+
+ public class SkinRequester : Drawable, ISkin
+ {
+ private ISkinSource skin;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ this.skin = skin;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
index f20440249b..a934d22b5d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
@@ -3,18 +3,18 @@
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Catch.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Scoring;
using osu.Framework.MathUtils;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
namespace osu.Game.Tests.Visual.Gameplay
@@ -36,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("New random judgement", () => newJudgement(), 40);
- AddRepeatStep("New max negative", () => newJudgement(-hitWindows.HalfWindowFor(HitResult.Meh)), 20);
- AddRepeatStep("New max positive", () => newJudgement(hitWindows.HalfWindowFor(HitResult.Meh)), 20);
+ AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
+ AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
}
@@ -85,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both,
Children = new[]
{
- new SpriteText { Text = $@"Great: {hitWindows?.Great}" },
- new SpriteText { Text = $@"Good: {hitWindows?.Good}" },
- new SpriteText { Text = $@"Meh: {hitWindows?.Meh}" },
+ new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
+ new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
+ new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
}
});
@@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void newJudgement(double offset = 0)
{
- var judgement = new JudgementResult(new Judgement())
+ var judgement = new JudgementResult(new HitObject(), new Judgement())
{
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
Type = HitResult.Perfect,
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
new file mode 100644
index 0000000000..d57ec44f39
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneFailJudgement : AllPlayersTestScene
+ {
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Array.Empty();
+
+ var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty());
+ return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
+ }
+
+ protected override void AddCheckSteps()
+ {
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
+ AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
+ }
+
+ private class FailPlayer : ReplayPlayer
+ {
+ public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public FailPlayer(Score score)
+ : base(score, false, false)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ ScoreProcessor.FailConditions += (_, __) => true;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index 4727140d99..c1635ffc83 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -3,12 +3,13 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play;
using osuTK;
@@ -29,57 +30,118 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
- Child = globalActionContainer = new GlobalActionContainer(game)
- {
- Children = new Drawable[]
- {
- pauseOverlay = new PauseOverlay
- {
- OnResume = () => Logger.Log(@"Resume"),
- OnRetry = () => Logger.Log(@"Retry"),
- OnQuit = () => Logger.Log(@"Quit"),
- },
- failOverlay = new FailOverlay
+ Child = globalActionContainer = new GlobalActionContainer(game);
+ }
- {
- OnRetry = () => Logger.Log(@"Retry"),
- OnQuit = () => Logger.Log(@"Quit"),
- }
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ globalActionContainer.Children = new Drawable[]
+ {
+ pauseOverlay = new PauseOverlay
+ {
+ OnResume = () => Logger.Log(@"Resume"),
+ OnRetry = () => Logger.Log(@"Retry"),
+ OnQuit = () => Logger.Log(@"Quit"),
+ },
+ failOverlay = new FailOverlay
+
+ {
+ OnRetry = () => Logger.Log(@"Retry"),
+ OnQuit = () => Logger.Log(@"Quit"),
}
};
+ InputManager.MoveMouseTo(Vector2.Zero);
+ });
+
+ [Test]
+ public void TestAdjustRetryCount()
+ {
+ showOverlay();
+
var retryCount = 0;
- AddStep("Add retry", () =>
+ AddRepeatStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
- });
+ }, 10);
+ }
- AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
- AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
+ ///
+ /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
+ ///
+ [Test]
+ public void TestEnterWithoutSelection()
+ {
+ showOverlay();
- testHideResets();
+ AddStep("Press select", () => press(GlobalAction.Select));
+ AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
+ }
- testEnterWithoutSelection();
- testKeyUpFromInitial();
- testKeyDownFromInitial();
- testKeyUpWrapping();
- testKeyDownWrapping();
+ ///
+ /// Tests that pressing the up arrow from the initial state selects the last button.
+ ///
+ [Test]
+ public void TestKeyUpFromInitial()
+ {
+ showOverlay();
- testMouseSelectionAfterKeySelection();
- testKeySelectionAfterMouseSelection();
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
+ }
- testMouseDeselectionResets();
+ ///
+ /// Tests that pressing the down arrow from the initial state selects the first button.
+ ///
+ [Test]
+ public void TestKeyDownFromInitial()
+ {
+ showOverlay();
- testClickSelection();
- testEnterKeySelection();
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
+ }
+
+ ///
+ /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
+ ///
+ [Test]
+ public void TestKeyUpWrapping()
+ {
+ AddStep("Show overlay", () => failOverlay.Show());
+
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
+ AddStep("Up arrow", () => press(Key.Up));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ }
+
+ ///
+ /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
+ ///
+ [Test]
+ public void TestKeyDownWrapping()
+ {
+ AddStep("Show overlay", () => failOverlay.Show());
+
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
+ AddStep("Down arrow", () => press(Key.Down));
+ AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
}
///
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
///
- private void testHideResets()
+ [Test]
+ public void TestHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
@@ -90,141 +152,78 @@ namespace osu.Game.Tests.Visual.Gameplay
}
///
- /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
+ /// Tests that entering menu with cursor initially on button doesn't selects it immediately.
+ /// This is to allow for stable keyboard navigation.
///
- private void testEnterWithoutSelection()
+ [Test]
+ public void TestInitialButtonHover()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
- AddStep("Press select", () => press(GlobalAction.Select));
- AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
+ AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0)));
AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
+ showOverlay();
- ///
- /// Tests that pressing the up arrow from the initial state selects the last button.
- ///
- private void testKeyUpFromInitial()
- {
- AddStep("Show overlay", () => pauseOverlay.Show());
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
+ AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
- AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the down arrow from the initial state selects the first button.
- ///
- private void testKeyDownFromInitial()
- {
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
- ///
- private void testKeyUpWrapping()
- {
- AddStep("Show overlay", () => failOverlay.Show());
-
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
- AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
-
- AddStep("Hide overlay", () => failOverlay.Hide());
- }
-
- ///
- /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
- ///
- private void testKeyDownWrapping()
- {
- AddStep("Show overlay", () => failOverlay.Show());
-
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
- AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => failOverlay.Hide());
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
}
///
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
///
- private void testMouseSelectionAfterKeySelection()
+ [Test]
+ public void TestMouseSelectionAfterKeySelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
+ showOverlay();
AddStep("Down arrow", () => press(Key.Down));
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
- AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
- AddAssert("Second button selected", () => secondButton.Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
+ AddAssert("Second button selected", () => getButton(1).Selected.Value);
}
///
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
///
- private void testKeySelectionAfterMouseSelection()
+ [Test]
+ public void TestKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () =>
{
pauseOverlay.Show();
- InputManager.MoveMouseTo(Vector2.Zero);
});
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
-
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up));
- AddAssert("Second button not selected", () => !secondButton.Selected.Value);
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
+ AddAssert("First button selected", () => getButton(0).Selected.Value);
}
///
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
///
- private void testMouseDeselectionResets()
+ [Test]
+ public void TestMouseDeselectionResets()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
- var secondButton = pauseOverlay.Buttons.Skip(1).First();
-
- AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
+ AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down));
- AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
-
- AddStep("Hide overlay", () => pauseOverlay.Hide());
+ AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
}
///
/// Tests that clicking on a button correctly causes a click event for that button.
///
- private void testClickSelection()
+ [Test]
+ public void TestClickSelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
-
- var retryButton = pauseOverlay.Buttons.Skip(1).First();
+ showOverlay();
bool triggered = false;
AddStep("Click retry button", () =>
@@ -232,7 +231,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
- retryButton.Click();
+ getButton(1).Click();
pauseOverlay.OnRetry = lastAction;
});
@@ -243,9 +242,10 @@ namespace osu.Game.Tests.Visual.Gameplay
///
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
///
- private void testEnterKeySelection()
+ [Test]
+ public void TestEnterKeySelection()
{
- AddStep("Show overlay", () => pauseOverlay.Show());
+ showOverlay();
AddStep("Select second button", () =>
{
@@ -275,6 +275,10 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
+ private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
+
+ private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
+
private void press(Key key)
{
InputManager.PressKey(key);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 5808a78056..50583e43c4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -160,6 +160,15 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
+ [Test]
+ public void TestRestartAfterResume()
+ {
+ pauseAndConfirm();
+ resumeAndConfirm();
+ restart();
+ confirmExited();
+ }
+
private void pauseAndConfirm()
{
pause();
@@ -198,6 +207,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
}
+ private void restart() => AddStep("restart", () => Player.Restart());
private void pause() => AddStep("pause", () => Player.Pause());
private void resume() => AddStep("resume", () => Player.Resume());
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 96dc864577..b3d4820737 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -5,7 +5,9 @@ using System;
using System.Globalization;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -133,12 +135,55 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
+ [Test]
+ public void TestSwitchOff()
+ {
+ SkinConsumer consumer = null;
+ SwitchableSkinProvidingContainer target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddStep("disable", () => target.Disable());
+ AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
+ }
+
+ private class SwitchableSkinProvidingContainer : SkinProvidingContainer
+ {
+ private bool allow = true;
+
+ protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
+
+ public void Disable()
+ {
+ allow = false;
+ TriggerSourceChanged();
+ }
+
+ public SwitchableSkinProvidingContainer(ISkin skin)
+ : base(skin)
+ {
+ }
+ }
+
private class ExposedSkinnableDrawable : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
- public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
- : base(name, defaultImplementation, allowFallback, confineMode)
+ public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null,
+ ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
}
@@ -206,8 +251,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Drawable Drawable => base.Drawable;
public int SkinChangedCount { get; private set; }
- public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null)
- : base(name, defaultImplementation, allowFallback)
+ public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null)
+ : base(new TestSkinComponent(name), defaultImplementation, allowFallback)
{
}
@@ -243,8 +288,8 @@ namespace osu.Game.Tests.Visual.Gameplay
this.size = size;
}
- public Drawable GetDrawableComponent(string componentName) =>
- componentName == "available"
+ public Drawable GetDrawableComponent(ISkinComponent componentName) =>
+ componentName.LookupName == "available"
? new DrawWidthBox
{
Colour = Color4.Yellow,
@@ -256,29 +301,46 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
{
- public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
+ public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
- private class SkinSourceContainer : Container, ISkin
+ [Cached(typeof(ISkinSource))]
+ private class SkinSourceContainer : Container, ISkinSource
{
- public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
+ public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
+ }
+
+ private class TestSkinComponent : ISkinComponent
+ {
+ private readonly string name;
+
+ public TestSkinComponent(string name)
+ {
+ this.name = name;
+ }
+
+ public string ComponentGroup => string.Empty;
+
+ public string LookupName => name;
}
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
index f2718b8e80..13116de320 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
@@ -10,14 +10,9 @@ namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneDisclaimer : ScreenTestScene
{
- [Cached(typeof(IAPIProvider))]
- private readonly DummyAPIAccess api = new DummyAPIAccess();
-
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api)
{
- Add(api);
-
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index fa3c392b2e..723e5fc03d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public TestSceneMatchLeaderboard()
{
Room.RoomID.Value = 3;
@@ -27,11 +29,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api)
{
var req = new GetRoomScoresRequest();
req.Success += v => { };
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
index 069e133c2b..b646433846 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestFixture]
public class TestSceneMultiScreen : ScreenTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(Screens.Multi.Multiplayer),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index 35449f5687..66ab1fe18a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
@@ -25,17 +25,14 @@ namespace osu.Game.Tests.Visual.Online
typeof(AccountCreationScreen),
};
- [Cached(typeof(IAPIProvider))]
- private DummyAPIAccess api = new DummyAPIAccess();
+ private readonly Container userPanelArea;
public TestSceneAccountCreationOverlay()
{
- Container userPanelArea;
AccountCreationOverlay accountCreation;
Children = new Drawable[]
{
- api,
accountCreation = new AccountCreationOverlay(),
userPanelArea = new Container
{
@@ -46,11 +43,16 @@ namespace osu.Game.Tests.Visual.Online
},
};
+ AddStep("show", () => accountCreation.Show());
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider api)
+ {
api.Logout();
api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
- AddStep("show", () => accountCreation.Show());
- AddStep("logout", () => api.Logout());
+ AddStep("logout", api.Logout);
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 8f19df65a9..5068064a1f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability),
};
+ protected override bool RequiresAPIAccess => true;
+
private RulesetInfo taikoRuleset;
private RulesetInfo maniaRuleset;
@@ -176,6 +178,8 @@ namespace osu.Game.Tests.Visual.Online
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
+ Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" },
+ Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" },
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index cf8bac7642..324291c9d7 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(Comments),
};
+ protected override bool RequiresAPIAccess => true;
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs
new file mode 100644
index 0000000000..4773e84a5e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLineTruncation.cs
@@ -0,0 +1,108 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online.Chat;
+using osu.Game.Overlays.Chat;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneChatLineTruncation : OsuTestScene
+ {
+ private readonly TestChatLineContainer textContainer;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ChatLine),
+ typeof(Message),
+ typeof(LinkFlowContainer),
+ typeof(MessageFormatter)
+ };
+
+ public TestSceneChatLineTruncation()
+ {
+ Add(textContainer = new TestChatLineContainer
+ {
+ Padding = new MarginPadding { Left = 20, Right = 20 },
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ testFormatting();
+ }
+
+ private void clear() => AddStep("clear messages", textContainer.Clear);
+
+ private void addMessageWithChecks(string text, bool isAction = false, bool isImportant = false, string username = null)
+ {
+ int index = textContainer.Count + 1;
+ var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index, username));
+ textContainer.Add(newLine);
+ }
+
+ private void testFormatting()
+ {
+ for (int a = 0; a < 25; a++)
+ addMessageWithChecks($"Wide {a} character username.", username: new string('w', a));
+ addMessageWithChecks("Short name with spaces.", username: "sho rt name");
+ addMessageWithChecks("Long name with spaces.", username: "long name with s p a c e s");
+ }
+
+ private class DummyMessage : Message
+ {
+ private static long messageCounter;
+
+ internal static readonly User TEST_SENDER_BACKGROUND = new User
+ {
+ Username = @"i-am-important",
+ Id = 42,
+ Colour = "#250cc9",
+ };
+
+ internal static readonly User TEST_SENDER = new User
+ {
+ Username = @"Somebody",
+ Id = 1,
+ };
+
+ public new DateTimeOffset Timestamp = DateTimeOffset.Now;
+
+ public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0, string username = null)
+ : base(messageCounter++)
+ {
+ Content = text;
+ IsAction = isAction;
+ Sender = new User
+ {
+ Username = username ?? $"user {number}",
+ Id = number,
+ Colour = isImportant ? "#250cc9" : null,
+ };
+ }
+ }
+
+ private class TestChatLineContainer : FillFlowContainer
+ {
+ protected override int Compare(Drawable x, Drawable y)
+ {
+ var xC = (ChatLine)x;
+ var yC = (ChatLine)y;
+
+ return xC.Message.CompareTo(yC.Message);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
index c18e0e3064..a1c77e2db0 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
@@ -127,6 +127,9 @@ namespace osu.Game.Tests.Visual.Online
addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap);
addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3,
expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External });
+ addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
+ addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", 1, expectedActions: LinkAction.External);
+ addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://osu.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External });
// note that there's 0 links here (they get removed if a channel is not found)
addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).");
addMessageWithChecks("I am important!", 0, false, true);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
index 75c2a2a6a1..14ae975806 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
{
private DirectOverlay direct;
+ protected override bool RequiresAPIAccess => true;
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
index 838347800f..c98f98c23d 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
@@ -17,14 +17,15 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneHistoricalSection : OsuTestScene
{
- public override IReadOnlyList RequiredTypes =>
- new[]
- {
- typeof(HistoricalSection),
- typeof(PaginatedMostPlayedBeatmapContainer),
- typeof(DrawableMostPlayedBeatmap),
- typeof(DrawableProfileRow)
- };
+ protected override bool RequiresAPIAccess => true;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(HistoricalSection),
+ typeof(PaginatedMostPlayedBeatmapContainer),
+ typeof(DrawableMostPlayedBeatmap),
+ typeof(DrawableProfileRow)
+ };
public TestSceneHistoricalSection()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs
new file mode 100644
index 0000000000..325d657f0e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs
@@ -0,0 +1,246 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Overlays.Profile.Sections.Kudosu;
+using System.Collections.Generic;
+using System;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Framework.Extensions.IEnumerableExtensions;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneKudosuHistory : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableKudosuHistoryItem),
+ };
+
+ private readonly Box background;
+
+ public TestSceneKudosuHistory()
+ {
+ FillFlowContainer content;
+
+ AddRange(new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ content = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Width = 0.7f,
+ AutoSizeAxes = Axes.Y,
+ }
+ });
+
+ items.ForEach(t => content.Add(new DrawableKudosuHistoryItem(t)));
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ background.Colour = colours.GreySeafoam;
+ }
+
+ private readonly IEnumerable items = new[]
+ {
+ new APIKudosuHistory
+ {
+ Amount = 10,
+ CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)),
+ Source = KudosuSource.DenyKudosu,
+ Action = KudosuAction.Reset,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 1",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username1",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 5,
+ CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)),
+ Source = KudosuSource.Forum,
+ Action = KudosuAction.Give,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 2",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username2",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 8,
+ CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)),
+ Source = KudosuSource.Forum,
+ Action = KudosuAction.Reset,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 3",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username3",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 7,
+ CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)),
+ Source = KudosuSource.Forum,
+ Action = KudosuAction.Revoke,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 4",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username4",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 100,
+ CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)),
+ Source = KudosuSource.Vote,
+ Action = KudosuAction.Give,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 5",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username5",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 20,
+ CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
+ Source = KudosuSource.Vote,
+ Action = KudosuAction.Reset,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 6",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username6",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 11,
+ CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
+ Source = KudosuSource.AllowKudosu,
+ Action = KudosuAction.Give,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 7",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username7",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 24,
+ CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)),
+ Source = KudosuSource.Delete,
+ Action = KudosuAction.Reset,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 8",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username8",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 12,
+ CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)),
+ Source = KudosuSource.Restore,
+ Action = KudosuAction.Give,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 9",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username9",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 2,
+ CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)),
+ Source = KudosuSource.Recalculate,
+ Action = KudosuAction.Give,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 10",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username10",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ },
+ new APIKudosuHistory
+ {
+ Amount = 32,
+ CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)),
+ Source = KudosuSource.Recalculate,
+ Action = KudosuAction.Reset,
+ Post = new APIKudosuHistory.ModdingPost
+ {
+ Title = @"Random post 11",
+ Url = @"https://osu.ppy.sh/b/1234",
+ },
+ Giver = new APIKudosuHistory.KudosuGiver
+ {
+ Username = @"Username11",
+ Url = @"https://osu.ppy.sh/u/1234"
+ }
+ }
+ };
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
new file mode 100644
index 0000000000..db6afa9bf3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsDismissableFlag.cs
@@ -0,0 +1,65 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsDismissableFlag : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DismissableFlag),
+ };
+
+ public TestSceneRankingsDismissableFlag()
+ {
+ DismissableFlag flag;
+ SpriteText text;
+
+ var countryA = new Country
+ {
+ FlagName = "BY",
+ FullName = "Belarus"
+ };
+
+ var countryB = new Country
+ {
+ FlagName = "US",
+ FullName = "United States"
+ };
+
+ AddRange(new Drawable[]
+ {
+ flag = new DismissableFlag
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(30, 20),
+ Country = countryA,
+ },
+ text = new SpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = "Invoked",
+ Font = OsuFont.GetFont(size: 30),
+ Alpha = 0,
+ }
+ });
+
+ flag.Action += () => text.FadeIn().Then().FadeOut(1000, Easing.OutQuint);
+
+ AddStep("Trigger click", () => flag.Click());
+ AddStep("Change to country 2", () => flag.Country = countryB);
+ AddStep("Change to country 1", () => flag.Country = countryA);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
new file mode 100644
index 0000000000..0ceb5f21d3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
@@ -0,0 +1,70 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsHeader : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DismissableFlag),
+ typeof(HeaderTitle),
+ typeof(RankingsRulesetSelector),
+ typeof(RankingsScopeSelector),
+ typeof(RankingsHeader),
+ };
+
+ public TestSceneRankingsHeader()
+ {
+ var countryBindable = new Bindable();
+ var ruleset = new Bindable();
+ var scope = new Bindable();
+
+ Add(new RankingsHeader
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scope = { BindTarget = scope },
+ Country = { BindTarget = countryBindable },
+ Ruleset = { BindTarget = ruleset },
+ Spotlights = new[]
+ {
+ new Spotlight
+ {
+ Id = 1,
+ Text = "Spotlight 1"
+ },
+ new Spotlight
+ {
+ Id = 2,
+ Text = "Spotlight 2"
+ },
+ new Spotlight
+ {
+ Id = 3,
+ Text = "Spotlight 3"
+ }
+ }
+ });
+
+ var country = new Country
+ {
+ FlagName = "BY",
+ FullName = "Belarus"
+ };
+
+ AddStep("Set country", () => countryBindable.Value = country);
+ AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddAssert("Check country is Null", () => countryBindable.Value == null);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
new file mode 100644
index 0000000000..849ca2defc
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
@@ -0,0 +1,60 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsHeaderTitle : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DismissableFlag),
+ typeof(HeaderTitle),
+ };
+
+ public TestSceneRankingsHeaderTitle()
+ {
+ var countryBindable = new Bindable();
+ var scope = new Bindable();
+
+ Add(new HeaderTitle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Country = { BindTarget = countryBindable },
+ Scope = { BindTarget = scope },
+ });
+
+ var countryA = new Country
+ {
+ FlagName = "BY",
+ FullName = "Belarus"
+ };
+
+ var countryB = new Country
+ {
+ FlagName = "US",
+ FullName = "United States"
+ };
+
+ AddStep("Set country", () => countryBindable.Value = countryA);
+ AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddAssert("Check country is Null", () => countryBindable.Value == null);
+
+ AddStep("Set country 1", () => countryBindable.Value = countryA);
+ AddStep("Set country 2", () => countryBindable.Value = countryB);
+ AddStep("Set null country", () => countryBindable.Value = null);
+ AddStep("Set scope to Performance", () => scope.Value = RankingsScope.Performance);
+ AddStep("Set scope to Spotlights", () => scope.Value = RankingsScope.Spotlights);
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddStep("Set scope to Country", () => scope.Value = RankingsScope.Country);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs
new file mode 100644
index 0000000000..84515bd3a4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsRulesetSelector.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Overlays.Rankings;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Rulesets.Catch;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsRulesetSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(RankingsRulesetSelector),
+ };
+
+ public TestSceneRankingsRulesetSelector()
+ {
+ var current = new Bindable();
+
+ Add(new RankingsRulesetSelector
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = current }
+ });
+
+ AddStep("Select osu!", () => current.Value = new OsuRuleset().RulesetInfo);
+ AddStep("Select mania", () => current.Value = new ManiaRuleset().RulesetInfo);
+ AddStep("Select taiko", () => current.Value = new TaikoRuleset().RulesetInfo);
+ AddStep("Select catch", () => current.Value = new CatchRuleset().RulesetInfo);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs
new file mode 100644
index 0000000000..3693d6b5b4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsScopeSelector.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Allocation;
+using osu.Game.Graphics;
+using osu.Game.Overlays.Rankings;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsScopeSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(RankingsScopeSelector),
+ };
+
+ private readonly Box background;
+
+ public TestSceneRankingsScopeSelector()
+ {
+ var scope = new Bindable();
+
+ AddRange(new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ new RankingsScopeSelector
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = scope,
+ }
+ });
+
+ AddStep(@"Select country", () => scope.Value = RankingsScope.Country);
+ AddStep(@"Select performance", () => scope.Value = RankingsScope.Performance);
+ AddStep(@"Select score", () => scope.Value = RankingsScope.Score);
+ AddStep(@"Select spotlights", () => scope.Value = RankingsScope.Spotlights);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ background.Colour = colours.GreySeafoam;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
index 5cb96c7ed2..806b36e855 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneSocialOverlay : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(UserPanel),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 2285c9b799..555d5334d8 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserProfileHeader : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(ProfileHeader),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 84c99d8c3a..42c8ffbf0a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserProfileOverlay : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
private readonly TestUserProfileOverlay profile;
[Resolved]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
index 9f0a8c769a..d777f9766a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserRanks : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
public TestSceneUserRanks()
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
index 7b97a27732..ed9e01a67e 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
+using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.SongSelect
@@ -30,45 +31,44 @@ namespace osu.Game.Tests.Visual.SongSelect
Size = new Vector2(550f, 450f),
});
- AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
+ {
+ BeatmapInfo =
{
- BeatmapSetInfo =
+ BeatmapSet = new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
},
- BeatmapInfo =
+ Version = "All Metrics",
+ Metadata = new BeatmapMetadata
{
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
+ Source = "osu!lazer",
+ Tags = "this beatmap has all the metrics",
+ },
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 7,
+ DrainRate = 1,
+ OverallDifficulty = 5.7f,
+ ApproachRate = 3.5f,
+ },
+ StarDifficulty = 5.3f,
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
+ },
}
- );
+ }));
- AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
- BeatmapSetInfo =
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
BeatmapInfo =
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
@@ -88,16 +88,16 @@ namespace osu.Game.Tests.Visual.SongSelect
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
- });
+ }));
- AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
- BeatmapSetInfo =
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
BeatmapInfo =
{
+ BeatmapSet = new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
+ },
Version = "Only Ratings",
Metadata = new BeatmapMetadata
{
@@ -113,9 +113,9 @@ namespace osu.Game.Tests.Visual.SongSelect
},
StarDifficulty = 4.8f
}
- });
+ }));
- AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
@@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.SongSelect
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
- });
+ }));
- AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
+ AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
@@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.SongSelect
},
StarDifficulty = 1.97f,
}
- });
+ }));
AddStep("null beatmap", () => detailsArea.Beatmap = null);
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
index 8e358a77db..186f27a8b2 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs
@@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
+ AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus)))
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index 94228e22f0..d84ffa0d93 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
@@ -25,6 +26,11 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private readonly NowPlayingOverlay np;
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(BeatSyncedContainer)
+ };
+
[Cached]
private MusicController musicController = new MusicController();
@@ -154,7 +160,9 @@ namespace osu.Game.Tests.Visual.UserInterface
if (timingPoints[timingPoints.Count - 1] == current)
return current;
- return timingPoints[timingPoints.IndexOf(current) + 1];
+ int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
+
+ return index == -1 ? current : timingPoints[index + 1];
}
private int calculateBeatCount(TimingControlPoint current)
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs
index a6ff3462d4..cc4a57fb83 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Add(overlay = new DialogOverlay());
- AddStep("dialog #1", () => overlay.Push(new PopupDialog
+ AddStep("dialog #1", () => overlay.Push(new TestPopupDialog
{
Icon = FontAwesome.Regular.TrashAlt,
HeaderText = @"Confirm deletion of",
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface
},
}));
- AddStep("dialog #2", () => overlay.Push(new PopupDialog
+ AddStep("dialog #2", () => overlay.Push(new TestPopupDialog
{
Icon = FontAwesome.Solid.Cog,
HeaderText = @"What do you want to do with",
@@ -71,5 +71,9 @@ namespace osu.Game.Tests.Visual.UserInterface
},
}));
}
+
+ private class TestPopupDialog : PopupDialog
+ {
+ }
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
index 9ddd8f4038..3d39bb7003 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
@@ -13,13 +13,22 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public TestScenePopupDialog()
{
- var popup = new PopupDialog
+ Add(new TestPopupDialog
{
RelativeSizeAxes = Axes.Both,
State = { Value = Framework.Graphics.Containers.Visibility.Visible },
- Icon = FontAwesome.Solid.AssistiveListeningSystems,
- HeaderText = @"This is a test popup",
- BodyText = "I can say lots of stuff and even wrap my words!",
+ });
+ }
+
+ private class TestPopupDialog : PopupDialog
+ {
+ public TestPopupDialog()
+ {
+ Icon = FontAwesome.Solid.AssistiveListeningSystems;
+
+ HeaderText = @"This is a test popup";
+ BodyText = "I can say lots of stuff and even wrap my words!";
+
Buttons = new PopupDialogButton[]
{
new PopupDialogCancelButton
@@ -30,10 +39,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
Text = @"You're a fake!",
},
- }
- };
-
- Add(popup);
+ };
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
index 9cdfcb6cc4..fdc50be3fa 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
private RulesetStore rulesets;
diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
index df3a45d1cc..06dee4d3f5 100644
--- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
@@ -75,6 +75,28 @@ namespace osu.Game.Beatmaps
/// The availability of this beatmap set.
///
public BeatmapSetOnlineAvailability Availability { get; set; }
+
+ ///
+ /// The song genre of this beatmap set.
+ ///
+ public BeatmapSetOnlineGenre Genre { get; set; }
+
+ ///
+ /// The song language of this beatmap set.
+ ///
+ public BeatmapSetOnlineLanguage Language { get; set; }
+ }
+
+ public class BeatmapSetOnlineGenre
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ }
+
+ public class BeatmapSetOnlineLanguage
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
}
public class BeatmapSetOnlineCovers
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 81f517dd86..8014631eca 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
- Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
+ Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
}
};
}
diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs
index d5cdd7e4bc..02382cfd2b 100644
--- a/osu.Game/Configuration/DatabasedConfigManager.cs
+++ b/osu.Game/Configuration/DatabasedConfigManager.cs
@@ -16,11 +16,11 @@ namespace osu.Game.Configuration
private readonly int? variant;
- private readonly List databasedSettings;
+ private List databasedSettings;
private readonly RulesetInfo ruleset;
- private readonly bool legacySettingsExist;
+ private bool legacySettingsExist;
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
{
@@ -28,21 +28,31 @@ namespace osu.Game.Configuration
this.ruleset = ruleset;
this.variant = variant;
- databasedSettings = settings.Query(ruleset?.ID, variant);
- legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
+ Load();
InitialiseDefaults();
}
protected override void PerformLoad()
{
+ databasedSettings = settings.Query(ruleset?.ID, variant);
+ legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
}
protected override bool PerformSave()
{
+ lock (dirtySettings)
+ {
+ foreach (var setting in dirtySettings)
+ settings.Update(setting);
+ dirtySettings.Clear();
+ }
+
return true;
}
+ private readonly List dirtySettings = new List();
+
protected override void AddBindable(T lookup, Bindable bindable)
{
base.AddBindable(lookup, bindable);
@@ -80,7 +90,12 @@ namespace osu.Game.Configuration
bindable.ValueChanged += b =>
{
setting.Value = b.NewValue;
- settings.Update(setting);
+
+ lock (dirtySettings)
+ {
+ if (!dirtySettings.Contains(setting))
+ dirtySettings.Add(setting);
+ }
};
}
}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 71a74a5558..e26021d930 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
- Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
+ Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
// Online settings
Set(OsuSetting.Username, string.Empty);
@@ -58,8 +58,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1);
// Input
- Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01);
- Set(OsuSetting.GameplayCursorSize, 1.0, 0.1f, 2, 0.01);
+ Set(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
+ Set(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
Set(OsuSetting.AutoCursorSize, false);
Set(OsuSetting.MouseDisableButtons, false);
diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs
index d13475189d..0f923c3a28 100644
--- a/osu.Game/Graphics/Backgrounds/Background.cs
+++ b/osu.Game/Graphics/Backgrounds/Background.cs
@@ -57,8 +57,9 @@ namespace osu.Game.Graphics.Backgrounds
AddInternal(bufferedContainer = new BufferedContainer
{
- CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both,
+ CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
Child = Sprite
});
}
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 621eeea2b7..370d044ba4 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -33,23 +33,46 @@ namespace osu.Game.Graphics.Containers
///
public double TimeSinceLastBeat { get; private set; }
+ ///
+ /// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
+ ///
+ private const double default_beat_length = 60000.0 / 60.0;
+
+ private TimingControlPoint defaultTiming;
+ private EffectControlPoint defaultEffect;
+ private TrackAmplitudes defaultAmplitudes;
+
protected override void Update()
{
- if (!Beatmap.Value.TrackLoaded || !Beatmap.Value.BeatmapLoaded) return;
+ Track track = null;
+ IBeatmap beatmap = null;
- var track = Beatmap.Value.Track;
- var beatmap = Beatmap.Value.Beatmap;
+ double currentTrackTime;
+ TimingControlPoint timingPoint;
+ EffectControlPoint effectPoint;
- if (track == null || beatmap == null)
- return;
+ if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded)
+ {
+ track = Beatmap.Value.Track;
+ beatmap = Beatmap.Value.Beatmap;
+ }
- double currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime;
+ if (track != null && beatmap != null && track.IsRunning)
+ {
+ currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime;
- TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
- EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
+ timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
+ effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
- if (timingPoint.BeatLength == 0)
- return;
+ if (timingPoint.BeatLength == 0)
+ return;
+ }
+ else
+ {
+ currentTrackTime = Clock.CurrentTime;
+ timingPoint = defaultTiming;
+ effectPoint = defaultEffect;
+ }
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
@@ -67,7 +90,7 @@ namespace osu.Game.Graphics.Containers
return;
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
- OnNewBeat(beatIndex, timingPoint, effectPoint, track.CurrentAmplitudes);
+ OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? defaultAmplitudes);
lastBeat = beatIndex;
lastTimingPoint = timingPoint;
@@ -77,6 +100,28 @@ namespace osu.Game.Graphics.Containers
private void load(IBindable beatmap)
{
Beatmap.BindTo(beatmap);
+
+ defaultTiming = new TimingControlPoint
+ {
+ BeatLength = default_beat_length,
+ AutoGenerated = true,
+ Time = 0
+ };
+
+ defaultEffect = new EffectControlPoint
+ {
+ Time = 0,
+ AutoGenerated = true,
+ KiaiMode = false,
+ OmitFirstBarLine = false
+ };
+
+ defaultAmplitudes = new TrackAmplitudes
+ {
+ FrequencyAmplitudes = new float[256],
+ LeftChannel = 0,
+ RightChannel = 0
+ };
}
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 9c948d6f90..b117d71006 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -21,8 +21,6 @@ namespace osu.Game.Graphics.Containers
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
- protected virtual bool PlaySamplesOnStateChange => true;
-
protected override bool BlockNonPositionalInput => true;
///
@@ -32,7 +30,7 @@ namespace osu.Game.Graphics.Containers
protected virtual bool DimMainContent => true;
[Resolved(CanBeNull = true)]
- private OsuGame osuGame { get; set; }
+ private OsuGame game { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
@@ -42,13 +40,22 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio)
{
- if (osuGame != null)
- OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
-
samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in");
samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out");
+ }
- State.ValueChanged += onStateChanged;
+ protected override void LoadComplete()
+ {
+ if (game != null)
+ OverlayActivationMode.BindTo(game.OverlayActivationMode);
+
+ OverlayActivationMode.BindValueChanged(mode =>
+ {
+ if (mode.NewValue == OverlayActivation.Disabled)
+ State.Value = Visibility.Hidden;
+ }, true);
+
+ base.LoadComplete();
}
///
@@ -62,21 +69,31 @@ namespace osu.Game.Graphics.Containers
protected override bool OnClick(ClickEvent e)
{
- closeIfOutside(e);
+ if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
+ Hide();
return base.OnClick(e);
}
- protected override bool OnDragEnd(DragEndEvent e)
- {
- closeIfOutside(e);
- return base.OnDragEnd(e);
- }
+ private bool closeOnDragEnd;
- private void closeIfOutside(MouseEvent e)
+ protected override bool OnDragStart(DragStartEvent e)
{
if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
+ closeOnDragEnd = true;
+
+ return base.OnDragStart(e);
+ }
+
+ protected override bool OnDragEnd(DragEndEvent e)
+ {
+ if (closeOnDragEnd)
+ {
Hide();
+ closeOnDragEnd = false;
+ }
+
+ return base.OnDragEnd(e);
}
public virtual bool OnPressed(GlobalAction action)
@@ -96,26 +113,28 @@ namespace osu.Game.Graphics.Containers
public bool OnReleased(GlobalAction action) => false;
- private void onStateChanged(ValueChangedEvent state)
+ protected override void UpdateState(ValueChangedEvent state)
{
switch (state.NewValue)
{
case Visibility.Visible:
- if (OverlayActivationMode.Value != OverlayActivation.Disabled)
+ if (OverlayActivationMode.Value == OverlayActivation.Disabled)
{
- if (PlaySamplesOnStateChange) samplePopIn?.Play();
- if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this);
+ State.Value = Visibility.Hidden;
+ return;
}
- else
- Hide();
+ samplePopIn?.Play();
+ if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
break;
case Visibility.Hidden:
- if (PlaySamplesOnStateChange) samplePopOut?.Play();
- if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this);
+ samplePopOut?.Play();
+ if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
break;
}
+
+ base.UpdateState(state);
}
protected override void PopOut()
@@ -127,7 +146,7 @@ namespace osu.Game.Graphics.Containers
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- osuGame?.RemoveBlockingOverlay(this);
+ game?.RemoveBlockingOverlay(this);
}
}
}
diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs
index e103798355..5a83d8e4ce 100644
--- a/osu.Game/Graphics/Cursor/MenuCursor.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursor.cs
@@ -124,7 +124,7 @@ namespace osu.Game.Graphics.Cursor
public class Cursor : Container
{
private Container cursorContainer;
- private Bindable cursorScale;
+ private Bindable cursorScale;
private const float base_scale = 0.15f;
public Sprite AdditiveLayer;
@@ -159,9 +159,8 @@ namespace osu.Game.Graphics.Cursor
}
};
- cursorScale = config.GetBindable(OsuSetting.MenuCursorSize);
- cursorScale.ValueChanged += scale => cursorContainer.Scale = new Vector2((float)scale.NewValue * base_scale);
- cursorScale.TriggerChange();
+ cursorScale = config.GetBindable(OsuSetting.MenuCursorSize);
+ cursorScale.BindValueChanged(scale => cursorContainer.Scale = new Vector2(scale.NewValue * base_scale), true);
}
}
diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
index 24816deeb5..12688da9df 100644
--- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
@@ -55,6 +55,7 @@ namespace osu.Game.Graphics.Sprites
Origin = Anchor.Centre,
BlurSigma = new Vector2(4),
CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Size = new Vector2(3f),
diff --git a/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs
new file mode 100644
index 0000000000..baca57ea89
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/GradientLineTabControl.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+using osu.Framework.Graphics.Colour;
+
+namespace osu.Game.Graphics.UserInterface
+{
+ public abstract class GradientLineTabControl : PageTabControl
+ {
+ protected Color4 LineColour
+ {
+ get => line.Colour;
+ set => line.Colour = value;
+ }
+
+ private readonly GradientLine line;
+
+ protected GradientLineTabControl()
+ {
+ RelativeSizeAxes = Axes.X;
+
+ AddInternal(line = new GradientLine
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ });
+ }
+
+ protected override Dropdown CreateDropdown() => null;
+
+ protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20, 0),
+ };
+
+ private class GradientLine : GridContainer
+ {
+ public GradientLine()
+ {
+ RelativeSizeAxes = Axes.X;
+ Size = new Vector2(0.8f, 1.5f);
+
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
+ new Dimension(),
+ };
+
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.White)
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Transparent)
+ },
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
index 70d988f60e..4f678b7218 100644
--- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
+++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
@@ -1,11 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Input.Events;
+using osuTK.Input;
namespace osu.Game.Graphics.UserInterface
{
@@ -16,15 +18,27 @@ namespace osu.Game.Graphics.UserInterface
public class HoverClickSounds : HoverSounds
{
private SampleChannel sampleClick;
+ private readonly MouseButton[] buttons;
- public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
+ ///
+ /// a container which plays sounds on hover and click for any specified s.
+ ///
+ /// Set of click samples to play.
+ ///
+ /// Array of button codes which should trigger the click sound.
+ /// If this optional parameter is omitted or set to null
, the click sound will only be played on left click.
+ ///
+ public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null)
: base(sampleSet)
{
+ this.buttons = buttons ?? new[] { MouseButton.Left };
}
protected override bool OnClick(ClickEvent e)
{
- sampleClick?.Play();
+ if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
+ sampleClick?.Play();
+
return base.OnClick(e);
}
diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs
index a0d3745180..ddcb626701 100644
--- a/osu.Game/Graphics/UserInterface/PageTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface
Margin = new MarginPadding { Top = 8, Bottom = 8 },
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
- Text = (value as Enum)?.GetDescription() ?? value.ToString(),
+ Text = CreateText(),
Font = OsuFont.GetFont(size: 14)
},
box = new Box
@@ -81,6 +81,8 @@ namespace osu.Game.Graphics.UserInterface
Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
}
+ protected virtual string CreateText() => (Value as Enum)?.GetDescription() ?? Value.ToString();
+
protected override bool OnHover(HoverEvent e)
{
if (!Active.Value)
diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs
new file mode 100644
index 0000000000..e90e297672
--- /dev/null
+++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Online.API.Requests
+{
+ public class GetUserKudosuHistoryRequest : PaginatedAPIRequest>
+ {
+ private readonly long userId;
+
+ public GetUserKudosuHistoryRequest(long userId, int page = 0, int itemsPerPage = 5)
+ : base(page, itemsPerPage)
+ {
+ this.userId = userId;
+ }
+
+ protected override string Target => $"users/{userId}/kudosu";
+ }
+}
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
index e5bfde8f8f..1ca14256e5 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
@@ -69,6 +69,12 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"availability")]
private BeatmapSetOnlineAvailability availability { get; set; }
+ [JsonProperty(@"genre")]
+ private BeatmapSetOnlineGenre genre { get; set; }
+
+ [JsonProperty(@"language")]
+ private BeatmapSetOnlineLanguage language { get; set; }
+
[JsonProperty(@"beatmaps")]
private IEnumerable beatmaps { get; set; }
@@ -95,6 +101,8 @@ namespace osu.Game.Online.API.Requests.Responses
LastUpdated = lastUpdated,
Availability = availability,
HasFavourited = hasFavourited,
+ Genre = genre,
+ Language = language
},
Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
};
diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs
new file mode 100644
index 0000000000..d596ddc560
--- /dev/null
+++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace osu.Game.Online.API.Requests.Responses
+{
+ public class APIKudosuHistory
+ {
+ [JsonProperty("created_at")]
+ public DateTimeOffset CreatedAt;
+
+ [JsonProperty("amount")]
+ public int Amount;
+
+ [JsonProperty("post")]
+ public ModdingPost Post;
+
+ public class ModdingPost
+ {
+ [JsonProperty("url")]
+ public string Url;
+
+ [JsonProperty("title")]
+ public string Title;
+ }
+
+ [JsonProperty("giver")]
+ public KudosuGiver Giver;
+
+ public class KudosuGiver
+ {
+ [JsonProperty("url")]
+ public string Url;
+
+ [JsonProperty("username")]
+ public string Username;
+ }
+
+ public KudosuSource Source;
+
+ public KudosuAction Action;
+
+ [JsonProperty("action")]
+ private string action
+ {
+ set
+ {
+ // incoming action may contain a prefix. if it doesn't, it's a legacy forum event.
+
+ string[] split = value.Split('.');
+
+ if (split.Length > 1)
+ Enum.TryParse(split.First().Replace("_", ""), true, out Source);
+ else
+ Source = KudosuSource.Forum;
+
+ Enum.TryParse(split.Last(), true, out Action);
+ }
+ }
+ }
+
+ public enum KudosuSource
+ {
+ Unknown,
+ AllowKudosu,
+ Delete,
+ DenyKudosu,
+ Forum,
+ Recalculate,
+ Restore,
+ Vote
+ }
+
+ public enum KudosuAction
+ {
+ Give,
+ Reset,
+ Revoke,
+ }
+}
diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs
index db26945ef3..24d17612ee 100644
--- a/osu.Game/Online/Chat/MessageFormatter.cs
+++ b/osu.Game/Online/Chat/MessageFormatter.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text.RegularExpressions;
namespace osu.Game.Online.Chat
@@ -10,16 +11,16 @@ namespace osu.Game.Online.Chat
public static class MessageFormatter
{
// [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points)
- private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]");
+ private static readonly Regex wiki_regex = new Regex(@"\[\[(?[^\]]+)\]\]");
// (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234)
- private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]");
+ private static readonly Regex old_link_regex = new Regex(@"\((?(((?<=\\)[\(\)])|[^\(\)])*(((?\()(((?<=\\)[\(\)])|[^\(\)])*)+((?\))(((?<=\\)[\(\)])|[^\(\)])*)+)*(?(open)(?!)))\)\[(?[a-z]+://[^ ]+)\]");
// [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234)
- private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]");
+ private static readonly Regex new_link_regex = new Regex(@"\[(?[a-z]+://[^ ]+) (?(((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]");
// [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format
- private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)");
+ private static readonly Regex markdown_link_regex = new Regex(@"\[(?(((?<=\\)[\[\]])|[^\[\]])*(((?\[)(((?<=\\)[\[\]])|[^\[\]])*)+((?\])(((?<=\\)[\[\]])|[^\[\]])*)+)*(?(open)(?!)))\]\((?[a-z]+://[^ ]+)\)");
// advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used
// This is in the format (, [optional]):
@@ -48,7 +49,7 @@ namespace osu.Game.Online.Chat
// Unicode emojis
private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])");
- private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null)
+ private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null)
{
int captureOffset = 0;
@@ -58,16 +59,20 @@ namespace osu.Game.Online.Chat
var displayText = string.Format(display,
m.Groups[0],
- m.Groups.Count > 1 ? m.Groups[1].Value : "",
- m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim();
+ m.Groups["text"].Value,
+ m.Groups["url"].Value).Trim();
var linkText = string.Format(link,
m.Groups[0],
- m.Groups.Count > 1 ? m.Groups[1].Value : "",
- m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim();
+ m.Groups["text"].Value,
+ m.Groups["url"].Value).Trim();
if (displayText.Length == 0 || linkText.Length == 0) continue;
+ // Remove backslash escapes in front of the characters provided in escapeChars
+ if (escapeChars != null)
+ displayText = escapeChars.Aggregate(displayText, (current, c) => current.Replace($"\\{c}", c.ToString()));
+
// Check for encapsulated links
if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || (index <= l.Index && index + m.Length >= l.Index + l.Length)) == null)
{
@@ -183,13 +188,13 @@ namespace osu.Game.Online.Chat
var result = new MessageFormatterResult(toFormat);
// handle the [link display] format
- handleMatches(new_link_regex, "{2}", "{1}", result, startIndex);
+ handleMatches(new_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '[', ']' });
// handle the standard markdown []() format
- handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex);
+ handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '[', ']' });
// handle the ()[] link format
- handleMatches(old_link_regex, "{1}", "{2}", result, startIndex);
+ handleMatches(old_link_regex, "{1}", "{2}", result, startIndex, escapeChars: new[] { '(', ')' });
// handle wiki links
handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex);
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index 98f15599fc..147556b78b 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -133,6 +133,10 @@ namespace osu.Game.Online.Leaderboards
});
break;
+ case PlaceholderState.NoneSelected:
+ replacePlaceholder(new MessagePlaceholder(@"Please select a beatmap!"));
+ break;
+
case PlaceholderState.Unavailable:
replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"));
break;
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index 008f8208eb..0b84cfc28a 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -215,6 +215,7 @@ namespace osu.Game.Online.Leaderboards
Origin = Anchor.BottomRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(1),
ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
},
},
diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/PlaceholderState.cs
index 930e1df484..297241fa73 100644
--- a/osu.Game/Online/Leaderboards/PlaceholderState.cs
+++ b/osu.Game/Online/Leaderboards/PlaceholderState.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Online.Leaderboards
Retrieving,
NetworkFailure,
Unavailable,
+ NoneSelected,
NoScores,
NotLoggedIn,
NotSupporter,
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index de8f316b06..d6b8ad3e67 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -207,6 +207,7 @@ namespace osu.Game
FileStore.Cleanup();
AddInternal(API);
+ AddInternal(RulesetConfigCache);
GlobalActionContainer globalBinding;
diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs
index 44827f0a0c..16d6236051 100644
--- a/osu.Game/Overlays/BeatmapSet/Info.cs
+++ b/osu.Game/Overlays/BeatmapSet/Info.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet
public Info()
{
- MetadataSection source, tags;
+ MetadataSection source, tags, genre, language;
RelativeSizeAxes = Axes.X;
Height = 220;
Masking = true;
@@ -83,11 +83,12 @@ namespace osu.Game.Overlays.BeatmapSet
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- LayoutDuration = transition_duration,
+ Direction = FillDirection.Full,
Children = new[]
{
source = new MetadataSection("Source"),
+ genre = new MetadataSection("Genre") { Width = 0.5f },
+ language = new MetadataSection("Language") { Width = 0.5f },
tags = new MetadataSection("Tags"),
},
},
@@ -119,6 +120,8 @@ namespace osu.Game.Overlays.BeatmapSet
{
source.Text = b.NewValue?.Metadata.Source ?? string.Empty;
tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty;
+ genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? string.Empty;
+ language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty;
};
}
@@ -139,7 +142,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
if (string.IsNullOrEmpty(value))
{
- this.FadeOut(transition_duration);
+ Hide();
return;
}
@@ -149,12 +152,6 @@ namespace osu.Game.Overlays.BeatmapSet
}
}
- public Color4 TextColour
- {
- get => textFlow.Colour;
- set => textFlow.Colour = value;
- }
-
public MetadataSection(string title)
{
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
index dcd58db427..e2a725ec46 100644
--- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
+++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
@@ -1,60 +1,37 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics.UserInterface;
using osu.Game.Screens.Select.Leaderboards;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osuTK;
using osu.Game.Graphics.UserInterface;
-using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using osuTK.Graphics;
-using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
- public class LeaderboardScopeSelector : PageTabControl
+ public class LeaderboardScopeSelector : GradientLineTabControl
{
protected override bool AddEnumEntriesAutomatically => false;
- protected override Dropdown CreateDropdown() => null;
-
protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value);
public LeaderboardScopeSelector()
{
- RelativeSizeAxes = Axes.X;
-
AddItem(BeatmapLeaderboardScope.Global);
AddItem(BeatmapLeaderboardScope.Country);
AddItem(BeatmapLeaderboardScope.Friend);
-
- AddInternal(new GradientLine
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- });
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Blue;
+ LineColour = Color4.Gray;
}
- protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- AutoSizeAxes = Axes.X,
- RelativeSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(20, 0),
- };
-
private class ScopeSelectorTabItem : PageTabItem
{
public ScopeSelectorTabItem(BeatmapLeaderboardScope value)
@@ -77,43 +54,5 @@ namespace osu.Game.Overlays.BeatmapSet
Text.FadeColour(Color4.White);
}
}
-
- private class GradientLine : GridContainer
- {
- public GradientLine()
- {
- RelativeSizeAxes = Axes.X;
- Size = new Vector2(0.8f, 1.5f);
-
- ColumnDimensions = new[]
- {
- new Dimension(),
- new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
- new Dimension(),
- };
-
- Content = new[]
- {
- new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray),
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Gray,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent),
- },
- }
- };
- }
- }
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 347522fb48..58f5f02956 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -171,6 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(1),
ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m)
{
AutoSizeAxes = Axes.Both,
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
index 6761d0f710..b9664d7c2f 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
@@ -172,7 +172,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
: this(new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(1),
})
{
}
diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs
index 2576b38ec8..7596231a3d 100644
--- a/osu.Game/Overlays/Chat/ChatLine.cs
+++ b/osu.Game/Overlays/Chat/ChatLine.cs
@@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Chat
protected virtual float MessagePadding => default_message_padding;
+ private const float timestamp_padding = 65;
+
private const float default_horizontal_padding = 15;
protected virtual float HorizontalPadding => default_horizontal_padding;
@@ -87,7 +89,12 @@ namespace osu.Game.Overlays.Chat
{
Shadow = false,
Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length],
- Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true)
+ Truncate = true,
+ EllipsisString = "… :",
+ Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true),
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ MaxWidth = default_message_padding - timestamp_padding
};
if (hasBackground)
@@ -142,6 +149,7 @@ namespace osu.Game.Overlays.Chat
new MessageSender(message.Sender)
{
AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = timestamp_padding },
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
Child = effectedUsername,
diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs
index 4d77e5f93d..31c48deee0 100644
--- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs
+++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat.Selection
private Color4 topicColour;
private Color4 hoverColour;
- public IEnumerable FilterTerms => new[] { channel.Name };
+ public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic };
public bool MatchingFilter
{
@@ -121,10 +121,11 @@ namespace osu.Game.Overlays.Chat.Selection
{
new SpriteIcon
{
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
Icon = FontAwesome.Solid.User,
Size = new Vector2(text_size - 2),
Shadow = false,
- Margin = new MarginPadding { Top = 1 },
},
new OsuSpriteText
{
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 53a05656b1..6f848c7627 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Overlays
private Box chatBackground;
private Box tabBackground;
- public Bindable ChatHeight { get; set; }
+ public Bindable ChatHeight { get; set; }
private Container channelSelectionContainer;
protected ChannelSelectionOverlay ChannelSelectionOverlay;
@@ -190,14 +190,13 @@ namespace osu.Game.Overlays
ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel);
ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel;
- ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight);
- ChatHeight.ValueChanged += height =>
+ ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight);
+ ChatHeight.BindValueChanged(height =>
{
- chatContainer.Height = (float)height.NewValue;
- channelSelectionContainer.Height = 1f - (float)height.NewValue;
- tabBackground.FadeTo(height.NewValue == 1 ? 1 : 0.8f, 200);
- };
- ChatHeight.TriggerChange();
+ chatContainer.Height = height.NewValue;
+ channelSelectionContainer.Height = 1f - height.NewValue;
+ tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200);
+ }, true);
chatBackground.Colour = colours.ChatBlue;
@@ -273,7 +272,7 @@ namespace osu.Game.Overlays
}
}
- private double startDragChatHeight;
+ private float startDragChatHeight;
private bool isDragging;
protected override bool OnDragStart(DragStartEvent e)
@@ -291,7 +290,7 @@ namespace osu.Game.Overlays
{
if (isDragging)
{
- double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
+ float targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
// If the channel selection screen is shown, mind its minimum height
if (ChannelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height)
diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs
index 1022edfe81..cff887865a 100644
--- a/osu.Game/Overlays/Dialog/PopupDialog.cs
+++ b/osu.Game/Overlays/Dialog/PopupDialog.cs
@@ -13,20 +13,17 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
-using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Overlays.Dialog
{
- public class PopupDialog : OsuFocusedOverlayContainer
+ public abstract class PopupDialog : VisibilityContainer
{
public static readonly float ENTER_DURATION = 500;
public static readonly float EXIT_DURATION = 200;
- protected override bool BlockPositionalInput => false;
-
private readonly Vector2 ringSize = new Vector2(100f);
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
@@ -90,7 +87,7 @@ namespace osu.Game.Overlays.Dialog
}
}
- public PopupDialog()
+ protected PopupDialog()
{
RelativeSizeAxes = Axes.Both;
@@ -202,18 +199,6 @@ namespace osu.Game.Overlays.Dialog
};
}
- public override bool OnPressed(GlobalAction action)
- {
- switch (action)
- {
- case GlobalAction.Select:
- Buttons.OfType().FirstOrDefault()?.Click();
- return true;
- }
-
- return base.OnPressed(action);
- }
-
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Repeat) return false;
@@ -238,8 +223,6 @@ namespace osu.Game.Overlays.Dialog
protected override void PopIn()
{
- base.PopIn();
-
actionInvoked = false;
// Reset various animations but only if the dialog animation fully completed
@@ -263,7 +246,6 @@ namespace osu.Game.Overlays.Dialog
// This is presumed to always be a sane default "cancel" action.
buttonsContainer.Last().Click();
- base.PopOut();
content.FadeOut(EXIT_DURATION, Easing.InSine);
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index aaae7bcf5c..6aaeff8554 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -5,6 +5,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Dialog;
using osu.Game.Graphics.Containers;
+using osu.Game.Input.Bindings;
+using System.Linq;
namespace osu.Game.Overlays
{
@@ -41,8 +43,6 @@ namespace osu.Game.Overlays
Show();
}
- protected override bool PlaySamplesOnStateChange => false;
-
protected override bool BlockNonPositionalInput => true;
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
@@ -74,5 +74,17 @@ namespace osu.Game.Overlays
this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
}
+
+ public override bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Select:
+ currentDialog?.Buttons.OfType().FirstOrDefault()?.Click();
+ return true;
+ }
+
+ return base.OnPressed(action);
+ }
}
}
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 7bf94c1483..2528ccec41 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -151,6 +151,7 @@ namespace osu.Game.Overlays.Direct
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
+ Spacing = new Vector2(3),
Children = GetDifficultyIcons(colours),
},
},
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 158ff648dd..b64142dfe7 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -129,6 +129,7 @@ namespace osu.Game.Overlays.Direct
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
+ Spacing = new Vector2(3),
Children = GetDifficultyIcons(colours),
},
},
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index 641423f21f..3ffc3f332b 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -28,14 +28,14 @@ namespace osu.Game.Overlays.Direct
public readonly BeatmapSetInfo SetInfo;
private const double hover_transition_time = 400;
- private const int maximum_difficulty_icons = 15;
+ private const int maximum_difficulty_icons = 10;
private Container content;
private BeatmapSetOverlay beatmapSetOverlay;
public PreviewTrack Preview => PlayButton.Preview;
- public Bindable PreviewPlaying => PlayButton.Playing;
+ public Bindable PreviewPlaying => PlayButton?.Playing;
protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; }
@@ -190,10 +190,11 @@ namespace osu.Game.Overlays.Direct
text = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, italics: true) },
new SpriteIcon
{
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
Icon = icon,
Shadow = true,
Size = new Vector2(14),
- Margin = new MarginPadding { Top = 1 },
},
};
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 7b8745cf42..58892cd0dd 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -283,7 +283,7 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.TopCentre,
Font = OsuFont.GetFont(size: 18)
},
- new HoverClickSounds()
+ new HoverClickSounds(buttons: new[] { MouseButton.Left, MouseButton.Right })
};
Mod = mod;
diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index df37a1b2c7..29b6ae00f3 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -161,12 +161,12 @@ namespace osu.Game.Overlays.Music
{
public PlaylistItemHandle()
{
- Anchor = Anchor.TopLeft;
- Origin = Anchor.TopLeft;
+ Anchor = Anchor.CentreLeft;
+ Origin = Anchor.CentreLeft;
Size = new Vector2(12);
Icon = FontAwesome.Solid.Bars;
Alpha = 0f;
- Margin = new MarginPadding { Left = 5, Top = 2 };
+ Margin = new MarginPadding { Left = 5 };
}
public override bool HandlePositionalInput => IsPresent;
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index a3243a655e..cf42c8005a 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -346,10 +346,12 @@ namespace osu.Game.Overlays
public Background(WorkingBeatmap beatmap = null)
{
this.beatmap = beatmap;
- CacheDrawnFrameBuffer = true;
+
Depth = float.MaxValue;
RelativeSizeAxes = Axes.Both;
+ CacheDrawnFrameBuffer = true;
+
Children = new Drawable[]
{
sprite = new Sprite
diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs
index 24ed0cc022..c6d96c5917 100644
--- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs
+++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs
@@ -296,7 +296,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
this.MoveTo(pos, 200, Easing.OutQuint);
}
- protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
+ protected override void PopIn()
+ {
+ instantMove |= !IsPresent;
+ this.FadeIn(200, Easing.OutQuint);
+ }
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
}
diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs
new file mode 100644
index 0000000000..d0cfe9fa54
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs
@@ -0,0 +1,147 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Chat;
+using System;
+using osuTK;
+
+namespace osu.Game.Overlays.Profile.Sections.Kudosu
+{
+ public class DrawableKudosuHistoryItem : CompositeDrawable
+ {
+ private const int height = 25;
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ private readonly APIKudosuHistory historyItem;
+ private readonly LinkFlowContainer linkFlowContainer;
+ private readonly DrawableDate date;
+
+ public DrawableKudosuHistoryItem(APIKudosuHistory historyItem)
+ {
+ this.historyItem = historyItem;
+
+ Height = height;
+ RelativeSizeAxes = Axes.X;
+ AddRangeInternal(new Drawable[]
+ {
+ linkFlowContainer = new LinkFlowContainer
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(0, 3),
+ },
+ date = new DrawableDate(historyItem.CreatedAt)
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ }
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ date.Colour = colours.GreySeafoamLighter;
+ var formattedSource = MessageFormatter.FormatText(getString(historyItem));
+ linkFlowContainer.AddLinks(formattedSource.Text, formattedSource.Links);
+ }
+
+ private string getString(APIKudosuHistory item)
+ {
+ string amount = $"{Math.Abs(item.Amount)} kudosu";
+ string post = $"[{item.Post.Title}]({item.Post.Url})";
+
+ switch (item.Source)
+ {
+ case KudosuSource.AllowKudosu:
+ switch (item.Action)
+ {
+ case KudosuAction.Give:
+ return $"Received {amount} from kudosu deny repeal of modding post {post}";
+ }
+
+ break;
+
+ case KudosuSource.DenyKudosu:
+ switch (item.Action)
+ {
+ case KudosuAction.Reset:
+ return $"Denied {amount} from modding post {post}";
+ }
+
+ break;
+
+ case KudosuSource.Delete:
+ switch (item.Action)
+ {
+ case KudosuAction.Reset:
+ return $"Lost {amount} from modding post deletion of {post}";
+ }
+
+ break;
+
+ case KudosuSource.Restore:
+ switch (item.Action)
+ {
+ case KudosuAction.Give:
+ return $"Received {amount} from modding post restoration of {post}";
+ }
+
+ break;
+
+ case KudosuSource.Vote:
+ switch (item.Action)
+ {
+ case KudosuAction.Give:
+ return $"Received {amount} from obtaining votes in modding post of {post}";
+
+ case KudosuAction.Reset:
+ return $"Lost {amount} from losing votes in modding post of {post}";
+ }
+
+ break;
+
+ case KudosuSource.Recalculate:
+ switch (item.Action)
+ {
+ case KudosuAction.Give:
+ return $"Received {amount} from votes recalculation in modding post of {post}";
+
+ case KudosuAction.Reset:
+ return $"Lost {amount} from votes recalculation in modding post of {post}";
+ }
+
+ break;
+
+ case KudosuSource.Forum:
+
+ string giver = $"[{item.Giver?.Username}]({item.Giver?.Url})";
+
+ switch (historyItem.Action)
+ {
+ case KudosuAction.Give:
+ return $"Received {amount} from {giver} for a post at {post}";
+
+ case KudosuAction.Reset:
+ return $"Kudosu reset by {giver} for the post {post}";
+
+ case KudosuAction.Revoke:
+ return $"Denied kudosu by {giver} for the post {post}";
+ }
+
+ break;
+ }
+
+ return $"Unknown event ({amount} change)";
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs
new file mode 100644
index 0000000000..0e7cfc37c0
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Online.API.Requests;
+using osu.Game.Users;
+using osu.Framework.Bindables;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.API;
+using System.Collections.Generic;
+
+namespace osu.Game.Overlays.Profile.Sections.Kudosu
+{
+ public class PaginatedKudosuHistoryContainer : PaginatedContainer
+ {
+ public PaginatedKudosuHistoryContainer(Bindable user, string header, string missing)
+ : base(user, header, missing)
+ {
+ ItemsPerPage = 5;
+ }
+
+ protected override APIRequest> CreateRequest()
+ => new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage);
+
+ protected override Drawable CreateDrawableItem(APIKudosuHistory item) => new DrawableKudosuHistoryItem(item);
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs
index a17b68933c..9ccce7d837 100644
--- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs
+++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
using osu.Game.Overlays.Profile.Sections.Kudosu;
namespace osu.Game.Overlays.Profile.Sections
@@ -13,9 +14,10 @@ namespace osu.Game.Overlays.Profile.Sections
public KudosuSection()
{
- Children = new[]
+ Children = new Drawable[]
{
new KudosuInfo(User),
+ new PaginatedKudosuHistoryContainer(User, null, @"This user hasn't received any kudosu!"),
};
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
index e54ce44ca2..6362d3dfb0 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
@@ -12,12 +12,13 @@ using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Beatmaps;
using osu.Framework.Localisation;
+using osu.Framework.Graphics.Containers;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public abstract class DrawableProfileScore : DrawableProfileRow
{
- private readonly ScoreModsContainer modsContainer;
+ private readonly FillFlowContainer modsContainer;
protected readonly ScoreInfo Score;
protected DrawableProfileScore(ScoreInfo score)
@@ -28,12 +29,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
Height = 60;
Children = new Drawable[]
{
- modsContainer = new ScoreModsContainer
+ modsContainer = new FillFlowContainer
{
- AutoSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
- Width = 60,
+ Spacing = new Vector2(1),
Margin = new MarginPadding { Right = 160 }
}
};
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs
deleted file mode 100644
index 1ce04effa8..0000000000
--- a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osuTK;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.UI;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace osu.Game.Overlays.Profile.Sections.Ranks
-{
- public class ScoreModsContainer : FlowContainer
- {
- protected override IEnumerable ComputeLayoutPositions()
- {
- int count = FlowingChildren.Count();
- for (int i = 0; i < count; i++)
- yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0);
- }
- }
-}
diff --git a/osu.Game/Overlays/Rankings/DismissableFlag.cs b/osu.Game/Overlays/Rankings/DismissableFlag.cs
new file mode 100644
index 0000000000..7a55b0bba6
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/DismissableFlag.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Users.Drawables;
+using osuTK.Graphics;
+using osuTK;
+using osu.Framework.Input.Events;
+using System;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class DismissableFlag : UpdateableFlag
+ {
+ private const int duration = 200;
+
+ public Action Action;
+
+ private readonly SpriteIcon hoverIcon;
+
+ public DismissableFlag()
+ {
+ AddInternal(hoverIcon = new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Depth = -1,
+ Alpha = 0,
+ Size = new Vector2(10),
+ Icon = FontAwesome.Solid.Times,
+ });
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ hoverIcon.FadeIn(duration, Easing.OutQuint);
+ this.FadeColour(Color4.Gray, duration, Easing.OutQuint);
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ base.OnHoverLost(e);
+ hoverIcon.FadeOut(duration, Easing.OutQuint);
+ this.FadeColour(Color4.White, duration, Easing.OutQuint);
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ Action?.Invoke();
+ return true;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/HeaderTitle.cs b/osu.Game/Overlays/Rankings/HeaderTitle.cs
new file mode 100644
index 0000000000..cba407ecf7
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/HeaderTitle.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Users;
+using osu.Framework.Graphics;
+using osuTK;
+using osu.Game.Graphics;
+using osu.Framework.Allocation;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class HeaderTitle : CompositeDrawable
+ {
+ private const int spacing = 10;
+ private const int flag_margin = 5;
+ private const int text_size = 40;
+
+ public readonly Bindable Scope = new Bindable();
+ public readonly Bindable Country = new Bindable();
+
+ private readonly SpriteText scopeText;
+ private readonly DismissableFlag flag;
+
+ public HeaderTitle()
+ {
+ AutoSizeAxes = Axes.Both;
+ InternalChild = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(spacing, 0),
+ Children = new Drawable[]
+ {
+ flag = new DismissableFlag
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Margin = new MarginPadding { Bottom = flag_margin },
+ Size = new Vector2(30, 20),
+ },
+ scopeText = new SpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light)
+ },
+ new SpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Light),
+ Text = @"Ranking"
+ }
+ }
+ };
+
+ flag.Action += () => Country.Value = null;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ scopeText.Colour = colours.Lime;
+ }
+
+ protected override void LoadComplete()
+ {
+ Scope.BindValueChanged(onScopeChanged, true);
+ Country.BindValueChanged(onCountryChanged, true);
+ base.LoadComplete();
+ }
+
+ private void onScopeChanged(ValueChangedEvent scope)
+ {
+ scopeText.Text = scope.NewValue.ToString();
+
+ if (scope.NewValue != RankingsScope.Performance)
+ Country.Value = null;
+ }
+
+ private void onCountryChanged(ValueChangedEvent country)
+ {
+ if (country.NewValue == null)
+ {
+ flag.Hide();
+ return;
+ }
+
+ Scope.Value = RankingsScope.Performance;
+
+ flag.Country = country.NewValue;
+ flag.Show();
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/RankingsHeader.cs b/osu.Game/Overlays/Rankings/RankingsHeader.cs
new file mode 100644
index 0000000000..fbf3097f4f
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/RankingsHeader.cs
@@ -0,0 +1,137 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osuTK;
+using osu.Game.Graphics.UserInterface;
+using System.Collections.Generic;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class RankingsHeader : CompositeDrawable
+ {
+ private const int content_height = 250;
+ private const int dropdown_height = 50;
+
+ public IEnumerable Spotlights
+ {
+ get => dropdown.Items;
+ set => dropdown.Items = value;
+ }
+
+ public readonly Bindable Scope = new Bindable();
+ public readonly Bindable Ruleset = new Bindable();
+ public readonly Bindable Country = new Bindable();
+ public readonly Bindable Spotlight = new Bindable();
+
+ private readonly Container dropdownPlaceholder;
+ private readonly OsuDropdown dropdown;
+
+ public RankingsHeader()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ AddInternal(new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ new RankingsRulesetSelector
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Current = Ruleset
+ },
+ new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = content_height,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = new HeaderBackground(),
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 20),
+ Children = new Drawable[]
+ {
+ new RankingsScopeSelector
+ {
+ Margin = new MarginPadding { Top = 10 },
+ Current = Scope
+ },
+ new HeaderTitle
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Margin = new MarginPadding { Top = 10 },
+ Scope = { BindTarget = Scope },
+ Country = { BindTarget = Country },
+ },
+ dropdownPlaceholder = new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = dropdown_height,
+ Width = 0.8f,
+ AlwaysPresent = true,
+ Child = dropdown = new OsuDropdown
+ {
+ RelativeSizeAxes = Axes.X,
+ Current = Spotlight,
+ }
+ }
+ }
+ },
+ }
+ }
+ }
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ Scope.BindValueChanged(onScopeChanged, true);
+ base.LoadComplete();
+ }
+
+ private void onScopeChanged(ValueChangedEvent scope) =>
+ dropdownPlaceholder.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint);
+
+ private class HeaderBackground : Sprite
+ {
+ public HeaderBackground()
+ {
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fill;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get(@"Headers/rankings");
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs
new file mode 100644
index 0000000000..3d25e3995a
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/RankingsRulesetSelector.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets;
+using osuTK;
+using System.Linq;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class RankingsRulesetSelector : PageTabControl
+ {
+ protected override TabItem CreateTabItem(RulesetInfo value) => new RankingsTabItem(value);
+
+ protected override Dropdown CreateDropdown() => null;
+
+ public RankingsRulesetSelector()
+ {
+ AutoSizeAxes = Axes.X;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, RulesetStore rulesets)
+ {
+ foreach (var r in rulesets.AvailableRulesets)
+ AddItem(r);
+
+ AccentColour = colours.Lime;
+
+ SelectTab(TabContainer.FirstOrDefault());
+ }
+
+ protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
+ {
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20, 0),
+ };
+
+ private class RankingsTabItem : PageTabItem
+ {
+ public RankingsTabItem(RulesetInfo value)
+ : base(value)
+ {
+ }
+
+ protected override string CreateText() => $"{Value.Name}";
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs b/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs
new file mode 100644
index 0000000000..2095bcc61c
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/RankingsScopeSelector.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Graphics.UserInterface;
+using osu.Framework.Allocation;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class RankingsScopeSelector : GradientLineTabControl
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AccentColour = LineColour = Color4.Black;
+ }
+ }
+
+ public enum RankingsScope
+ {
+ Performance,
+ Spotlights,
+ Score,
+ Country
+ }
+}
diff --git a/osu.Game/Overlays/Rankings/Spotlight.cs b/osu.Game/Overlays/Rankings/Spotlight.cs
new file mode 100644
index 0000000000..e956b4f449
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/Spotlight.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Newtonsoft.Json;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class Spotlight
+ {
+ [JsonProperty("id")]
+ public int Id;
+
+ [JsonProperty("text")]
+ public string Text;
+
+ public override string ToString() => Text;
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 35be930a2e..d3029d8ab9 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -35,16 +35,16 @@ namespace osu.Game.Overlays.Settings.Sections
Children = new Drawable[]
{
skinDropdown = new SkinSettingsDropdown(),
- new SettingsSlider
+ new SettingsSlider
{
LabelText = "Menu cursor size",
- Bindable = config.GetBindable(OsuSetting.MenuCursorSize),
+ Bindable = config.GetBindable(OsuSetting.MenuCursorSize),
KeyboardStep = 0.01f
},
- new SettingsSlider
+ new SettingsSlider
{
LabelText = "Gameplay cursor size",
- Bindable = config.GetBindable(OsuSetting.GameplayCursorSize),
+ Bindable = config.GetBindable(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
@@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Settings.Sections
}
}
- private class SizeSlider : OsuSliderBar
+ private class SizeSlider : OsuSliderBar
{
public override string TooltipText => Current.Value.ToString(@"0.##x");
}
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index 61c2644c6f..4f8cb7660b 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Judgements
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Child = new SkinnableDrawable($"Play/{Result.Type}", _ => JudgementText = new OsuSpriteText
+ Child = new SkinnableDrawable(new GameplaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText
{
Text = Result.Type.GetDescription().ToUpperInvariant(),
Font = OsuFont.Numeric.With(size: 12),
diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs
index 195fe316ac..59a7917e55 100644
--- a/osu.Game/Rulesets/Judgements/JudgementResult.cs
+++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -16,9 +18,16 @@ namespace osu.Game.Rulesets.Judgements
///
public HitResult Type;
+ ///
+ /// The which was judged.
+ ///
+ [NotNull]
+ public readonly HitObject HitObject;
+
///
/// The which this applies for.
///
+ [NotNull]
public readonly Judgement Judgement;
///
@@ -42,6 +51,11 @@ namespace osu.Game.Rulesets.Judgements
///
public double HealthAtJudgement { get; internal set; }
+ ///
+ /// Whether the user was in a failed state prior to this occurring.
+ ///
+ public bool FailedAtJudgement { get; internal set; }
+
///
/// Whether a miss or hit occurred.
///
@@ -55,9 +69,11 @@ namespace osu.Game.Rulesets.Judgements
///
/// Creates a new .
///
+ /// The which was judged.
/// The to refer to for scoring information.
- public JudgementResult(Judgement judgement)
+ public JudgementResult([NotNull] HitObject hitObject, [NotNull] Judgement judgement)
{
+ HitObject = hitObject;
Judgement = judgement;
}
diff --git a/osu.Game/Rulesets/Objects/BarLine.cs b/osu.Game/Rulesets/Objects/BarLine.cs
new file mode 100644
index 0000000000..a5c716e127
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/BarLine.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Objects
+{
+ ///
+ /// A hit object representing the end of a bar.
+ ///
+ public class BarLine : HitObject
+ {
+ ///
+ /// Whether this barline is a prominent beat (based on time signature of beatmap).
+ ///
+ public bool Major;
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs
new file mode 100644
index 0000000000..ce571d7b17
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs
@@ -0,0 +1,58 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects.Types;
+
+namespace osu.Game.Rulesets.Objects
+{
+ public class BarLineGenerator
+ {
+ ///
+ /// The generated bar lines.
+ ///
+ public readonly List BarLines = new List();
+
+ ///
+ /// Constructs and generates bar lines for provided beatmap.
+ ///
+ /// The beatmap to generate bar lines for.
+ public BarLineGenerator(IBeatmap beatmap)
+ {
+ if (beatmap.HitObjects.Count == 0)
+ return;
+
+ HitObject lastObject = beatmap.HitObjects.Last();
+ double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
+
+ var timingPoints = beatmap.ControlPointInfo.TimingPoints;
+
+ if (timingPoints.Count == 0)
+ return;
+
+ for (int i = 0; i < timingPoints.Count; i++)
+ {
+ TimingControlPoint currentTimingPoint = timingPoints[i];
+ int currentBeat = 0;
+
+ // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
+ double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature;
+
+ double barLength = currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature;
+
+ for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++)
+ {
+ BarLines.Add(new BarLine
+ {
+ StartTime = t,
+ Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 4a6f261905..00b57f7249 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
+using osu.Framework.Threading;
using osu.Game.Audio;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
@@ -75,8 +76,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
public JudgementResult Result { get; private set; }
- private bool judgementOccurred;
-
public override bool RemoveWhenNotAlive => false;
public override bool RemoveCompletedTransforms => false;
protected override bool RequiresChildrenUpdate => true;
@@ -154,6 +153,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (UseTransformStateManagement)
{
+ LifetimeEnd = double.MaxValue;
+
double transformTime = HitObject.StartTime - InitialLifetimeOffset;
base.ApplyTransformsAt(transformTime, true);
@@ -163,7 +164,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
UpdateInitialTransforms();
- var judgementOffset = Math.Min(HitObject.HitWindows?.HalfWindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0);
+ var judgementOffset = Math.Min(HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0);
using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true))
{
@@ -171,6 +172,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
state.Value = newState;
}
}
+
+ if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue)
+ Expire();
}
else
state.Value = newState;
@@ -201,6 +205,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Apply transforms based on the current . Previous states are automatically cleared.
+ /// In the case of a non-idle , and if was not set during this call, will be invoked.
///
/// The new armed state.
protected virtual void UpdateStateTransforms(ArmedState state)
@@ -240,7 +245,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
- AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
+ {
+ var comboColours = skin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+
+ AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
+ }
}
///
@@ -278,6 +287,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
UpdateResult(false);
}
+ ///
+ /// Schedules an to this .
+ ///
+ ///
+ /// Only provided temporarily until hitobject pooling is implemented.
+ ///
+ protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action);
+
private double? lifetimeStart;
public override double LifetimeStart
@@ -297,7 +314,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required.
/// It is indirectly used to decide the automatic transform offset provided to .
- /// A more accurate should be set inside for an state.
+ /// A more accurate should be set for further optimisation (in , for example).
///
protected virtual double InitialLifetimeOffset => 10000;
@@ -333,8 +350,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (!Result.HasResult)
throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}.");
- judgementOccurred = true;
-
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
Result.TimeOffset = Time.Current - endTime;
@@ -367,21 +382,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (Time.Elapsed < 0)
return false;
- judgementOccurred = false;
-
- if (AllJudged)
+ if (Judged)
return false;
- foreach (var d in NestedHitObjects)
- judgementOccurred |= d.UpdateResult(userTriggered);
-
- if (judgementOccurred || Judged)
- return judgementOccurred;
-
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
CheckForResult(userTriggered, Time.Current - endTime);
- return judgementOccurred;
+ return Judged;
}
///
@@ -401,7 +408,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Creates the that represents the scoring result for this .
///
/// The that provides the scoring information.
- protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(judgement);
+ protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement);
}
public abstract class DrawableHitObject : DrawableHitObject
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index bf04963b76..96297ab44f 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -2,12 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects
{
@@ -56,6 +58,7 @@ namespace osu.Game.Rulesets.Objects
///
/// The hit windows for this .
///
+ [CanBeNull]
public HitWindows HitWindows { get; set; }
private readonly List nestedHitObjects = new List();
@@ -111,11 +114,12 @@ namespace osu.Game.Rulesets.Objects
///
/// Creates the for this .
- /// This can be null to indicate that the has no .
+ /// This can be null to indicate that the has no and timing errors should not be displayed to the user.
///
/// This will only be invoked if hasn't been set externally (e.g. from a .
///
///
+ [CanBeNull]
protected virtual HitWindows CreateHitWindows() => new HitWindows();
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
index 06fde576d2..609bdd571a 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
index 096c07f7d2..350ee3185d 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
index 226d91bb86..e372fbd273 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
index eb20fa67f1..067377d300 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
index 84b66a4c26..c9851a0074 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
index c850feb189..1c1180702b 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
index e5a8884aa2..bc94ea1803 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
index 5cecc2a59f..709345170f 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Scoring;
+
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
///
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
index 5cedc6e2e5..c173b3e11a 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Scoring;
+
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
///
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
index ca9fdd53ed..9a35ad2776 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index b63292757d..197c089f71 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
+using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Edit;
@@ -83,6 +84,8 @@ namespace osu.Game.Rulesets
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
+ public virtual IResourceStore CreateReourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources");
+
public abstract string Description { get; }
public virtual RulesetSettingsSubsection CreateSettings() => null;
diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs
index 8c9e3c94e2..cdcd2666cf 100644
--- a/osu.Game/Rulesets/RulesetConfigCache.cs
+++ b/osu.Game/Rulesets/RulesetConfigCache.cs
@@ -36,5 +36,14 @@ namespace osu.Game.Rulesets
return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore));
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ // ensures any potential database operations are finalised before game destruction.
+ foreach (var c in configCache.Values)
+ (c as IDisposable)?.Dispose();
+ }
}
}
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index d9cff86265..6a69fd8dd0 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -20,7 +20,12 @@ namespace osu.Game.Rulesets
[JsonIgnore]
public bool Available { get; set; }
- public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
+ public virtual Ruleset CreateInstance()
+ {
+ if (!Available) return null;
+
+ return (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
+ }
public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs
index 2376f12e9e..7ba88d3df8 100644
--- a/osu.Game/Rulesets/Scoring/HitResult.cs
+++ b/osu.Game/Rulesets/Scoring/HitResult.cs
@@ -16,6 +16,10 @@ namespace osu.Game.Rulesets.Scoring
///
/// Indicates that the object has been judged as a miss.
///
+ ///
+ /// This miss window should determine how early a hit can be before it is considered for judgement (as opposed to being ignored as
+ /// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time).
+ ///
[Description(@"Miss")]
Miss,
diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs
similarity index 55%
rename from osu.Game/Rulesets/Objects/HitWindows.cs
rename to osu.Game/Rulesets/Scoring/HitWindows.cs
index e88af67c7c..efc4cd9f5c 100644
--- a/osu.Game/Rulesets/Objects/HitWindows.cs
+++ b/osu.Game/Rulesets/Scoring/HitWindows.cs
@@ -4,51 +4,31 @@
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Objects;
-namespace osu.Game.Rulesets.Objects
+namespace osu.Game.Rulesets.Scoring
{
+ ///
+ /// A structure containing timing data for hit window based gameplay.
+ ///
public class HitWindows
{
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
+ private static readonly DifficultyRange[] base_ranges =
{
- { HitResult.Perfect, (44.8, 38.8, 27.8) },
- { HitResult.Great, (128, 98, 68) },
- { HitResult.Good, (194, 164, 134) },
- { HitResult.Ok, (254, 224, 194) },
- { HitResult.Meh, (302, 272, 242) },
- { HitResult.Miss, (376, 346, 316) },
+ new DifficultyRange(HitResult.Perfect, 22.4D, 19.4D, 13.9D),
+ new DifficultyRange(HitResult.Great, 64, 49, 34),
+ new DifficultyRange(HitResult.Good, 97, 82, 67),
+ new DifficultyRange(HitResult.Ok, 127, 112, 97),
+ new DifficultyRange(HitResult.Meh, 151, 136, 121),
+ new DifficultyRange(HitResult.Miss, 188, 173, 158),
};
- ///
- /// Hit window for a result.
- ///
- public double Perfect { get; protected set; }
-
- ///
- /// Hit window for a result.
- ///
- public double Great { get; protected set; }
-
- ///
- /// Hit window for a result.
- ///
- public double Good { get; protected set; }
-
- ///
- /// Hit window for an result.
- ///
- public double Ok { get; protected set; }
-
- ///
- /// Hit window for a result.
- ///
- public double Meh { get; protected set; }
-
- ///
- /// Hit window for a result.
- ///
- public double Miss { get; protected set; }
+ private double perfect;
+ private double great;
+ private double good;
+ private double ok;
+ private double meh;
+ private double miss;
///
/// Retrieves the with the largest hit window that produces a successful hit.
@@ -66,15 +46,15 @@ namespace osu.Game.Rulesets.Objects
}
///
- /// Retrieves a mapping of s to their half window timing for all allowed s.
+ /// Retrieves a mapping of s to their timing windows for all allowed s.
///
///
- public IEnumerable<(HitResult result, double length)> GetAllAvailableHalfWindows()
+ public IEnumerable<(HitResult result, double length)> GetAllAvailableWindows()
{
for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result)
{
if (IsHitResultAllowed(result))
- yield return (result, HalfWindowFor(result));
+ yield return (result, WindowFor(result));
}
}
@@ -83,31 +63,45 @@ namespace osu.Game.Rulesets.Objects
///
/// The result type to check.
/// Whether the can be achieved.
- public virtual bool IsHitResultAllowed(HitResult result)
- {
- switch (result)
- {
- case HitResult.Perfect:
- case HitResult.Ok:
- return false;
-
- default:
- return true;
- }
- }
+ public virtual bool IsHitResultAllowed(HitResult result) => true;
///
/// Sets hit windows with values that correspond to a difficulty parameter.
///
/// The parameter.
- public virtual void SetDifficulty(double difficulty)
+ public void SetDifficulty(double difficulty)
{
- Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
+ foreach (var range in GetRanges())
+ {
+ var value = BeatmapDifficulty.DifficultyRange(difficulty, (range.Min, range.Average, range.Max));
+
+ switch (range.Result)
+ {
+ case HitResult.Miss:
+ miss = value;
+ break;
+
+ case HitResult.Meh:
+ meh = value;
+ break;
+
+ case HitResult.Ok:
+ ok = value;
+ break;
+
+ case HitResult.Good:
+ good = value;
+ break;
+
+ case HitResult.Great:
+ great = value;
+ break;
+
+ case HitResult.Perfect:
+ perfect = value;
+ break;
+ }
+ }
}
///
@@ -121,7 +115,7 @@ namespace osu.Game.Rulesets.Objects
for (var result = HitResult.Perfect; result >= HitResult.Miss; --result)
{
- if (IsHitResultAllowed(result) && timeOffset <= HalfWindowFor(result))
+ if (IsHitResultAllowed(result) && timeOffset <= WindowFor(result))
return result;
}
@@ -129,32 +123,32 @@ namespace osu.Game.Rulesets.Objects
}
///
- /// Retrieves half the hit window for a .
- /// This is useful if the hit window for one half of the hittable range of a is required.
+ /// Retrieves the hit window for a .
+ /// This is the number of +/- milliseconds allowed for the requested result (so the actual hittable range is double this).
///
/// The expected .
/// One half of the hit window for .
- public double HalfWindowFor(HitResult result)
+ public double WindowFor(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
- return Perfect / 2;
+ return perfect;
case HitResult.Great:
- return Great / 2;
+ return great;
case HitResult.Good:
- return Good / 2;
+ return good;
case HitResult.Ok:
- return Ok / 2;
+ return ok;
case HitResult.Meh:
- return Meh / 2;
+ return meh;
case HitResult.Miss:
- return Miss / 2;
+ return miss;
default:
throw new ArgumentException(nameof(result));
@@ -167,6 +161,30 @@ namespace osu.Game.Rulesets.Objects
///
/// The time offset.
/// Whether the can be hit at any point in the future from this time offset.
- public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(LowestSuccessfulHitResult());
+ public bool CanBeHit(double timeOffset) => timeOffset <= WindowFor(LowestSuccessfulHitResult());
+
+ ///
+ /// Retrieve a valid list of s representing hit windows.
+ /// Defaults are provided but can be overridden to customise for a ruleset.
+ ///
+ protected virtual DifficultyRange[] GetRanges() => base_ranges;
+ }
+
+ public struct DifficultyRange
+ {
+ public readonly HitResult Result;
+
+ public double Min;
+ public double Average;
+ public double Max;
+
+ public DifficultyRange(HitResult result, double min, double average, double max)
+ {
+ Result = result;
+
+ Min = min;
+ Average = average;
+ Max = max;
+ }
}
}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index e47df6b473..18c2a2ca01 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Scoring
///
/// The current health.
///
- public readonly BindableDouble Health = new BindableDouble { MinValue = 0, MaxValue = 1 };
+ public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
///
/// The current combo.
@@ -90,7 +90,12 @@ namespace osu.Game.Rulesets.Scoring
///
/// Whether all s have been processed.
///
- protected virtual bool HasCompleted => false;
+ public virtual bool HasCompleted => false;
+
+ ///
+ /// The total number of judged s at the current point in time.
+ ///
+ public int JudgedHits { get; protected set; }
///
/// Whether this ScoreProcessor has already triggered the failed state.
@@ -142,6 +147,8 @@ namespace osu.Game.Rulesets.Scoring
Rank.Value = ScoreRank.X;
HighestCombo.Value = 0;
+ JudgedHits = 0;
+
HasFailed = false;
}
@@ -193,7 +200,7 @@ namespace osu.Game.Rulesets.Scoring
score.Statistics[result] = GetStatistic(result);
}
- protected abstract int GetStatistic(HitResult result);
+ public abstract int GetStatistic(HitResult result);
public abstract double GetStandardisedScore();
}
@@ -205,10 +212,9 @@ namespace osu.Game.Rulesets.Scoring
private const double combo_portion = 0.7;
private const double max_score = 1000000;
- protected sealed override bool HasCompleted => JudgedHits == MaxHits;
+ public sealed override bool HasCompleted => JudgedHits == MaxHits;
protected int MaxHits { get; private set; }
- protected int JudgedHits { get; private set; }
private double maxHighestCombo;
@@ -227,6 +233,8 @@ namespace osu.Game.Rulesets.Scoring
drawableRuleset.OnRevertResult += revertResult;
ApplyBeatmap(drawableRuleset.Beatmap);
+
+ Reset(false);
SimulateAutoplay(drawableRuleset.Beatmap);
Reset(true);
@@ -275,7 +283,7 @@ namespace osu.Game.Rulesets.Scoring
if (judgement == null)
return;
- var result = CreateResult(judgement);
+ var result = CreateResult(obj, judgement);
if (result == null)
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
@@ -322,6 +330,10 @@ namespace osu.Game.Rulesets.Scoring
result.ComboAtJudgement = Combo.Value;
result.HighestComboAtJudgement = HighestCombo.Value;
result.HealthAtJudgement = Health.Value;
+ result.FailedAtJudgement = HasFailed;
+
+ if (HasFailed)
+ return;
JudgedHits++;
@@ -369,6 +381,11 @@ namespace osu.Game.Rulesets.Scoring
HighestCombo.Value = result.HighestComboAtJudgement;
Health.Value = result.HealthAtJudgement;
+ // Todo: Revert HasFailed state with proper player support
+
+ if (result.FailedAtJudgement)
+ return;
+
JudgedHits--;
if (result.Judgement.IsBonus)
@@ -415,7 +432,7 @@ namespace osu.Game.Rulesets.Scoring
}
}
- protected override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result);
+ public override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result);
public override double GetStandardisedScore() => getScore(ScoringMode.Standardised);
@@ -432,7 +449,6 @@ namespace osu.Game.Rulesets.Scoring
base.Reset(storeResults);
- JudgedHits = 0;
baseScore = 0;
rollingMaxBaseScore = 0;
bonusScore = 0;
@@ -441,8 +457,9 @@ namespace osu.Game.Rulesets.Scoring
///
/// Creates the that represents the scoring result for a .
///
+ /// The which was judged.
/// The that provides the scoring information.
- protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(judgement);
+ protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement);
}
public enum ScoringMode
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index 021bd515b5..a34bb6e8ea 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -11,12 +11,20 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Events;
+using osu.Framework.IO.Stores;
using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers;
@@ -50,6 +58,10 @@ namespace osu.Game.Rulesets.UI
private readonly Lazy playfield;
+ private TextureStore textureStore;
+
+ private ISampleStore localSampleStore;
+
///
/// The playfield.
///
@@ -141,6 +153,18 @@ namespace osu.Game.Rulesets.UI
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ var resources = Ruleset.CreateReourceStore();
+
+ if (resources != null)
+ {
+ textureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, "Textures")));
+ textureStore.AddStore(dependencies.Get());
+ dependencies.Cache(textureStore);
+
+ localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples"));
+ dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get()));
+ }
+
onScreenDisplay = dependencies.Get();
Config = dependencies.Get().GetConfigFor(Ruleset);
@@ -215,10 +239,6 @@ namespace osu.Game.Rulesets.UI
continueResume();
}
- public ResumeOverlay ResumeOverlay { get; private set; }
-
- protected virtual ResumeOverlay CreateResumeOverlay() => null;
-
///
/// Creates and adds the visual representation of a to this .
///
@@ -317,6 +337,8 @@ namespace osu.Game.Rulesets.UI
{
base.Dispose(isDisposing);
+ localSampleStore?.Dispose();
+
if (Config != null)
{
onScreenDisplay?.StopTracking(this, Config);
@@ -389,6 +411,35 @@ namespace osu.Game.Rulesets.UI
///
public abstract GameplayCursorContainer Cursor { get; }
+ ///
+ /// An optional overlay used when resuming gameplay from a paused state.
+ ///
+ public ResumeOverlay ResumeOverlay { get; protected set; }
+
+ ///
+ /// Returns first available provided by a .
+ ///
+ [CanBeNull]
+ public HitWindows FirstAvailableHitWindows
+ {
+ get
+ {
+ foreach (var h in Objects)
+ {
+ if (h.HitWindows != null)
+ return h.HitWindows;
+
+ foreach (var n in h.NestedHitObjects)
+ if (n.HitWindows != null)
+ return n.HitWindows;
+ }
+
+ return null;
+ }
+ }
+
+ protected virtual ResumeOverlay CreateResumeOverlay() => null;
+
///
/// Sets a replay to be used, overriding local input.
///
@@ -417,4 +468,50 @@ namespace osu.Game.Rulesets.UI
{
}
}
+
+ ///
+ /// A sample store which adds a fallback source.
+ ///
+ ///
+ /// This is a temporary implementation to workaround ISampleStore limitations.
+ ///
+ public class FallbackSampleStore : ISampleStore
+ {
+ private readonly ISampleStore primary;
+ private readonly ISampleStore secondary;
+
+ public FallbackSampleStore(ISampleStore primary, ISampleStore secondary)
+ {
+ this.primary = primary;
+ this.secondary = secondary;
+ }
+
+ public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name);
+
+ public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name);
+
+ public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name);
+
+ public IEnumerable GetAvailableResources() => throw new NotImplementedException();
+
+ public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException();
+
+ public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException();
+
+ public BindableDouble Volume => throw new NotImplementedException();
+
+ public BindableDouble Balance => throw new NotImplementedException();
+
+ public BindableDouble Frequency => throw new NotImplementedException();
+
+ public int PlaybackConcurrency
+ {
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ }
+ }
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 1df8c8218f..bd1f496dfa 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
@@ -50,8 +51,13 @@ namespace osu.Game.Rulesets.UI.Scrolling
public override bool Remove(DrawableHitObject hitObject)
{
var result = base.Remove(hitObject);
+
if (result)
+ {
initialStateCache.Invalidate();
+ hitObjectInitialStateCache.Remove(hitObject);
+ }
+
return result;
}
@@ -86,13 +92,34 @@ namespace osu.Game.Rulesets.UI.Scrolling
scrollingInfo.Algorithm.Reset();
foreach (var obj in Objects)
+ {
+ computeLifetimeStartRecursive(obj);
computeInitialStateRecursive(obj);
+ }
+
initialStateCache.Validate();
}
}
- private void computeInitialStateRecursive(DrawableHitObject hitObject)
+ private void computeLifetimeStartRecursive(DrawableHitObject hitObject)
{
+ hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
+
+ foreach (var obj in hitObject.NestedHitObjects)
+ computeLifetimeStartRecursive(obj);
+ }
+
+ private readonly Dictionary hitObjectInitialStateCache = new Dictionary();
+
+ // Cant use AddOnce() since the delegate is re-constructed every invocation
+ private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
+ {
+ if (!hitObjectInitialStateCache.TryGetValue(hitObject, out var cached))
+ cached = hitObjectInitialStateCache[hitObject] = new Cached();
+
+ if (cached.IsValid)
+ return;
+
double endTime = hitObject.HitObject.StartTime;
if (hitObject.HitObject is IHasEndTime e)
@@ -113,7 +140,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
}
}
- hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength);
foreach (var obj in hitObject.NestedHitObjects)
@@ -123,7 +149,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Nested hitobjects don't need to scroll, but they do need accurate positions
updatePosition(obj, hitObject.HitObject.StartTime);
}
- }
+
+ cached.Validate();
+ });
protected override void UpdateAfterChildrenLife()
{
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index 266725a739..d3c37bd4f4 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -11,8 +11,8 @@ using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
-using osu.Game.Users;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Users;
namespace osu.Game.Scoring
{
diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs
index ea3b68e3bd..2aeb1ef04b 100644
--- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs
+++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs
@@ -4,6 +4,9 @@
using System;
using System.Linq;
using osu.Framework.Bindables;
+using osu.Framework.Graphics.Colour;
+using osu.Game.Graphics;
+using osuTK.Graphics;
namespace osu.Game.Screens.Edit
{
@@ -35,5 +38,41 @@ namespace osu.Game.Screens.Edit
protected override int DefaultMinValue => VALID_DIVISORS.First();
protected override int DefaultMaxValue => VALID_DIVISORS.Last();
protected override int DefaultPrecision => 1;
+
+ ///
+ /// Retrieves the appropriate colour for a beat divisor.
+ ///
+ /// The beat divisor.
+ /// The set of colours.
+ /// The applicable colour from for .
+ public static ColourInfo GetColourFor(int beatDivisor, OsuColour colours)
+ {
+ switch (beatDivisor)
+ {
+ case 2:
+ return colours.BlueLight;
+
+ case 4:
+ return colours.Blue;
+
+ case 8:
+ return colours.BlueDarker;
+
+ case 16:
+ return colours.PurpleDark;
+
+ case 3:
+ return colours.YellowLight;
+
+ case 6:
+ return colours.Yellow;
+
+ case 12:
+ return colours.YellowDarker;
+
+ default:
+ return Color4.White;
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
index 0d16d8474b..4d89e43ee5 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
@@ -188,6 +188,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
private Marker marker;
+ [Resolved]
+ private OsuColour colours { get; set; }
+
private readonly BindableBeatDivisor beatDivisor;
private readonly int[] availableDivisors;
@@ -204,11 +207,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
foreach (var t in availableDivisors)
{
- AddInternal(new Tick(t)
+ AddInternal(new Tick
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopCentre,
RelativePositionAxes = Axes.X,
+ Colour = BindableBeatDivisor.GetColourFor(t, colours),
X = getMappedPosition(t)
});
}
@@ -284,11 +288,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private class Tick : CompositeDrawable
{
- private readonly int divisor;
-
- public Tick(int divisor)
+ public Tick()
{
- this.divisor = divisor;
Size = new Vector2(2.5f, 10);
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
@@ -296,42 +297,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
CornerRadius = 0.5f;
Masking = true;
}
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- Colour = getColourForDivisor(divisor, colours);
- }
-
- private ColourInfo getColourForDivisor(int divisor, OsuColour colours)
- {
- switch (divisor)
- {
- case 2:
- return colours.BlueLight;
-
- case 4:
- return colours.Blue;
-
- case 8:
- return colours.BlueDarker;
-
- case 16:
- return colours.PurpleDark;
-
- case 3:
- return colours.YellowLight;
-
- case 6:
- return colours.Yellow;
-
- case 12:
- return colours.YellowDarker;
-
- default:
- return Color4.White;
- }
- }
}
private class Marker : CompositeDrawable
diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs
index 4fa1a81123..c069f82134 100644
--- a/osu.Game/Screens/Menu/IntroCircles.cs
+++ b/osu.Game/Screens/Menu/IntroCircles.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Screens.Menu
Scheduler.AddDelayed(delegate
{
- // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu.
+ // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
if (menuMusic.Value)
{
track.Restart();
diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs
index 9d0a5cd05b..59ab6ad265 100644
--- a/osu.Game/Screens/Menu/LogoVisualisation.cs
+++ b/osu.Game/Screens/Menu/LogoVisualisation.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu
private const float visualiser_rounds = 5;
///
- /// How much should each bar go down each milisecond (based on a full bar).
+ /// How much should each bar go down each millisecond (based on a full bar).
///
private const float decay_per_milisecond = 0.0024f;
@@ -122,7 +122,7 @@ namespace osu.Game.Screens.Menu
Color4 defaultColour = Color4.White.Opacity(0.2f);
if (user.Value?.IsSupporter ?? false)
- AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour;
+ AccentColour = skin.Value.GetConfig(GlobalSkinColour.MenuGlow)?.Value ?? defaultColour;
else
AccentColour = defaultColour;
}
@@ -161,7 +161,7 @@ namespace osu.Game.Screens.Menu
private IShader shader;
private Texture texture;
- //Asuming the logo is a circle, we don't need a second dimension.
+ //Assuming the logo is a circle, we don't need a second dimension.
private float size;
private Color4 colour;
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 499b5089f6..a006877082 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -11,6 +11,7 @@ using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
+using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
@@ -44,6 +45,12 @@ namespace osu.Game.Screens.Menu
[Resolved(canBeNull: true)]
private MusicController music { get; set; }
+ [Resolved(canBeNull: true)]
+ private LoginOverlay login { get; set; }
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
private BackgroundScreenDefault background;
protected override BackgroundScreen CreateBackground() => background;
@@ -133,6 +140,8 @@ namespace osu.Game.Screens.Menu
Beatmap.ValueChanged += beatmap_ValueChanged;
}
+ private bool loginDisplayed;
+
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
base.LogoArriving(logo, resuming);
@@ -151,6 +160,21 @@ namespace osu.Game.Screens.Menu
sideFlashes.Delay(FADE_IN_DURATION).FadeIn(64, Easing.InQuint);
}
+ else if (!api.IsLoggedIn)
+ {
+ logo.Action += displayLogin;
+ }
+
+ bool displayLogin()
+ {
+ if (!loginDisplayed)
+ {
+ Scheduler.AddDelayed(() => login?.Show(), 500);
+ loginDisplayed = true;
+ }
+
+ return true;
+ }
}
protected override void LogoSuspending(OsuLogo logo)
diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 393964561c..55a6a33e89 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Screens.Menu
Color4 baseColour = colours.Blue;
if (user.Value?.IsSupporter ?? false)
- baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour;
+ baseColour = skin.Value.GetConfig(GlobalSkinColour.MenuGlow)?.Value ?? baseColour;
// linear colour looks better in this case, so let's use it for now.
Color4 gradientDark = baseColour.Opacity(0).ToLinear();
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 0c5bf12bdb..534400e720 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -229,7 +229,7 @@ namespace osu.Game.Screens.Menu
}
///
- /// Schedule a new extenral animation. Handled queueing and finishing previous animations in a sane way.
+ /// Schedule a new external animation. Handled queueing and finishing previous animations in a sane way.
///
/// The animation to be performed
/// If true, the new animation is delayed until all previous transforms finish. If false, existing transformed are cleared.
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index c7e762714c..f54d638584 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -304,6 +304,7 @@ namespace osu.Game.Screens.Play
private class Button : DialogButton
{
+ // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved)
protected override bool OnHover(HoverEvent e) => true;
protected override bool OnMouseMove(MouseMoveEvent e)
@@ -312,5 +313,22 @@ namespace osu.Game.Screens.Play
return base.OnMouseMove(e);
}
}
+
+ [Resolved]
+ private GlobalActionContainer globalAction { get; set; }
+
+ protected override bool Handle(UIEvent e)
+ {
+ switch (e)
+ {
+ case ScrollEvent _:
+ if (ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
+ return globalAction.TriggerEvent(e);
+
+ break;
+ }
+
+ return base.Handle(e);
+ }
}
}
diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
index cdfa0e993b..920d11c910 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
@@ -49,6 +48,9 @@ namespace osu.Game.Screens.Play.HUD
private void onNewJudgement(JudgementResult result)
{
+ if (result.HitObject.HitWindows == null)
+ return;
+
foreach (var c in Children)
c.OnNewJudgement(result);
}
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index 594dd64e52..03a0f23fb6 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osuTK.Graphics;
@@ -146,7 +145,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
private void createColourBars(OsuColour colours)
{
- var windows = HitWindows.GetAllAvailableHalfWindows().ToArray();
+ var windows = HitWindows.GetAllAvailableWindows().ToArray();
maxHitWindow = windows.First().length;
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
index da1d9fff0d..dee25048ed 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
@@ -3,7 +3,7 @@
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 8e642ea552..eee7235a6e 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -259,7 +258,7 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding { Top = 20, Right = 10 },
};
- protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault()?.HitWindows);
+ protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.FirstAvailableHitWindows);
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 274107dfa5..5c7ac9e762 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -180,6 +180,7 @@ namespace osu.Game.Screens.Play
},
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
+ DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value)
{
HoldToQuit =
@@ -503,15 +504,18 @@ namespace osu.Game.Screens.Play
return true;
}
- if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value)
- // still want to block if we are within the cooldown period and not already paused.
- return true;
-
- if (HasFailed && ValidForResume && !FailOverlay.IsPresent)
- // ValidForResume is false when restarting
+ // ValidForResume is false when restarting
+ if (ValidForResume)
{
- failAnimation.FinishTransforms(true);
- return true;
+ if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value)
+ // still want to block if we are within the cooldown period and not already paused.
+ return true;
+
+ if (HasFailed && !FailOverlay.IsPresent)
+ {
+ failAnimation.FinishTransforms(true);
+ return true;
+ }
}
GameplayClockContainer.ResetLocalAdjustments();
diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs
index 9c56725c4e..05f6128ac2 100644
--- a/osu.Game/Screens/Play/SquareGraph.cs
+++ b/osu.Game/Screens/Play/SquareGraph.cs
@@ -103,6 +103,7 @@ namespace osu.Game.Screens.Play
var newColumns = new BufferedContainer
{
CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
};
diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs
index b66a2ffe0f..5348de68d6 100644
--- a/osu.Game/Screens/Select/BeatmapDetailArea.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs
@@ -27,8 +27,8 @@ namespace osu.Game.Screens.Select
set
{
beatmap = value;
- Leaderboard.Beatmap = beatmap?.BeatmapInfo;
Details.Beatmap = beatmap?.BeatmapInfo;
+ Leaderboard.Beatmap = beatmap is DummyWorkingBeatmap ? null : beatmap?.BeatmapInfo;
}
}
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 5f6307e3b4..65ecd7b812 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -154,6 +154,8 @@ namespace osu.Game.Screens.Select
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
CacheDrawnFrameBuffer = true;
+ RedrawOnScale = false;
+
RelativeSizeAxes = Axes.Both;
titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title)));
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index 97b6a78804..699e01bca7 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -146,6 +146,7 @@ namespace osu.Game.Screens.Select.Carousel
public PanelBackground(WorkingBeatmap working)
{
CacheDrawnFrameBuffer = true;
+ RedrawOnScale = false;
Children = new Drawable[]
{
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index cb45c00f66..33f040755e 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -83,6 +83,12 @@ namespace osu.Game.Screens.Select.Leaderboards
protected override APIRequest FetchScores(Action> scoresCallback)
{
+ if (Beatmap == null)
+ {
+ PlaceholderState = PlaceholderState.NoneSelected;
+ return null;
+ }
+
if (Scope == BeatmapLeaderboardScope.Local)
{
var scores = scoreManager
@@ -113,7 +119,7 @@ namespace osu.Game.Screens.Select.Leaderboards
return null;
}
- if (Beatmap?.OnlineBeatmapID == null || Beatmap?.Status <= BeatmapSetOnlineStatus.Pending)
+ if (Beatmap.OnlineBeatmapID == null || Beatmap?.Status <= BeatmapSetOnlineStatus.Pending)
{
PlaceholderState = PlaceholderState.Unavailable;
return null;
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index edb0e6deb8..d0cb5986a8 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -414,7 +414,11 @@ namespace osu.Game.Screens.Select
{
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
- Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value);
+ WorkingBeatmap previous = Beatmap.Value;
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous);
+
+ if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track)
+ ensurePlayingSelected();
if (beatmap != null)
{
@@ -425,8 +429,6 @@ namespace osu.Game.Screens.Select
}
}
- if (this.IsCurrentScreen())
- ensurePlayingSelected();
UpdateBeatmap(Beatmap.Value);
}
}
diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs
index 345df35b12..40335db697 100644
--- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs
+++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Skinning
private readonly Bindable beatmapHitsounds = new Bindable();
protected override bool AllowConfigurationLookup => beatmapSkins.Value;
- protected override bool AllowDrawableLookup(string componentName) => beatmapSkins.Value;
+ protected override bool AllowDrawableLookup(ISkinComponent component) => beatmapSkins.Value;
protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value;
protected override bool AllowSampleLookup(ISampleInfo componentName) => beatmapHitsounds.Value;
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index b35c9c7b97..98f158c725 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -3,6 +3,7 @@
using osu.Framework.Audio;
using osu.Framework.IO.Stores;
+using osuTK.Graphics;
namespace osu.Game.Skinning
{
@@ -11,6 +12,7 @@ namespace osu.Game.Skinning
public DefaultLegacySkin(IResourceStore storage, AudioManager audioManager)
: base(Info, storage, audioManager, string.Empty)
{
+ Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
}
public static SkinInfo Info { get; } = new SkinInfo
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index ec957566cb..529c1afca5 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
+using osuTK.Graphics;
namespace osu.Game.Skinning
{
@@ -16,10 +19,29 @@ namespace osu.Game.Skinning
Configuration = new DefaultSkinConfiguration();
}
- public override Drawable GetDrawableComponent(string componentName) => null;
+ public override Drawable GetDrawableComponent(ISkinComponent component) => null;
public override Texture GetTexture(string componentName) => null;
public override SampleChannel GetSample(ISampleInfo sampleInfo) => null;
+
+ public override IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ // todo: this code is pulled from LegacySkin and should not exist.
+ // will likely change based on how databased storage of skin configuration goes.
+ case GlobalSkinConfiguration global:
+ switch (global)
+ {
+ case GlobalSkinConfiguration.ComboColours:
+ return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ }
+
+ break;
+ }
+
+ return null;
+ }
}
}
diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs
index 722b35f102..f52fac6077 100644
--- a/osu.Game/Skinning/DefaultSkinConfiguration.cs
+++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs
@@ -12,8 +12,6 @@ namespace osu.Game.Skinning
{
public DefaultSkinConfiguration()
{
- HitCircleFont = "default";
-
ComboColours.AddRange(new[]
{
new Color4(17, 136, 170, 255),
@@ -21,8 +19,6 @@ namespace osu.Game.Skinning
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
});
-
- CursorExpand = true;
}
}
}
diff --git a/osu.Game/Skinning/GameplaySkinComponent.cs b/osu.Game/Skinning/GameplaySkinComponent.cs
new file mode 100644
index 0000000000..2aa380fa90
--- /dev/null
+++ b/osu.Game/Skinning/GameplaySkinComponent.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+
+namespace osu.Game.Skinning
+{
+ public class GameplaySkinComponent : ISkinComponent
+ {
+ public readonly T Component;
+
+ public GameplaySkinComponent(T component)
+ {
+ Component = component;
+ }
+
+ protected virtual string RulesetPrefix => string.Empty;
+ protected virtual string ComponentName => Component.ToString();
+
+ public string LookupName =>
+ string.Join("/", new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+}
diff --git a/osu.Game/Skinning/GlobalSkinColour.cs b/osu.Game/Skinning/GlobalSkinColour.cs
new file mode 100644
index 0000000000..d039be98ce
--- /dev/null
+++ b/osu.Game/Skinning/GlobalSkinColour.cs
@@ -0,0 +1,10 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Skinning
+{
+ public enum GlobalSkinColour
+ {
+ MenuGlow
+ }
+}
diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs
new file mode 100644
index 0000000000..66dc9a9395
--- /dev/null
+++ b/osu.Game/Skinning/GlobalSkinConfiguration.cs
@@ -0,0 +1,10 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Skinning
+{
+ public enum GlobalSkinConfiguration
+ {
+ ComboColours
+ }
+}
diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs
index 4867aba0a9..cb2a379b8e 100644
--- a/osu.Game/Skinning/ISkin.cs
+++ b/osu.Game/Skinning/ISkin.cs
@@ -1,8 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
+using JetBrains.Annotations;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -14,12 +15,36 @@ namespace osu.Game.Skinning
///
public interface ISkin
{
- Drawable GetDrawableComponent(string componentName);
+ ///
+ /// Retrieve a component implementation.
+ ///
+ /// The requested component.
+ /// A drawable representation for the requested component, or null if unavailable.
+ [CanBeNull]
+ Drawable GetDrawableComponent(ISkinComponent component);
+ ///
+ /// Retrieve a .
+ ///
+ /// The requested texture.
+ /// A matching texture, or null if unavailable.
+ [CanBeNull]
Texture GetTexture(string componentName);
+ ///
+ /// Retrieve a .
+ ///
+ /// The requested sample.
+ /// A matching sample channel, or null if unavailable.
+ [CanBeNull]
SampleChannel GetSample(ISampleInfo sampleInfo);
- TValue GetValue(Func query) where TConfiguration : SkinConfiguration;
+ ///
+ /// Retrieve a configuration value.
+ ///
+ /// The requested configuration value.
+ /// A matching value boxed in an , or null if unavailable.
+ [CanBeNull]
+ IBindable GetConfig(TLookup lookup);
}
}
diff --git a/osu.Game/Skinning/ISkinComponent.cs b/osu.Game/Skinning/ISkinComponent.cs
new file mode 100644
index 0000000000..4bd9f21b6b
--- /dev/null
+++ b/osu.Game/Skinning/ISkinComponent.cs
@@ -0,0 +1,10 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Skinning
+{
+ public interface ISkinComponent
+ {
+ string LookupName { get; }
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 56b8aa19c2..0b1076be01 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -1,43 +1,50 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.IO;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Audio;
+using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
namespace osu.Game.Skinning
{
public class LegacySkin : Skin
{
+ [CanBeNull]
protected TextureStore Textures;
+ [CanBeNull]
protected IResourceStore Samples;
public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager)
: this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini")
{
- // defaults should only be applied for non-beatmap skins (which are parsed via this constructor).
- if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
}
protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename)
: base(skin)
{
- Stream stream = storage.GetStream(filename);
+ Stream stream = storage?.GetStream(filename);
if (stream != null)
using (StreamReader reader = new StreamReader(stream))
Configuration = new LegacySkinDecoder().Decode(reader);
else
Configuration = new DefaultSkinConfiguration();
- Samples = audioManager.GetSampleStore(storage);
- Textures = new TextureStore(new TextureLoaderStore(storage));
+ if (storage != null)
+ {
+ Samples = audioManager?.GetSampleStore(storage);
+ Textures = new TextureStore(new TextureLoaderStore(storage));
+ }
}
protected override void Dispose(bool isDisposing)
@@ -47,39 +54,76 @@ namespace osu.Game.Skinning
Samples?.Dispose();
}
- public override Drawable GetDrawableComponent(string componentName)
+ public override IBindable GetConfig(TLookup lookup)
{
- bool animatable = false;
- bool looping = true;
-
- switch (componentName)
+ switch (lookup)
{
- case "Play/Miss":
- componentName = "hit0";
- animatable = true;
- looping = false;
+ case GlobalSkinConfiguration global:
+ switch (global)
+ {
+ case GlobalSkinConfiguration.ComboColours:
+ return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ }
+
break;
- case "Play/Meh":
- componentName = "hit50";
- animatable = true;
- looping = false;
- break;
+ case GlobalSkinColour colour:
+ return SkinUtils.As(getCustomColour(colour.ToString()));
- case "Play/Good":
- componentName = "hit100";
- animatable = true;
- looping = false;
- break;
+ case SkinCustomColourLookup customColour:
+ return SkinUtils.As(getCustomColour(customColour.Lookup.ToString()));
+
+ default:
+ try
+ {
+ if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out var val))
+ {
+ // special case for handling skins which use 1 or 0 to signify a boolean state.
+ if (typeof(TValue) == typeof(bool))
+ val = val == "1" ? "true" : "false";
+
+ var bindable = new Bindable();
+ if (val != null)
+ bindable.Parse(val);
+ return bindable;
+ }
+ }
+ catch
+ {
+ }
- case "Play/Great":
- componentName = "hit300";
- animatable = true;
- looping = false;
break;
}
- return this.GetAnimation(componentName, animatable, looping);
+ return null;
+ }
+
+ private IBindable getCustomColour(string lookup) => Configuration.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null;
+
+ public override Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case GameplaySkinComponent resultComponent:
+ switch (resultComponent.Component)
+ {
+ case HitResult.Miss:
+ return this.GetAnimation("hit0", true, false);
+
+ case HitResult.Meh:
+ return this.GetAnimation("hit50", true, false);
+
+ case HitResult.Good:
+ return this.GetAnimation("hit100", true, false);
+
+ case HitResult.Great:
+ return this.GetAnimation("hit300", true, false);
+ }
+
+ break;
+ }
+
+ return this.GetAnimation(component.LookupName, false, false);
}
public override Texture GetTexture(string componentName)
@@ -87,12 +131,12 @@ namespace osu.Game.Skinning
componentName = getFallbackName(componentName);
float ratio = 2;
- var texture = Textures.Get($"{componentName}@2x");
+ var texture = Textures?.Get($"{componentName}@2x");
if (texture == null)
{
ratio = 1;
- texture = Textures.Get(componentName);
+ texture = Textures?.Get(componentName);
}
if (texture != null)
@@ -105,7 +149,7 @@ namespace osu.Game.Skinning
{
foreach (var lookup in sampleInfo.LookupNames)
{
- var sample = Samples.Get(getFallbackName(lookup));
+ var sample = Samples?.Get(getFallbackName(lookup));
if (sample != null)
return sample;
@@ -113,7 +157,7 @@ namespace osu.Game.Skinning
if (sampleInfo is HitSampleInfo hsi)
// Try fallback to non-bank samples.
- return Samples.Get(hsi.Name);
+ return Samples?.Get(hsi.Name);
return null;
}
diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs
index 0160755eed..e97664e75e 100644
--- a/osu.Game/Skinning/LegacySkinDecoder.cs
+++ b/osu.Game/Skinning/LegacySkinDecoder.cs
@@ -14,47 +14,31 @@ namespace osu.Game.Skinning
protected override void ParseLine(DefaultSkinConfiguration skin, Section section, string line)
{
- line = StripComments(line);
-
- var pair = SplitKeyVal(line);
-
- switch (section)
+ if (section != Section.Colours)
{
- case Section.General:
- switch (pair.Key)
- {
- case @"Name":
- skin.SkinInfo.Name = pair.Value;
- break;
+ line = StripComments(line);
- case @"Author":
- skin.SkinInfo.Creator = pair.Value;
- break;
+ var pair = SplitKeyVal(line);
- case @"CursorExpand":
- skin.CursorExpand = pair.Value != "0";
- break;
+ switch (section)
+ {
+ case Section.General:
+ switch (pair.Key)
+ {
+ case @"Name":
+ skin.SkinInfo.Name = pair.Value;
+ return;
- case @"SliderBorderSize":
- skin.SliderBorderSize = Parsing.ParseFloat(pair.Value);
- break;
- }
+ case @"Author":
+ skin.SkinInfo.Creator = pair.Value;
+ return;
+ }
- break;
+ break;
+ }
- case Section.Fonts:
- switch (pair.Key)
- {
- case "HitCirclePrefix":
- skin.HitCircleFont = pair.Value;
- break;
-
- case "HitCircleOverlap":
- skin.HitCircleOverlap = int.Parse(pair.Value);
- break;
- }
-
- break;
+ if (!string.IsNullOrEmpty(pair.Key))
+ skin.ConfigDictionary[pair.Key] = pair.Value;
}
base.ParseLine(skin, section, line);
diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs
index dbcec019d6..773a9dc5c6 100644
--- a/osu.Game/Skinning/LegacySpriteText.cs
+++ b/osu.Game/Skinning/LegacySpriteText.cs
@@ -4,7 +4,6 @@
using System.Threading.Tasks;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Text;
-using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Skinning
@@ -18,7 +17,7 @@ namespace osu.Game.Skinning
Shadow = false;
UseFullGlyphHeight = false;
- Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE);
+ Font = new FontUsage(font, 1);
glyphStore = new LegacyGlyphStore(skin);
}
@@ -37,10 +36,6 @@ namespace osu.Game.Skinning
{
var texture = skin.GetTexture($"{fontName}-{character}");
- if (texture != null)
- // Approximate value that brings character sizing roughly in-line with stable
- texture.ScaleAdjust *= 18;
-
if (texture == null)
return null;
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 027d9df8b8..fa4aebd8a5 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -13,16 +14,15 @@ namespace osu.Game.Skinning
{
public readonly SkinInfo SkinInfo;
- public virtual SkinConfiguration Configuration { get; protected set; }
+ public SkinConfiguration Configuration { get; protected set; }
- public abstract Drawable GetDrawableComponent(string componentName);
+ public abstract Drawable GetDrawableComponent(ISkinComponent componentName);
public abstract SampleChannel GetSample(ISampleInfo sampleInfo);
public abstract Texture GetTexture(string componentName);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
- => Configuration is TConfiguration conf ? query.Invoke(conf) : default;
+ public abstract IBindable GetConfig(TLookup lookup);
protected Skin(SkinInfo skin)
{
diff --git a/osu.Game/Skinning/SkinConfigManager.cs b/osu.Game/Skinning/SkinConfigManager.cs
new file mode 100644
index 0000000000..896444d1d2
--- /dev/null
+++ b/osu.Game/Skinning/SkinConfigManager.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Configuration;
+
+namespace osu.Game.Skinning
+{
+ public class SkinConfigManager : ConfigManager where T : struct
+ {
+ protected override void PerformLoad()
+ {
+ }
+
+ protected override bool PerformSave() => false;
+ }
+}
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index d585c58ef1..54aac86e3c 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -18,14 +18,6 @@ namespace osu.Game.Skinning
public Dictionary CustomColours { get; set; } = new Dictionary();
- public string HitCircleFont { get; set; }
-
- public int HitCircleOverlap { get; set; }
-
- public float? SliderBorderSize { get; set; }
-
- public float? SliderPathRadius { get; set; }
-
- public bool? CursorExpand { get; set; }
+ public readonly Dictionary ConfigDictionary = new Dictionary();
}
}
diff --git a/osu.Game/Skinning/SkinCustomColourLookup.cs b/osu.Game/Skinning/SkinCustomColourLookup.cs
new file mode 100644
index 0000000000..b8e5ac9b53
--- /dev/null
+++ b/osu.Game/Skinning/SkinCustomColourLookup.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Skinning
+{
+ public class SkinCustomColourLookup
+ {
+ public readonly object Lookup;
+
+ public SkinCustomColourLookup(object lookup)
+ {
+ Lookup = lookup;
+ }
+ }
+}
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index a713933c6e..aa3b3981c2 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -125,12 +125,12 @@ namespace osu.Game.Skinning
public event Action SourceChanged;
- public Drawable GetDrawableComponent(string componentName) => CurrentSkin.Value.GetDrawableComponent(componentName);
+ public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component);
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query);
+ public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup);
}
}
diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs
index 45b8baa0bb..ef7f5f381b 100644
--- a/osu.Game/Skinning/SkinProvidingContainer.cs
+++ b/osu.Game/Skinning/SkinProvidingContainer.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
@@ -22,7 +23,7 @@ namespace osu.Game.Skinning
private ISkinSource fallbackSource;
- protected virtual bool AllowDrawableLookup(string componentName) => true;
+ protected virtual bool AllowDrawableLookup(ISkinComponent component) => true;
protected virtual bool AllowTextureLookup(string componentName) => true;
@@ -37,13 +38,13 @@ namespace osu.Game.Skinning
RelativeSizeAxes = Axes.Both;
}
- public Drawable GetDrawableComponent(string componentName)
+ public Drawable GetDrawableComponent(ISkinComponent component)
{
Drawable sourceDrawable;
- if (AllowDrawableLookup(componentName) && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null)
+ if (AllowDrawableLookup(component) && (sourceDrawable = skin?.GetDrawableComponent(component)) != null)
return sourceDrawable;
- return fallbackSource?.GetDrawableComponent(componentName);
+ return fallbackSource?.GetDrawableComponent(component);
}
public Texture GetTexture(string componentName)
@@ -64,13 +65,16 @@ namespace osu.Game.Skinning
return fallbackSource?.GetSample(sampleInfo);
}
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
+ public IBindable GetConfig(TLookup lookup)
{
- TValue val;
- if (AllowConfigurationLookup && skin != null && (val = skin.GetValue(query)) != null)
- return val;
+ if (AllowConfigurationLookup && skin != null)
+ {
+ var bindable = skin.GetConfig(lookup);
+ if (bindable != null)
+ return bindable;
+ }
- return fallbackSource == null ? default : fallbackSource.GetValue(query);
+ return fallbackSource?.GetConfig(lookup);
}
protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke();
diff --git a/osu.Game/Skinning/SkinUtils.cs b/osu.Game/Skinning/SkinUtils.cs
new file mode 100644
index 0000000000..e3bc5e28b8
--- /dev/null
+++ b/osu.Game/Skinning/SkinUtils.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Contains helper methods to assist in implementing s.
+ ///
+ public static class SkinUtils
+ {
+ ///
+ /// Converts an to a . Used for returning configuration values of specific types.
+ ///
+ /// The value.
+ /// The type of value , and the type of the resulting bindable.
+ /// The resulting bindable.
+ public static Bindable As(object value) => (Bindable)value;
+ }
+}
diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs
index 07f802944b..9ca5d60cb0 100644
--- a/osu.Game/Skinning/SkinnableDrawable.cs
+++ b/osu.Game/Skinning/SkinnableDrawable.cs
@@ -18,39 +18,39 @@ namespace osu.Game.Skinning
///
public Drawable Drawable { get; private set; }
- private readonly string componentName;
+ private readonly ISkinComponent component;
private readonly ConfineMode confineMode;
///
/// Create a new skinnable drawable.
///
- /// The namespace-complete resource name for this skinnable element.
+ /// The namespace-complete resource name for this skinnable element.
/// A function to create the default skin implementation of this element.
/// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.
/// How (if at all) the should be resize to fit within our own bounds.
- public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
- : this(name, allowFallback, confineMode)
+ public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : this(component, allowFallback, confineMode)
{
createDefault = defaultImplementation;
}
- protected SkinnableDrawable(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ protected SkinnableDrawable(ISkinComponent component, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(allowFallback)
{
- componentName = name;
+ this.component = component;
this.confineMode = confineMode;
RelativeSizeAxes = Axes.Both;
}
- private readonly Func createDefault;
+ private readonly Func createDefault;
private readonly Cached scaling = new Cached();
private bool isDefault;
- protected virtual Drawable CreateDefault(string name) => createDefault(name);
+ protected virtual Drawable CreateDefault(ISkinComponent component) => createDefault(component);
///
/// Whether to apply size restrictions (specified via ) to the default implementation.
@@ -59,13 +59,13 @@ namespace osu.Game.Skinning
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
- Drawable = skin.GetDrawableComponent(componentName);
+ Drawable = skin.GetDrawableComponent(component);
isDefault = false;
if (Drawable == null && allowFallback)
{
- Drawable = CreateDefault(componentName);
+ Drawable = CreateDefault(component);
isDefault = true;
}
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index dcb14b4412..bdf8be773b 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -6,6 +6,8 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Audio;
@@ -14,9 +16,12 @@ namespace osu.Game.Skinning
public class SkinnableSound : SkinReloadableDrawable
{
private readonly ISampleInfo[] hitSamples;
+
+ private List<(AdjustableProperty property, BindableDouble bindable)> adjustments;
+
private SampleChannel[] channels;
- private AudioManager audio;
+ private ISampleStore samples;
public SkinnableSound(IEnumerable hitSamples)
{
@@ -29,13 +34,44 @@ namespace osu.Game.Skinning
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load(ISampleStore samples)
{
- this.audio = audio;
+ this.samples = samples;
+ }
+
+ private bool looping;
+
+ public bool Looping
+ {
+ get => looping;
+ set
+ {
+ if (value == looping) return;
+
+ looping = value;
+
+ channels?.ForEach(c => c.Looping = looping);
+ }
}
public void Play() => channels?.ForEach(c => c.Play());
+ public void Stop() => channels?.ForEach(c => c.Stop());
+
+ public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
+ {
+ if (adjustments == null) adjustments = new List<(AdjustableProperty, BindableDouble)>();
+
+ adjustments.Add((type, adjustBindable));
+ channels?.ForEach(c => c.AddAdjustment(type, adjustBindable));
+ }
+
+ public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
+ {
+ adjustments?.Remove((type, adjustBindable));
+ channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable));
+ }
+
public override bool IsPresent => Scheduler.HasPendingTasks;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
@@ -46,12 +82,19 @@ namespace osu.Game.Skinning
if (ch == null && allowFallback)
foreach (var lookup in s.LookupNames)
- if ((ch = audio.Samples.Get($"Gameplay/{lookup}")) != null)
+ if ((ch = samples.Get($"Gameplay/{lookup}")) != null)
break;
if (ch != null)
+ {
+ ch.Looping = looping;
ch.Volume.Value = s.Volume / 100.0;
+ if (adjustments != null)
+ foreach (var adjustment in adjustments)
+ ch.AddAdjustment(adjustment.property, adjustment.bindable);
+ }
+
return ch;
}).Where(c => c != null).ToArray();
}
@@ -60,8 +103,9 @@ namespace osu.Game.Skinning
{
base.Dispose(isDisposing);
- foreach (var c in channels)
- c.Dispose();
+ if (channels != null)
+ foreach (var c in channels)
+ c.Dispose();
}
}
}
diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs
index 07ba48d6ae..4b78493e97 100644
--- a/osu.Game/Skinning/SkinnableSprite.cs
+++ b/osu.Game/Skinning/SkinnableSprite.cs
@@ -19,11 +19,23 @@ namespace osu.Game.Skinning
[Resolved]
private TextureStore textures { get; set; }
- public SkinnableSprite(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
- : base(name, allowFallback, confineMode)
+ public SkinnableSprite(string textureName, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(new SpriteComponent(textureName), allowFallback, confineMode)
{
}
- protected override Drawable CreateDefault(string name) => new Sprite { Texture = textures.Get(name) };
+ protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) };
+
+ private class SpriteComponent : ISkinComponent
+ {
+ private readonly string textureName;
+
+ public SpriteComponent(string textureName)
+ {
+ this.textureName = textureName;
+ }
+
+ public string LookupName => textureName;
+ }
}
}
diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs
index 5af6df15e1..e72f9c9811 100644
--- a/osu.Game/Skinning/SkinnableSpriteText.cs
+++ b/osu.Game/Skinning/SkinnableSpriteText.cs
@@ -8,8 +8,8 @@ namespace osu.Game.Skinning
{
public class SkinnableSpriteText : SkinnableDrawable, IHasText
{
- public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
- : base(name, defaultImplementation, allowFallback, confineMode)
+ public SkinnableSpriteText(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(component, defaultImplementation, allowFallback, confineMode)
{
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
index b04f1d4518..f3f8308964 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
@@ -64,5 +64,11 @@ namespace osu.Game.Storyboards.Drawables
LifetimeEnd = sampleInfo.StartTime;
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ channel?.Stop();
+ base.Dispose(isDisposing);
+ }
}
}
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 27d72f3950..dd68ed93e6 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -16,6 +16,7 @@ using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Database;
+using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Beatmaps;
@@ -47,6 +48,12 @@ namespace osu.Game.Tests.Visual
private readonly Lazy contextFactory;
protected DatabaseContextFactory ContextFactory => contextFactory.Value;
+ ///
+ /// Whether this test scene requires API access
+ /// Setting this will cache an actual .
+ ///
+ protected virtual bool RequiresAPIAccess => false;
+
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
// This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures
@@ -57,7 +64,17 @@ namespace osu.Game.Tests.Visual
Default = working
};
- return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ Dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ if (!RequiresAPIAccess)
+ {
+ var dummyAPI = new DummyAPIAccess();
+
+ Dependencies.CacheAs(dummyAPI);
+ Add(dummyAPI);
+ }
+
+ return Dependencies;
}
protected OsuTestScene()
diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs
index 1ab20ecd48..2c5a51ca02 100644
--- a/osu.Game/Tests/Visual/PlayerTestScene.cs
+++ b/osu.Game/Tests/Visual/PlayerTestScene.cs
@@ -3,6 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets;
@@ -40,6 +41,8 @@ namespace osu.Game.Tests.Visual
protected virtual bool AllowFail => false;
+ protected virtual bool Autoplay => false;
+
private void loadPlayer()
{
var beatmap = CreateBeatmap(ruleset.RulesetInfo);
@@ -47,7 +50,18 @@ namespace osu.Game.Tests.Visual
Beatmap.Value = CreateWorkingBeatmap(beatmap);
if (!AllowFail)
- Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
+ {
+ var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail);
+ if (noFailMod != null)
+ Mods.Value = new[] { noFailMod };
+ }
+
+ if (Autoplay)
+ {
+ var mod = ruleset.GetAutoplayMod();
+ if (mod != null)
+ Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray();
+ }
Player = CreatePlayer(ruleset);
LoadScreen(Player);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 330018d5cb..5703293caf 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -6,16 +6,27 @@
AnyCPU
true
+
+ osu!
+ ppy.osu.Game
+ ppy Pty Ltd
+ https://github.com/ppy/osu/blob/master/LICENCE.md
+ https://github.com/ppy/osu
+ https://github.com/ppy/osu
+ Automated release.
+ Copyright (c) 2019 ppy Pty Ltd
+ osu game
+
-
+
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9f8d82ad1e..683dccf3ae 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -113,13 +113,13 @@
-
+
-
-
-
+
+
+