diff --git a/osu.Android.props b/osu.Android.props
index 77c29a5d6e..dd263d6aaa 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 182cc51572..2dc99077d3 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -21,7 +21,6 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
@@ -250,11 +249,9 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestHitLightingColour()
{
- var fruitColour = SkinConfiguration.DefaultComboColours[1];
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit()));
- AddAssert("correct hit lighting colour", () =>
- catcher.ChildrenOfType().First()?.Entry?.ObjectColour == fruitColour);
+ AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType().First()?.Entry?.ObjectColour == this.ChildrenOfType().First().AccentColour.Value);
}
[Test]
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs
new file mode 100644
index 0000000000..c24ba6d530
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs
@@ -0,0 +1,102 @@
+// 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.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModRandom : OsuModTestScene
+ {
+ [TestCase(1)]
+ [TestCase(7)]
+ [TestCase(10)]
+ public void TestDefaultBeatmap(float angleSharpness) => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModRandom
+ {
+ AngleSharpness = { Value = angleSharpness }
+ },
+ Autoplay = true,
+ PassCondition = () => true
+ });
+
+ [TestCase(1)]
+ [TestCase(7)]
+ [TestCase(10)]
+ public void TestJumpBeatmap(float angleSharpness) => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModRandom
+ {
+ AngleSharpness = { Value = angleSharpness }
+ },
+ Beatmap = jumpBeatmap,
+ Autoplay = true,
+ PassCondition = () => true
+ });
+
+ [TestCase(1)]
+ [TestCase(7)]
+ [TestCase(10)]
+ public void TestStreamBeatmap(float angleSharpness) => CreateModTest(new ModTestData
+ {
+ Mod = new OsuModRandom
+ {
+ AngleSharpness = { Value = angleSharpness }
+ },
+ Beatmap = streamBeatmap,
+ Autoplay = true,
+ PassCondition = () => true
+ });
+
+ private OsuBeatmap jumpBeatmap =>
+ createHitCircleBeatmap(new[] { 100, 200, 300, 400 }, 8, 300, 2 * 300);
+
+ private OsuBeatmap streamBeatmap =>
+ createHitCircleBeatmap(new[] { 10, 20, 30, 40, 50, 60, 70, 80 }, 16, 150, 4 * 150);
+
+ private OsuBeatmap createHitCircleBeatmap(IEnumerable spacings, int objectsPerSpacing, int interval, int beatLength)
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.Add(0, new TimingControlPoint
+ {
+ Time = 0,
+ BeatLength = beatLength
+ });
+
+ var beatmap = new OsuBeatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ StackLeniency = 0,
+ Difficulty = new BeatmapDifficulty
+ {
+ ApproachRate = 8.5f
+ }
+ },
+ ControlPointInfo = controlPointInfo
+ };
+
+ foreach (int spacing in spacings)
+ {
+ for (int i = 0; i < objectsPerSpacing; i++)
+ {
+ beatmap.HitObjects.Add(new HitCircle
+ {
+ StartTime = interval * beatmap.HitObjects.Count,
+ Position = beatmap.HitObjects.Count % 2 == 0 ? Vector2.Zero : new Vector2(spacing, 0),
+ NewCombo = i == 0
+ });
+ }
+ }
+
+ return beatmap;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs
new file mode 100644
index 0000000000..446f3c83ae
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs
@@ -0,0 +1,80 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Argon
+{
+ public class ArgonCursor : OsuCursorSprite
+ {
+ public ArgonCursor()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ InternalChildren = new[]
+ {
+ ExpandTarget = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = 6,
+ BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.4f,
+ Colour = Colour4.FromHex("FC618F").Darken(0.6f),
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = 2,
+ BorderColour = Color4.White.Opacity(0.8f),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ },
+ },
+ },
+ },
+ new Circle
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Scale = new Vector2(0.2f),
+ Colour = new Color4(255, 255, 255, 255),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 20,
+ Colour = new Color4(171, 255, 255, 100),
+ },
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs
new file mode 100644
index 0000000000..9bb3122a3b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs
@@ -0,0 +1,29 @@
+// 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.Textures;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Argon
+{
+ public class ArgonCursorTrail : CursorTrail
+ {
+ protected override float IntervalMultiplier => 0.4f;
+
+ protected override float FadeExponent => 4;
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get(@"Cursor/cursortrail");
+ Scale = new Vector2(0.8f / Texture.ScaleAdjust);
+
+ Blending = BlendingParameters.Additive;
+
+ Alpha = 0.8f;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs
new file mode 100644
index 0000000000..47dae3c30a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs
@@ -0,0 +1,39 @@
+// 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.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Argon
+{
+ public class ArgonFollowPoint : CompositeDrawable
+ {
+ public ArgonFollowPoint()
+ {
+ Blending = BlendingParameters.Additive;
+
+ Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41"));
+ AutoSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new SpriteIcon
+ {
+ Icon = FontAwesome.Solid.ChevronRight,
+ Size = new Vector2(8),
+ Colour = OsuColour.Gray(0.2f),
+ },
+ new SpriteIcon
+ {
+ Icon = FontAwesome.Solid.ChevronRight,
+ Size = new Vector2(8),
+ X = 4,
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
index 0479b3ff3e..ffdcba3cdb 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
@@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
const double fade_out_time = 800;
const double flash_in_duration = 150;
- const double resize_duration = 300;
+ const double resize_duration = 400;
const float shrink_size = 0.8f;
@@ -165,13 +165,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
// The outer gradient is resize with a slight delay from the border.
// This is to give it a bomb-like effect, with the border "triggering" its animation when getting close.
using (BeginDelayedSequence(flash_in_duration / 12))
+ {
outerGradient.ResizeTo(outerGradient.Size * shrink_size, resize_duration, Easing.OutElasticHalf);
+ outerGradient
+ .FadeColour(Color4.White, 80)
+ .Then()
+ .FadeOut(flash_in_duration);
+ }
// The flash layer starts white to give the wanted brightness, but is almost immediately
// recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
// but works well enough with the colour fade.
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
- flash.FlashColour(Color4.White, flash_in_duration, Easing.OutQuint);
+ flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
this.FadeOut(fade_out_time, Easing.OutQuad);
break;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
index ba79d94937..7bc6723afb 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
@@ -47,6 +47,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
case OsuSkinComponents.ReverseArrow:
return new ArgonReverseArrow();
+
+ case OsuSkinComponents.FollowPoint:
+ return new ArgonFollowPoint();
+
+ case OsuSkinComponents.Cursor:
+ return new ArgonCursor();
+
+ case OsuSkinComponents.CursorTrail:
+ return new ArgonCursorTrail();
}
break;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index f1da492c05..59c0af1533 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -24,6 +24,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Framework.Screens;
@@ -187,7 +188,8 @@ namespace osu.Game
{
this.args = args;
- forwardLoggedErrorsToNotifications();
+ forwardGeneralLogsToNotifications();
+ forwardTabletLogsToNotifications();
SentryLogger = new SentryLogger(this);
}
@@ -994,7 +996,7 @@ namespace osu.Game
overlay.Depth = (float)-Clock.CurrentTime;
}
- private void forwardLoggedErrorsToNotifications()
+ private void forwardGeneralLogsToNotifications()
{
int recentLogCount = 0;
@@ -1002,7 +1004,7 @@ namespace osu.Game
Logger.NewEntry += entry =>
{
- if (entry.Level < LogLevel.Important || entry.Target == null) return;
+ if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return;
const int short_term_display_limit = 3;
@@ -1035,6 +1037,52 @@ namespace osu.Game
};
}
+ private void forwardTabletLogsToNotifications()
+ {
+ const string tablet_prefix = @"[Tablet] ";
+ bool notifyOnWarning = true;
+
+ Logger.NewEntry += entry =>
+ {
+ if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(tablet_prefix, StringComparison.OrdinalIgnoreCase))
+ return;
+
+ string message = entry.Message.Replace(tablet_prefix, string.Empty);
+
+ if (entry.Level == LogLevel.Error)
+ {
+ Schedule(() => Notifications.Post(new SimpleNotification
+ {
+ Text = $"Encountered tablet error: \"{message}\"",
+ Icon = FontAwesome.Solid.PenSquare,
+ IconColour = Colours.RedDark,
+ }));
+ }
+ else if (notifyOnWarning)
+ {
+ Schedule(() => Notifications.Post(new SimpleNotification
+ {
+ Text = @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.",
+ Icon = FontAwesome.Solid.PenSquare,
+ IconColour = Colours.YellowDark,
+ Activated = () =>
+ {
+ OpenUrlExternally("https://opentabletdriver.net/Tablets", true);
+ return true;
+ }
+ }));
+
+ notifyOnWarning = false;
+ }
+ };
+
+ Schedule(() =>
+ {
+ ITabletHandler tablet = Host.AvailableInputHandlers.OfType().SingleOrDefault();
+ tablet?.Tablet.BindValueChanged(_ => notifyOnWarning = true, true);
+ });
+ }
+
private Task asyncLoadStream;
///
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 2d32fe398a..478f154d58 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -125,6 +125,8 @@ namespace osu.Game
protected SessionStatics SessionStatics { get; private set; }
+ protected OsuColour Colours { get; private set; }
+
protected BeatmapManager BeatmapManager { get; private set; }
protected BeatmapModelDownloader BeatmapDownloader { get; private set; }
@@ -311,7 +313,7 @@ namespace osu.Game
dependencies.CacheAs(powerStatus);
dependencies.Cache(SessionStatics = new SessionStatics());
- dependencies.Cache(new OsuColour());
+ dependencies.Cache(Colours = new OsuColour());
RegisterImportHandler(BeatmapManager);
RegisterImportHandler(ScoreManager);
diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs
index 1dba60fb5f..f3bb6a0578 100644
--- a/osu.Game/Overlays/Notifications/SimpleNotification.cs
+++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs
@@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
@@ -41,6 +42,12 @@ namespace osu.Game.Overlays.Notifications
}
}
+ public ColourInfo IconColour
+ {
+ get => IconContent.Colour;
+ set => IconContent.Colour = value;
+ }
+
private TextFlowContainer? textDrawable;
private SpriteIcon? iconDrawable;
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 2c17b28b01..010e2175e1 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -44,6 +44,28 @@ namespace osu.Game.Skinning
: base(skin, resources)
{
this.resources = resources;
+
+ Configuration.CustomComboColours = new List
+ {
+ // Standard combo progression order is green - blue - red - yellow.
+ // But for whatever reason, this starts from index 1, not 0.
+ //
+ // We've added two new combo colours in argon, so to ensure the initial rotation matches,
+ // this same progression is in slots 1 - 4.
+
+ // Orange
+ new Color4(241, 116, 0, 255),
+ // Green
+ new Color4(0, 241, 53, 255),
+ // Blue
+ new Color4(0, 82, 241, 255),
+ // Red
+ new Color4(241, 0, 0, 255),
+ // Yellow
+ new Color4(232, 235, 0, 255),
+ // Purple
+ new Color4(92, 0, 241, 255),
+ };
}
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 29e690a024..c4e2a5168e 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -35,7 +35,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 83410b08f6..622efcd63d 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,7 +61,7 @@
-
+
@@ -82,7 +82,7 @@
-
+