diff --git a/global.json b/global.json
index 9aa5b6192b..233a040d18 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.50"
+ "Microsoft.Build.Traversal": "2.0.52"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 71d4e5aacf..7e6f1469f5 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index cd31df316a..2079f136d2 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -16,6 +16,7 @@ using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Updater;
+using osu.Desktop.Windows;
namespace osu.Desktop
{
@@ -98,6 +99,9 @@ namespace osu.Desktop
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
LoadComponentAsync(new DiscordRichPresence(), Add);
+
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
+ LoadComponentAsync(new GameplayWinKeyBlocker(), Add);
}
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
new file mode 100644
index 0000000000..86174ceb90
--- /dev/null
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.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 osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Platform;
+using osu.Game.Configuration;
+
+namespace osu.Desktop.Windows
+{
+ public class GameplayWinKeyBlocker : Component
+ {
+ private Bindable allowScreenSuspension;
+ private Bindable disableWinKey;
+
+ private GameHost host;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, OsuConfigManager config)
+ {
+ this.host = host;
+
+ allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy();
+ allowScreenSuspension.BindValueChanged(_ => updateBlocking());
+
+ disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey);
+ disableWinKey.BindValueChanged(_ => updateBlocking(), true);
+ }
+
+ private void updateBlocking()
+ {
+ bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value;
+
+ if (shouldDisable)
+ host.InputThread.Scheduler.Add(WindowsKey.Disable);
+ else
+ host.InputThread.Scheduler.Add(WindowsKey.Enable);
+ }
+ }
+}
diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs
new file mode 100644
index 0000000000..f19d741107
--- /dev/null
+++ b/osu.Desktop/Windows/WindowsKey.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 System;
+using System.Runtime.InteropServices;
+
+namespace osu.Desktop.Windows
+{
+ internal class WindowsKey
+ {
+ private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam);
+
+ private static bool isBlocked;
+
+ private const int wh_keyboard_ll = 13;
+ private const int wm_keydown = 256;
+ private const int wm_syskeyup = 261;
+
+ //Resharper disable once NotAccessedField.Local
+ private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC
+ private static IntPtr keyHook;
+
+ [StructLayout(LayoutKind.Explicit)]
+ private readonly struct KdDllHookStruct
+ {
+ [FieldOffset(0)]
+ public readonly int VkCode;
+
+ [FieldOffset(8)]
+ public readonly int Flags;
+ }
+
+ private static int lowLevelKeyboardProc(int nCode, int wParam, ref KdDllHookStruct lParam)
+ {
+ if (wParam >= wm_keydown && wParam <= wm_syskeyup)
+ {
+ switch (lParam.VkCode)
+ {
+ case 0x5B: // left windows key
+ case 0x5C: // right windows key
+ return 1;
+ }
+ }
+
+ return callNextHookEx(0, nCode, wParam, ref lParam);
+ }
+
+ internal static void Disable()
+ {
+ if (keyHook != IntPtr.Zero || isBlocked)
+ return;
+
+ keyHook = setWindowsHookEx(wh_keyboard_ll, (keyboardHookDelegate = lowLevelKeyboardProc), Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0);
+
+ isBlocked = true;
+ }
+
+ internal static void Enable()
+ {
+ if (keyHook == IntPtr.Zero || !isBlocked)
+ return;
+
+ keyHook = unhookWindowsHookEx(keyHook);
+ keyboardHookDelegate = null;
+
+ keyHook = IntPtr.Zero;
+
+ isBlocked = false;
+ }
+
+ [DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")]
+ private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId);
+
+ [DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")]
+ private static extern IntPtr unhookWindowsHookEx(IntPtr hHook);
+
+ [DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")]
+ private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
index 3e06e78dba..c1b7214d72 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
// We only care about testing misses, hits are tested via JuiceStream
- [TestCase(true)]
+ [TestCase(false)]
public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index fbb22a8498..b4f123598b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
@@ -25,6 +26,11 @@ namespace osu.Game.Rulesets.Catch.Tests
{
private RulesetInfo catchRuleset;
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ private Catcher catcher => this.ChildrenOfType().First().MovableCatcher;
+
public TestSceneCatcherArea()
{
AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
@@ -34,24 +40,43 @@ namespace osu.Game.Rulesets.Catch.Tests
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
{
- X = this.ChildrenOfType().First().MovableCatcher.X
+ X = catcher.X
}), 20);
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
{
- X = this.ChildrenOfType().First().MovableCatcher.X,
+ X = catcher.X,
LastInCombo = true,
}), 20);
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
{
- X = this.ChildrenOfType().First().MovableCatcher.X,
+ X = catcher.X
}), 20);
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
{
- X = this.ChildrenOfType().First().MovableCatcher.X + 100,
+ X = catcher.X + 100,
LastInCombo = true,
}, true), 20);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestHitLighting(bool enable)
+ {
+ AddStep("create catcher", () => createCatcher(5));
+
+ AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
+ AddStep("catch fruit", () => catchFruit(new TestFruit(false)
+ {
+ X = catcher.X
+ }));
+ AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
+ {
+ X = catcher.X,
+ LastInCombo = true
+ }));
+ AddAssert("check hit explosion", () => catcher.ChildrenOfType().Any() == enable);
+ }
+
private void catchFruit(Fruit fruit, bool miss = false)
{
this.ChildrenOfType().ForEach(area =>
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index 2ee7cea645..d700f79e5b 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -78,7 +78,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (mods.Any(m => m is ModHidden))
{
- value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
// Hiddens gives almost nothing on max approach rate, and more the lower it is
if (approachRate <= 10.0)
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
index e3391c47f1..fb92399102 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs
@@ -1,17 +1,11 @@
// 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.Catch.Judgements;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModPerfect : ModPerfect
{
- protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
- => !(result.Judgement is CatchBananaJudgement)
- && base.FailCondition(healthProcessor, result);
}
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index f122588a2b..99d899db80 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -35,18 +35,15 @@ namespace osu.Game.Rulesets.Catch.Replays
}
}
- public override List GetPendingInputs()
+ public override void CollectPendingInputs(List inputs)
{
- if (!Position.HasValue) return new List();
+ if (!Position.HasValue) return;
- return new List
+ inputs.Add(new CatchReplayState
{
- new CatchReplayState
- {
- PressedActions = CurrentFrame?.Actions ?? new List(),
- CatcherX = Position.Value
- },
- };
+ PressedActions = CurrentFrame?.Actions ?? new List(),
+ CatcherX = Position.Value
+ });
}
public class CatchReplayState : ReplayState
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index b41fd24a9c..8820dff730 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -5,12 +5,14 @@ using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
@@ -103,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.UI
private double hyperDashModifier = 1;
private int hyperDashDirection;
private float hyperDashTargetPosition;
+ private Bindable hitLighting;
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
{
@@ -118,8 +121,10 @@ namespace osu.Game.Rulesets.Catch.UI
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OsuConfigManager config)
{
+ hitLighting = config.GetBindable(OsuSetting.HitLighting);
+
InternalChildren = new Drawable[]
{
caughtFruitContainer,
@@ -194,11 +199,14 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruitContainer.Add(fruit);
- AddInternal(new HitExplosion(fruit)
+ if (hitLighting.Value)
{
- X = fruit.X,
- Scale = new Vector2(fruit.HitObject.Scale)
- });
+ AddInternal(new HitExplosion(fruit)
+ {
+ X = fruit.X,
+ Scale = new Vector2(fruit.HitObject.Scale)
+ });
+ }
}
///
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 0d13b85901..95072cf4f8 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -2,6 +2,7 @@
// 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.Screens;
using osu.Game.Beatmaps;
@@ -10,6 +11,8 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@@ -236,6 +239,53 @@ namespace osu.Game.Rulesets.Mania.Tests
assertTailJudgement(HitResult.Meh);
}
+ [Test]
+ public void TestMissReleaseAndHitSecondRelease()
+ {
+ var windows = new ManiaHitWindows();
+ windows.SetDifficulty(10);
+
+ var beatmap = new Beatmap
+ {
+ HitObjects =
+ {
+ new HoldNote
+ {
+ StartTime = 1000,
+ Duration = 500,
+ Column = 0,
+ },
+ new HoldNote
+ {
+ StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10,
+ Duration = 500,
+ Column = 0,
+ },
+ },
+ BeatmapInfo =
+ {
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ SliderTickRate = 4,
+ OverallDifficulty = 10,
+ },
+ Ruleset = new ManiaRuleset().RulesetInfo
+ },
+ };
+
+ performTest(new List
+ {
+ new ManiaReplayFrame(beatmap.HitObjects[1].StartTime, ManiaAction.Key1),
+ new ManiaReplayFrame(beatmap.HitObjects[1].GetEndTime()),
+ }, beatmap);
+
+ AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
+ .All(j => j.Type == HitResult.Miss));
+
+ AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
+ .All(j => j.Type == HitResult.Perfect));
+ }
+
private void assertHeadJudgement(HitResult result)
=> AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
@@ -250,11 +300,11 @@ namespace osu.Game.Rulesets.Mania.Tests
private ScoreAccessibleReplayPlayer currentPlayer;
- private void performTest(List frames)
+ private void performTest(List frames, Beatmap beatmap = null)
{
- AddStep("load player", () =>
+ if (beatmap == null)
{
- Beatmap.Value = CreateWorkingBeatmap(new Beatmap
+ beatmap = new Beatmap
{
HitObjects =
{
@@ -270,9 +320,14 @@ namespace osu.Game.Rulesets.Mania.Tests
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
Ruleset = new ManiaRuleset().RulesetInfo
},
- });
+ };
- Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
+ beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
+ }
+
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(beatmap);
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs
new file mode 100644
index 0000000000..8698ba3abd
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.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 NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+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
+{
+ public class TestScenePlayfieldCoveringContainer : OsuTestScene
+ {
+ private readonly ScrollingTestContainer scrollingContainer;
+ private readonly PlayfieldCoveringWrapper cover;
+
+ public TestScenePlayfieldCoveringContainer()
+ {
+ Child = scrollingContainer = new ScrollingTestContainer(ScrollingDirection.Down)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(300, 500),
+ Child = cover = new PlayfieldCoveringWrapper(new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Orange
+ })
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ }
+
+ [Test]
+ public void TestScrollingDownwards()
+ {
+ AddStep("set down scroll", () => scrollingContainer.Direction = ScrollingDirection.Down);
+ AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
+ AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
+ AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
+ }
+
+ [Test]
+ public void TestScrollingUpwards()
+ {
+ AddStep("set up scroll", () => scrollingContainer.Direction = ScrollingDirection.Up);
+ AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
+ AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
+ AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
index 53db676a54..53967ffa05 100644
--- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
return 300;
case HitResult.Perfect:
- return 320;
+ return 350;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
index bdc8cb31e5..cbdcd49c5b 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModHidden;
public override string Description => @"Keys appear out of nowhere!";
+
+ protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
index 66b90984b4..4bdb15526f 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
@@ -2,15 +2,44 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModHidden : ModHidden
+ public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset
{
public override string Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
+
+ ///
+ /// The direction in which the cover should expand.
+ ///
+ protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
+
+ public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
+
+ foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
+ {
+ HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
+ Container hocParent = (Container)hoc.Parent;
+
+ hocParent.Remove(hoc);
+ hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
+ {
+ c.RelativeSizeAxes = Axes.Both;
+ c.Direction = ExpandDirection;
+ c.Coverage = 0.5f;
+ }));
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 2262bd2b7d..0c5289efe1 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -167,6 +167,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value)
return false;
+ // The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed).
+ // But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time.
+ // Note: Unlike below, we use the tail's start time to determine the time offset.
+ if (Time.Current > Tail.HitObject.StartTime && !Tail.HitObject.HitWindows.CanBeHit(Time.Current - Tail.HitObject.StartTime))
+ return false;
+
beginHoldAt(Time.Current - Head.HitObject.StartTime);
Head.UpdateResult();
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index 899718b77e..aa0c148caf 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
- public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } };
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index ba84c21845..4b2f643333 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -7,9 +7,9 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
internal class ManiaScoreProcessor : ScoreProcessor
{
- protected override double DefaultAccuracyPortion => 0.8;
+ protected override double DefaultAccuracyPortion => 0.95;
- protected override double DefaultComboPortion => 0.2;
+ protected override double DefaultComboPortion => 0.05;
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
index a749f80855..9f716428c0 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
?? $"mania-note{FallbackColumnIndex}L";
- sprite = skin.GetAnimation(imageName, true, true).With(d =>
+ sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
{
if (d == null)
return;
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
index 515c941d65..283b04373b 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.UI.Scrolling;
@@ -92,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
string noteImage = GetColumnSkinConfig(skin, lookup)?.Value
?? $"mania-note{FallbackColumnIndex}{suffix}";
- return skin.GetTexture(noteImage);
+ return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 511d6c8623..642353bd0b 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable Action = new Bindable();
- private readonly ColumnHitObjectArea hitObjectArea;
+ public readonly ColumnHitObjectArea HitObjectArea;
internal readonly Container TopLevelContainer;
- public Container UnderlayElements => hitObjectArea.UnderlayElements;
+ public Container UnderlayElements => HitObjectArea.UnderlayElements;
public Column(int index)
{
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
- hitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
+ HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
- TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy());
+ TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
}
public override Axes RelativeSizeAxes => Axes.Y;
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both
};
- hitObjectArea.Explosions.Add(explosion);
+ HitObjectArea.Explosions.Add(explosion);
explosion.Delay(200).Expire(true);
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs
index ba5281a1a2..8f7880dafa 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -14,12 +15,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components
public class HitObjectArea : SkinReloadableDrawable
{
protected readonly IBindable Direction = new Bindable();
+ public readonly HitObjectContainer HitObjectContainer;
public HitObjectArea(HitObjectContainer hitObjectContainer)
{
- InternalChildren = new[]
+ InternalChild = new Container
{
- hitObjectContainer,
+ RelativeSizeAxes = Axes.Both,
+ Child = HitObjectContainer = hitObjectContainer
};
}
diff --git a/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs
new file mode 100644
index 0000000000..15d216e8c5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs
@@ -0,0 +1,133 @@
+// 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.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ ///
+ /// A that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield.
+ ///
+ public class PlayfieldCoveringWrapper : CompositeDrawable
+ {
+ ///
+ /// The complete cover, including gradient and fill.
+ ///
+ private readonly Drawable cover;
+
+ ///
+ /// The gradient portion of the cover.
+ ///
+ private readonly Box gradient;
+
+ ///
+ /// The fully-opaque portion of the cover.
+ ///
+ private readonly Box filled;
+
+ private readonly IBindable scrollDirection = new Bindable();
+
+ public PlayfieldCoveringWrapper(Drawable content)
+ {
+ InternalChild = new BufferedContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ content,
+ cover = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = new BlendingParameters
+ {
+ // Don't change the destination colour.
+ RGBEquation = BlendingEquation.Add,
+ Source = BlendingType.Zero,
+ Destination = BlendingType.One,
+ // Subtract the cover's alpha from the destination (points with alpha 1 should make the destination completely transparent).
+ AlphaEquation = BlendingEquation.Add,
+ SourceAlpha = BlendingType.Zero,
+ DestinationAlpha = BlendingType.OneMinusSrcAlpha
+ },
+ Children = new Drawable[]
+ {
+ gradient = new Box
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.Both,
+ RelativePositionAxes = Axes.Both,
+ Height = 0.25f,
+ Colour = ColourInfo.GradientVertical(
+ Color4.White.Opacity(0f),
+ Color4.White.Opacity(1f)
+ )
+ },
+ filled = new Box
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.Both,
+ Height = 0
+ }
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ scrollDirection.BindTo(scrollingInfo.Direction);
+ scrollDirection.BindValueChanged(onScrollDirectionChanged, true);
+ }
+
+ private void onScrollDirectionChanged(ValueChangedEvent direction)
+ => cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f;
+
+ ///
+ /// The relative area that should be completely covered. This does not include the fade.
+ ///
+ public float Coverage
+ {
+ set
+ {
+ filled.Height = value;
+ gradient.Y = -value;
+ }
+ }
+
+ ///
+ /// The direction in which the cover expands.
+ ///
+ public CoverExpandDirection Direction
+ {
+ set => cover.Scale = value == CoverExpandDirection.AlongScroll ? Vector2.One : new Vector2(1, -1);
+ }
+ }
+
+ public enum CoverExpandDirection
+ {
+ ///
+ /// The cover expands along the scrolling direction.
+ ///
+ AlongScroll,
+
+ ///
+ /// The cover expands against the scrolling direction.
+ ///
+ AgainstScroll
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
index 46769f65fe..dde02e873b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -8,6 +8,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing.Input;
using osu.Game.Audio;
@@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
- public Texture GetTexture(string componentName)
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
switch (componentName)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index f08f994b07..646f12f710 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -4,62 +4,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
+using osu.Framework.Testing;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneDrawableJudgement : OsuSkinnableTestScene
{
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ private readonly List> pools;
+
public TestSceneDrawableJudgement()
{
- var pools = new List>();
+ pools = new List>();
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ showResult(result);
+ }
+
+ [Test]
+ public void TestHitLightingDisabled()
+ {
+ AddStep("hit lighting disabled", () => config.Set(OsuSetting.HitLighting, false));
+
+ showResult(HitResult.Great);
+
+ AddUntilStep("judgements shown", () => this.ChildrenOfType().Any());
+ AddAssert("judgement body immediately visible",
+ () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha == 1));
+ AddAssert("hit lighting hidden",
+ () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0));
+ }
+
+ [Test]
+ public void TestHitLightingEnabled()
+ {
+ AddStep("hit lighting enabled", () => config.Set(OsuSetting.HitLighting, true));
+
+ showResult(HitResult.Great);
+
+ AddUntilStep("judgements shown", () => this.ChildrenOfType().Any());
+ AddAssert("judgement body not immediately visible",
+ () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1));
+ AddAssert("hit lighting shown",
+ () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha > 0));
+ }
+
+ private void showResult(HitResult result)
+ {
+ AddStep("Show " + result.GetDescription(), () =>
{
- AddStep("Show " + result.GetDescription(), () =>
+ int poolIndex = 0;
+
+ SetContents(() =>
{
- int poolIndex = 0;
+ DrawablePool pool;
- SetContents(() =>
+ if (poolIndex >= pools.Count)
+ pools.Add(pool = new DrawablePool(1));
+ else
{
- DrawablePool pool;
+ pool = pools[poolIndex];
- if (poolIndex >= pools.Count)
- pools.Add(pool = new DrawablePool(1));
- else
+ // We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent.
+ ((Container)pool.Parent).Clear(false);
+ }
+
+ var container = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- pool = pools[poolIndex];
-
- // We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent.
- ((Container)pool.Parent).Clear(false);
- }
-
- var container = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ pool,
+ pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j =>
{
- pool,
- pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j =>
- {
- j.Anchor = Anchor.Centre;
- j.Origin = Anchor.Centre;
- })
- }
- };
+ j.Anchor = Anchor.Centre;
+ j.Origin = Anchor.Centre;
+ })
+ }
+ };
- poolIndex++;
- return container;
- });
+ poolIndex++;
+ return container;
});
- }
+ });
+ }
+
+ private class TestDrawableOsuJudgement : DrawableOsuJudgement
+ {
+ public new SkinnableSprite Lighting => base.Lighting;
+ public new Container JudgementBody => base.JudgementBody;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
index c3b4d2625e..854626d362 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
@@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests
const double time_slider = 1500;
const double time_circle = 1510;
Vector2 positionCircle = Vector2.Zero;
- Vector2 positionSlider = new Vector2(80);
+ Vector2 positionSlider = new Vector2(30);
var hitObjects = new List
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index b357e20ee8..075bf314bc 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -9,6 +9,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Timing;
@@ -131,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests
};
}
- public Texture GetTexture(string componentName) => null;
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index 8cb7f3f4b6..67afc45e32 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void testSingle(float circleSize, bool auto = false)
{
- var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
+ var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + 5000 };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index 6b1394d799..c36bec391f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -1,26 +1,28 @@
// 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.Framework.Audio;
-using osu.Framework.Utils;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Timing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Replays;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osuTK;
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Replays;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Storyboards;
+using osu.Game.Tests.Visual;
+using osuTK;
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
@@ -34,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override bool Autoplay => true;
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
+
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
@@ -129,18 +133,44 @@ namespace osu.Game.Rulesets.Osu.Tests
.ToList()
};
+ [Test]
+ public void TestSpinnerNormalBonusRewinding()
+ {
+ addSeekStep(1000);
+
+ AddAssert("player score matching expected bonus score", () =>
+ {
+ // multipled by 2 to nullify the score multiplier. (autoplay mod selected)
+ var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
+ return totalScore == (int)(drawableSpinner.Disc.CumulativeRotation / 360) * SpinnerTick.SCORE_PER_TICK;
+ });
+
+ addSeekStep(0);
+
+ AddAssert("player score is 0", () => ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value == 0);
+ }
+
+ [Test]
+ public void TestSpinnerCompleteBonusRewinding()
+ {
+ addSeekStep(2500);
+ addSeekStep(0);
+
+ AddAssert("player score is 0", () => ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value == 0);
+ }
+
[Test]
public void TestSpinPerMinuteOnRewind()
{
double estimatedSpm = 0;
- addSeekStep(2500);
+ addSeekStep(1000);
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
- addSeekStep(5000);
+ addSeekStep(2000);
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
- addSeekStep(2500);
+ addSeekStep(1000);
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
}
@@ -160,12 +190,17 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192),
EndTime = 6000,
},
- // placeholder object to avoid hitting the results screen
- new HitCircle
- {
- StartTime = 99999,
- }
}
};
+
+ private class ScoreExposedPlayer : TestPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ public ScoreExposedPlayer()
+ : base(false, false)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index d75f4c70d7..2263e2b2f4 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -30,6 +31,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private OsuInputManager inputManager;
+ private GameplayClock gameplayClock;
+
private List replayFrames;
private int currentFrame;
@@ -38,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
if (currentFrame == replayFrames.Count - 1) return;
- double time = playfield.Time.Current;
+ double time = gameplayClock.CurrentTime;
// Very naive implementation of autopilot based on proximity to replay frames.
// TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
@@ -53,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
+ gameplayClock = drawableRuleset.FrameStableClock;
+
// Grab the input manager to disable the user's cursor, and for future use
inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
inputManager.AllowUserCursorMovement = false;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
index 73cb483ef0..ee6a7815e2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
@@ -1,7 +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.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -15,6 +17,14 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Hit them at the right size!";
- protected override float StartScale => 2f;
+ [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
+ public override BindableNumber StartScale { get; } = new BindableFloat
+ {
+ MinValue = 1f,
+ MaxValue = 25f,
+ Default = 2f,
+ Value = 2f,
+ Precision = 0.1f,
+ };
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index f08d4e8f5e..182d6eeb4b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -1,7 +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.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -15,6 +17,14 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Hit them at the right size!";
- protected override float StartScale => 0.5f;
+ [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
+ public override BindableNumber StartScale { get; } = new BindableFloat
+ {
+ MinValue = 0f,
+ MaxValue = 0.99f,
+ Default = 0.5f,
+ Value = 0.5f,
+ Precision = 0.01f,
+ };
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 42ddddc4dd..06ba4cde4a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
- protected virtual float StartScale => 1;
+ public abstract BindableNumber StartScale { get; }
protected virtual float EndScale => 1;
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods
case DrawableHitCircle _:
{
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
- drawable.ScaleTo(StartScale).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
+ drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
index 1493ddfcf3..012d9f8878 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
@@ -16,9 +16,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableOsuJudgement : DrawableJudgement
{
- private SkinnableSprite lighting;
+ protected SkinnableSprite Lighting;
+
private Bindable lightingColour;
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(result, judgedObject)
{
@@ -29,18 +33,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
+ private void load()
{
- if (config.Get(OsuSetting.HitLighting))
+ AddInternal(Lighting = new SkinnableSprite("lighting")
{
- AddInternal(lighting = new SkinnableSprite("lighting")
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Blending = BlendingParameters.Additive,
- Depth = float.MaxValue
- });
- }
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Depth = float.MaxValue,
+ Alpha = 0
+ });
}
public override void Apply(JudgementResult result, DrawableHitObject judgedObject)
@@ -60,33 +62,39 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
lightingColour?.UnbindAll();
- if (lighting != null)
- {
- lighting.ResetAnimation();
+ Lighting.ResetAnimation();
- if (JudgedObject != null)
- {
- lightingColour = JudgedObject.AccentColour.GetBoundCopy();
- lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true);
- }
- else
- {
- lighting.Colour = Color4.White;
- }
+ if (JudgedObject != null)
+ {
+ lightingColour = JudgedObject.AccentColour.GetBoundCopy();
+ lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true);
+ }
+ else
+ {
+ Lighting.Colour = Color4.White;
}
}
- protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400;
+ private double fadeOutDelay;
+ protected override double FadeOutDelay => fadeOutDelay;
protected override void ApplyHitAnimations()
{
- if (lighting != null)
+ bool hitLightingEnabled = config.Get(OsuSetting.HitLighting);
+
+ if (hitLightingEnabled)
{
JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400);
- lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
- lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
+ Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
+ Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
}
+ else
+ {
+ JudgementBody.Alpha = 1;
+ }
+
+ fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay;
JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
base.ApplyHitAnimations();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 72502c02cd..07f40f763b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
@@ -11,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -81,6 +83,42 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
}, true);
+
+ Tracking.BindValueChanged(updateSlidingSample);
+ }
+
+ private SkinnableSound slidingSample;
+
+ protected override void LoadSamples()
+ {
+ base.LoadSamples();
+
+ slidingSample?.Expire();
+ slidingSample = null;
+
+ var firstSample = HitObject.Samples.FirstOrDefault();
+
+ if (firstSample != null)
+ {
+ var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
+ clone.Name = "sliderslide";
+
+ AddInternal(slidingSample = new SkinnableSound(clone)
+ {
+ Looping = true
+ });
+ }
+ }
+
+ private void updateSlidingSample(ValueChangedEvent tracking)
+ {
+ // note that samples will not start playing if exiting a seek operation in the middle of a slider.
+ // may be something we want to address at a later point, but not so easy to make happen right now
+ // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
+ if (tracking.NewValue && ShouldPlaySamples)
+ slidingSample?.Play();
+ else
+ slidingSample?.Stop();
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
@@ -156,6 +194,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking.Value = Ball.Tracking;
+ if (Tracking.Value && slidingSample != null)
+ // keep the sliding sample playing at the current tracking position
+ slidingSample.Balance.Value = CalculateSamplePlaybackBalance(Ball.X / OsuPlayfield.BASE_SIZE.X);
+
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index 720ffcd51c..d79ecb7b4e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly Drawable scaleContainer;
+ public override bool DisplayResult => false;
+
public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
: base(sliderRepeat)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index be6766509c..ecf78efdd9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -14,6 +14,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Objects;
using osu.Framework.Utils;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
@@ -24,9 +25,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
protected readonly Spinner Spinner;
+ private readonly Container ticks;
+
public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks;
public readonly SpinnerSpmCounter SpmCounter;
+ private readonly SpinnerBonusDisplay bonusDisplay;
private readonly Container mainContainer;
@@ -60,6 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
+ ticks = new Container(),
circleContainer = new Container
{
AutoSizeAxes = Axes.Both,
@@ -93,7 +98,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Background = new SpinnerBackground
{
- Alpha = 0.6f,
+ Disc =
+ {
+ Alpha = 0f,
+ },
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@@ -117,18 +125,56 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre,
Y = 120,
Alpha = 0
+ },
+ bonusDisplay = new SpinnerBonusDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Y = -120,
}
};
}
+ protected override void AddNestedHitObject(DrawableHitObject hitObject)
+ {
+ base.AddNestedHitObject(hitObject);
+
+ switch (hitObject)
+ {
+ case DrawableSpinnerTick tick:
+ ticks.Add(tick);
+ break;
+ }
+ }
+
+ protected override void ClearNestedHitObjects()
+ {
+ base.ClearNestedHitObjects();
+ ticks.Clear();
+ }
+
+ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case SpinnerBonusTick bonusTick:
+ return new DrawableSpinnerBonusTick(bonusTick);
+
+ case SpinnerTick tick:
+ return new DrawableSpinnerTick(tick);
+ }
+
+ return base.CreateNestedHitObject(hitObject);
+ }
+
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
normalColour = baseColour;
+ completeColour = colours.YellowLight;
Background.AccentColour = normalColour;
-
- completeColour = colours.YellowLight.Opacity(0.75f);
+ Ticks.AccentColour = normalColour;
Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark;
@@ -147,21 +193,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (Progress >= 1 && !Disc.Complete)
{
Disc.Complete = true;
-
- const float duration = 200;
-
- Disc.FadeAccent(completeColour, duration);
-
- Background.FadeAccent(completeColour, duration);
- Background.FadeOut(duration);
-
- circle.FadeColour(completeColour, duration);
- glow.FadeColour(completeColour, duration);
+ transformFillColour(completeColour, 200);
}
if (userTriggered || Time.Current < Spinner.EndTime)
return;
+ // Trigger a miss result for remaining ticks to avoid infinite gameplay.
+ foreach (var tick in ticks.Where(t => !t.IsHit))
+ tick.TriggerResult(false);
+
ApplyResult(r =>
{
if (Progress >= 1)
@@ -191,8 +232,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
+
SpmCounter.SetRotation(Disc.CumulativeRotation);
+ updateBonusScore();
+
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress;
Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
@@ -200,36 +244,95 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
+ private int wholeSpins;
+
+ private void updateBonusScore()
+ {
+ if (ticks.Count == 0)
+ return;
+
+ int spins = (int)(Disc.CumulativeRotation / 360);
+
+ if (spins < wholeSpins)
+ {
+ // rewinding, silently handle
+ wholeSpins = spins;
+ return;
+ }
+
+ while (wholeSpins != spins)
+ {
+ var tick = ticks.FirstOrDefault(t => !t.IsHit);
+
+ // tick may be null if we've hit the spin limit.
+ if (tick != null)
+ {
+ tick.TriggerResult(true);
+ if (tick is DrawableSpinnerBonusTick)
+ bonusDisplay.SetBonusCount(spins - Spinner.SpinsRequired);
+ }
+
+ wholeSpins++;
+ }
+ }
+
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
- circleContainer.ScaleTo(Spinner.Scale * 0.3f);
- circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint);
+ circleContainer.ScaleTo(0);
+ mainContainer.ScaleTo(0);
- mainContainer
- .ScaleTo(0)
- .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint)
- .Then()
- .ScaleTo(1, 500, Easing.OutQuint);
+ using (BeginDelayedSequence(HitObject.TimePreempt / 2, true))
+ {
+ float phaseOneScale = Spinner.Scale * 0.7f;
+
+ circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint);
+
+ mainContainer
+ .ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint)
+ .RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration);
+
+ using (BeginDelayedSequence(HitObject.TimePreempt / 2, true))
+ {
+ circleContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint);
+ mainContainer.ScaleTo(1, 400, Easing.OutQuint);
+ }
+ }
}
protected override void UpdateStateTransforms(ArmedState state)
{
base.UpdateStateTransforms(state);
- var sequence = this.Delay(Spinner.Duration).FadeOut(160);
-
- switch (state)
+ using (BeginDelayedSequence(Spinner.Duration, true))
{
- case ArmedState.Hit:
- sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
- break;
+ this.FadeOut(160);
- case ArmedState.Miss:
- sequence.ScaleTo(Scale * 0.8f, 320, Easing.In);
- break;
+ switch (state)
+ {
+ case ArmedState.Hit:
+ transformFillColour(completeColour, 0);
+ this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
+ mainContainer.RotateTo(mainContainer.Rotation + 180, 320);
+ break;
+
+ case ArmedState.Miss:
+ this.ScaleTo(Scale * 0.8f, 320, Easing.In);
+ break;
+ }
}
}
+
+ private void transformFillColour(Colour4 colour, double duration)
+ {
+ Disc.FadeAccent(colour, duration);
+
+ Background.FadeAccent(colour.Darken(1), duration);
+ Ticks.FadeAccent(colour, duration);
+
+ circle.FadeColour(colour, duration);
+ glow.FadeColour(colour, duration);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs
new file mode 100644
index 0000000000..2e1c07c4c6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerBonusTick.cs
@@ -0,0 +1,13 @@
+// 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.Objects.Drawables
+{
+ public class DrawableSpinnerBonusTick : DrawableSpinnerTick
+ {
+ public DrawableSpinnerBonusTick(SpinnerBonusTick spinnerTick)
+ : base(spinnerTick)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
new file mode 100644
index 0000000000..c390b673be
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.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 osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables
+{
+ public class DrawableSpinnerTick : DrawableOsuHitObject
+ {
+ public override bool DisplayResult => false;
+
+ public DrawableSpinnerTick(SpinnerTick spinnerTick)
+ : base(spinnerTick)
+ {
+ }
+
+ ///
+ /// Apply a judgement result.
+ ///
+ /// Whether this tick was reached.
+ internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : HitResult.Miss);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 395c76a233..07dc6021c9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private readonly Slider slider;
private readonly Drawable followCircle;
private readonly DrawableSlider drawableSlider;
- private readonly CircularContainer ball;
+ private readonly Drawable ball;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
@@ -54,19 +54,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Alpha = 0,
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
- ball = new CircularContainer
+ ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall())
{
- Masking = true,
- RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Alpha = 1,
- Child = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()),
- }
- }
+ Origin = Anchor.Centre,
+ },
};
}
@@ -187,12 +179,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return;
Position = newPos;
- Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
+ ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
lastPosition = newPos;
}
- private class FollowCircleContainer : Container
+ private class FollowCircleContainer : CircularContainer
{
public override bool HandlePositionalInput => true;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
index 77228e28af..944354abca 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
@@ -1,18 +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 osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SpinnerBackground : CircularContainer, IHasAccentColour
{
- protected Box Disc;
+ public readonly Box Disc;
public Color4 AccentColour
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs
new file mode 100644
index 0000000000..b499d7a92b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs
@@ -0,0 +1,44 @@
+// 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.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ ///
+ /// Shows incremental bonus score achieved for a spinner.
+ ///
+ public class SpinnerBonusDisplay : CompositeDrawable
+ {
+ private readonly OsuSpriteText bonusCounter;
+
+ public SpinnerBonusDisplay()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = bonusCounter = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.Numeric.With(size: 24),
+ Alpha = 0,
+ };
+ }
+
+ private int displayedCount;
+
+ public void SetBonusCount(int count)
+ {
+ if (displayedCount == count)
+ return;
+
+ displayedCount = count;
+ bonusCounter.Text = $"{SpinnerBonusTick.SCORE_PER_TICK * count}";
+ bonusCounter.FadeOutFromOne(1500);
+ bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
index 676cefb236..ba7e8eae6f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -9,10 +10,11 @@ using osu.Framework.Graphics.Effects;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
- public class SpinnerTicks : Container
+ public class SpinnerTicks : Container, IHasAccentColour
{
public SpinnerTicks()
{
@@ -20,28 +22,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
- const float count = 18;
+ const float count = 8;
for (float i = 0; i < count; i++)
{
Add(new Container
{
- Colour = Color4.Black,
Alpha = 0.4f,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 10,
- Colour = Color4.Gray.Opacity(0.2f),
- },
+ Blending = BlendingParameters.Additive,
RelativePositionAxes = Axes.Both,
Masking = true,
CornerRadius = 5,
Size = new Vector2(60, 10),
Origin = Anchor.Centre,
Position = new Vector2(
- 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f,
- 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f
+ 0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.83f,
+ 0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.83f
),
Rotation = -i / count * 360 + 90,
Children = new[]
@@ -54,5 +50,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
});
}
}
+
+ public Color4 AccentColour
+ {
+ get => Colour;
+ set
+ {
+ Colour = value;
+
+ foreach (var c in Children.OfType())
+ {
+ c.EdgeEffect =
+ new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 20,
+ Colour = value.Opacity(0.8f),
+ };
+ }
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 418375c090..619b49926e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -3,9 +3,9 @@
using System;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -26,14 +26,43 @@ namespace osu.Game.Rulesets.Osu.Objects
///
public int SpinsRequired { get; protected set; } = 1;
+ ///
+ /// Number of spins available to give bonus, beyond .
+ ///
+ public int MaximumBonusSpins { get; protected set; } = 1;
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
- SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
-
// spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
- SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6);
+ const double stable_matching_fudge = 0.6;
+
+ // close to 477rpm
+ const double maximum_rotations_per_second = 8;
+
+ double secondsDuration = Duration / 1000;
+
+ double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5);
+
+ SpinsRequired = (int)Math.Max(1, (secondsDuration * minimumRotationsPerSecond));
+ MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration);
+ }
+
+ protected override void CreateNestedHitObjects()
+ {
+ base.CreateNestedHitObjects();
+
+ int totalSpins = MaximumBonusSpins + SpinsRequired;
+
+ for (int i = 0; i < totalSpins; i++)
+ {
+ double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
+
+ AddNested(i < SpinsRequired
+ ? new SpinnerTick { StartTime = startTime }
+ : new SpinnerBonusTick { StartTime = startTime });
+ }
}
public override Judgement CreateJudgement() => new OsuJudgement();
diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs
new file mode 100644
index 0000000000..9c4b6f774f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs
@@ -0,0 +1,28 @@
+// 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.Audio;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Objects
+{
+ public class SpinnerBonusTick : SpinnerTick
+ {
+ public new const int SCORE_PER_TICK = 50;
+
+ public SpinnerBonusTick()
+ {
+ Samples.Add(new HitSampleInfo { Name = "spinnerbonus" });
+ }
+
+ public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement();
+
+ public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
+ {
+ protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK;
+
+ protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
new file mode 100644
index 0000000000..de3ae27e55
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.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.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Objects
+{
+ public class SpinnerTick : OsuHitObject
+ {
+ public const int SCORE_PER_TICK = 10;
+
+ public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public class OsuSpinnerTickJudgement : OsuJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK;
+
+ protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
index 9ab358ee12..3356a0fbe0 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Replays
///
protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
- protected const float SPIN_RADIUS = 50;
+ public const float SPIN_RADIUS = 50;
///
/// The time in ms between each ReplayFrame.
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
index b42e9ac187..cf48dc053f 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
@@ -36,19 +36,10 @@ namespace osu.Game.Rulesets.Osu.Replays
}
}
- public override List GetPendingInputs()
+ public override void CollectPendingInputs(List inputs)
{
- return new List
- {
- new MousePositionAbsoluteInput
- {
- Position = GamefieldToScreenSpace(Position ?? Vector2.Zero)
- },
- new ReplayState
- {
- PressedActions = CurrentFrame?.Actions ?? new List()
- }
- };
+ inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) });
+ inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
index b4ed75d97c..0f586034d5 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -15,6 +15,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
private readonly Drawable animationContent;
+ private Sprite layerNd;
+ private Sprite layerSpec;
+
public LegacySliderBall(Drawable animationContent)
{
this.animationContent = animationContent;
@@ -29,18 +32,37 @@ namespace osu.Game.Rulesets.Osu.Skinning
InternalChildren = new[]
{
- new Sprite
+ layerNd = new Sprite
{
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
Texture = skin.GetTexture("sliderb-nd"),
Colour = new Color4(5, 5, 5, 255),
},
- animationContent,
- new Sprite
+ animationContent.With(d =>
{
+ d.Anchor = Anchor.Centre;
+ d.Origin = Anchor.Centre;
+ }),
+ layerSpec = new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
Texture = skin.GetTexture("sliderb-spec"),
Blending = BlendingParameters.Additive,
},
};
}
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ //undo rotation on layers which should not be rotated.
+ float appliedRotation = Parent.Rotation;
+
+ layerNd.Rotation = -appliedRotation;
+ layerSpec.Rotation = -appliedRotation;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index 3e5758ca01..95ef2d58b1 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
case OsuSkinComponents.HitCircleText:
var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default";
- var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0;
+ var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2;
return !hasFont(font)
? null
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonclear.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonclear.png
new file mode 100644
index 0000000000..c5bcdbd3fc
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonclear.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonfail.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonfail.png
new file mode 100644
index 0000000000..39cf737285
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonfail.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonidle.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonidle.png
new file mode 100644
index 0000000000..4c3b2bfec9
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonidle.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonkiai.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonkiai.png
new file mode 100644
index 0000000000..7de00b5390
Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/pippidonkiai.png differ
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
index d200c44a02..47d8a5c012 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
@@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -36,6 +37,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
private TaikoScoreProcessor scoreProcessor;
private IEnumerable mascots => this.ChildrenOfType();
+
+ private IEnumerable animatedMascots =>
+ mascots.Where(mascot => mascot.ChildrenOfType().All(animation => animation.FrameCount > 0));
+
private IEnumerable playfields => this.ChildrenOfType();
[SetUp]
@@ -72,11 +77,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
AddStep("set clear state", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
AddStep("miss", () => mascots.ForEach(mascot => mascot.LastResult.Value = new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }));
- AddAssert("skins with animations remain in clear state", () => someMascotsIn(TaikoMascotAnimationState.Clear));
+ AddAssert("skins with animations remain in clear state", () => animatedMascotsIn(TaikoMascotAnimationState.Clear));
AddUntilStep("state reverts to fail", () => allMascotsIn(TaikoMascotAnimationState.Fail));
AddStep("set clear state again", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
- AddAssert("skins with animations change to clear", () => someMascotsIn(TaikoMascotAnimationState.Clear));
+ AddAssert("skins with animations change to clear", () => animatedMascotsIn(TaikoMascotAnimationState.Clear));
}
[Test]
@@ -111,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
- assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Fail);
+ assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle);
}
@@ -186,10 +191,18 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
private void assertStateAfterResult(JudgementResult judgementResult, TaikoMascotAnimationState expectedState)
{
- AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}",
- () => applyNewResult(judgementResult));
+ TaikoMascotAnimationState[] mascotStates = null;
- AddAssert($"state is {expectedState.ToString().ToLower()}", () => allMascotsIn(expectedState));
+ AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}",
+ () =>
+ {
+ applyNewResult(judgementResult);
+ // store the states as soon as possible, so that the delay between steps doesn't incorrectly fail the test
+ // due to not checking if the state changed quickly enough.
+ Schedule(() => mascotStates = animatedMascots.Select(mascot => mascot.State.Value).ToArray());
+ });
+
+ AddAssert($"state is {expectedState.ToString().ToLower()}", () => mascotStates.All(state => state == expectedState));
}
private void applyNewResult(JudgementResult judgementResult)
@@ -211,6 +224,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
}
private bool allMascotsIn(TaikoMascotAnimationState state) => mascots.All(d => d.State.Value == state);
- private bool someMascotsIn(TaikoMascotAnimationState state) => mascots.Any(d => d.State.Value == state);
+ private bool animatedMascotsIn(TaikoMascotAnimationState state) => animatedMascots.Any(d => d.State.Value == state);
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index aaa634648a..0be005e1c4 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
};
[Test]
- public void TestSpinnerDoesNotFail()
+ public void TestSpinnerDoesFail()
{
bool judged = false;
AddStep("Setup judgements", () =>
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Player.ScoreProcessor.NewJudgement += b => judged = true;
});
AddUntilStep("swell judged", () => judged);
- AddAssert("not failed", () => !Player.HasFailed);
+ AddAssert("failed", () => Player.HasFailed);
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs
new file mode 100644
index 0000000000..7c39c040b1
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs
@@ -0,0 +1,64 @@
+// 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.Graphics.Containers;
+using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko.Audio
+{
+ ///
+ /// Stores samples for the input drum.
+ /// The lifetime of the samples is adjusted so that they are only alive during the appropriate sample control point.
+ ///
+ public class DrumSampleContainer : LifetimeManagementContainer
+ {
+ private readonly ControlPointInfo controlPoints;
+ private readonly Dictionary mappings = new Dictionary();
+
+ public DrumSampleContainer(ControlPointInfo controlPoints)
+ {
+ this.controlPoints = controlPoints;
+
+ IReadOnlyList samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints;
+
+ for (int i = 0; i < samplePoints.Count; i++)
+ {
+ var samplePoint = samplePoints[i];
+
+ var centre = samplePoint.GetSampleInfo();
+ var rim = samplePoint.GetSampleInfo(HitSampleInfo.HIT_CLAP);
+
+ var lifetimeStart = i > 0 ? samplePoint.Time : double.MinValue;
+ var lifetimeEnd = i + 1 < samplePoints.Count ? samplePoints[i + 1].Time : double.MaxValue;
+
+ mappings[samplePoint.Time] = new DrumSample
+ {
+ Centre = addSound(centre, lifetimeStart, lifetimeEnd),
+ Rim = addSound(rim, lifetimeStart, lifetimeEnd)
+ };
+ }
+ }
+
+ private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd)
+ {
+ var drawable = new SkinnableSound(hitSampleInfo)
+ {
+ LifetimeStart = lifetimeStart,
+ LifetimeEnd = lifetimeEnd
+ };
+ AddInternal(drawable);
+ return drawable;
+ }
+
+ public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
+
+ public class DrumSample
+ {
+ public SkinnableSound Centre;
+ public SkinnableSound Rim;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
deleted file mode 100644
index c31b07344d..0000000000
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
+++ /dev/null
@@ -1,52 +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.Audio;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Skinning;
-
-namespace osu.Game.Rulesets.Taiko.Audio
-{
- public class DrumSampleMapping
- {
- private readonly ControlPointInfo controlPoints;
- private readonly Dictionary mappings = new Dictionary();
-
- public readonly List Sounds = new List();
-
- public DrumSampleMapping(ControlPointInfo controlPoints)
- {
- this.controlPoints = controlPoints;
-
- IEnumerable samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints;
-
- foreach (var s in samplePoints)
- {
- var centre = s.GetSampleInfo();
- var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
-
- mappings[s.Time] = new DrumSample
- {
- Centre = addSound(centre),
- Rim = addSound(rim)
- };
- }
- }
-
- private SkinnableSound addSound(HitSampleInfo hitSampleInfo)
- {
- var drawable = new SkinnableSound(hitSampleInfo);
- Sounds.Add(drawable);
- return drawable;
- }
-
- public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
-
- public class DrumSample
- {
- public SkinnableSound Centre;
- public SkinnableSound Rim;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs
index 604daa929f..0d91002f4b 100644
--- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs
@@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoDrumRollJudgement : TaikoJudgement
{
- public override bool AffectsCombo => false;
-
protected override double HealthIncreaseFor(HitResult result)
{
// Drum rolls can be ignored with no health penalty
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs
index 29be5e0eac..4d61efd3ee 100644
--- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs
@@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoSwellJudgement : TaikoJudgement
{
- public override bool AffectsCombo => false;
-
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
index 97337acc45..138e8f9785 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
@@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
- public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } };
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
index 8531f3cefd..8223e3bc01 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Skinning;
@@ -34,13 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
- Texture = skin.GetTexture("taiko-roll-end"),
+ Texture = skin.GetTexture("taiko-roll-end", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
FillMode = FillMode.Fit,
},
body = new Sprite
{
RelativeSizeAxes = Axes.Both,
- Texture = skin.GetTexture("taiko-roll-middle"),
+ Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
},
headCircle = new LegacyCirclePiece
{
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs
index 81d645e294..b7b55b9ae0 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
public readonly Sprite Centre;
[Resolved]
- private DrumSampleMapping sampleMappings { get; set; }
+ private DrumSampleContainer sampleContainer { get; set; }
public LegacyHalfDrum(bool flipped)
{
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
public bool OnPressed(TaikoAction action)
{
Drawable target = null;
- var drumSample = sampleMappings.SampleAt(Time.Current);
+ var drumSample = sampleContainer.SampleAt(Time.Current);
if (action == CentreAction)
{
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index 23d675cfb0..f032c5f485 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -91,10 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return null;
case TaikoSkinComponents.Mascot:
- if (GetTexture("pippidonclear0") != null)
- return new DrawableTaikoMascot();
-
- return null;
+ return new DrawableTaikoMascot();
}
return Source.GetDrawableComponent(component);
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 06ccd45cb8..5966b24b34 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.UI
private const float middle_split = 0.025f;
[Cached]
- private DrumSampleMapping sampleMapping;
+ private DrumSampleContainer sampleContainer;
public InputDrum(ControlPointInfo controlPoints)
{
- sampleMapping = new DrumSampleMapping(controlPoints);
+ sampleContainer = new DrumSampleContainer(controlPoints);
RelativeSizeAxes = Axes.Both;
}
@@ -37,39 +37,41 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader]
private void load()
{
- Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- Scale = new Vector2(0.9f),
- Children = new Drawable[]
+ new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container
{
- new TaikoHalfDrum(false)
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ Scale = new Vector2(0.9f),
+ Children = new Drawable[]
{
- Name = "Left Half",
- Anchor = Anchor.Centre,
- Origin = Anchor.CentreRight,
- RelativeSizeAxes = Axes.Both,
- RelativePositionAxes = Axes.X,
- X = -middle_split / 2,
- RimAction = TaikoAction.LeftRim,
- CentreAction = TaikoAction.LeftCentre
- },
- new TaikoHalfDrum(true)
- {
- Name = "Right Half",
- Anchor = Anchor.Centre,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- RelativePositionAxes = Axes.X,
- X = middle_split / 2,
- RimAction = TaikoAction.RightRim,
- CentreAction = TaikoAction.RightCentre
+ new TaikoHalfDrum(false)
+ {
+ Name = "Left Half",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.Both,
+ RelativePositionAxes = Axes.X,
+ X = -middle_split / 2,
+ RimAction = TaikoAction.LeftRim,
+ CentreAction = TaikoAction.LeftCentre
+ },
+ new TaikoHalfDrum(true)
+ {
+ Name = "Right Half",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ RelativePositionAxes = Axes.X,
+ X = middle_split / 2,
+ RimAction = TaikoAction.RightRim,
+ CentreAction = TaikoAction.RightCentre
+ }
}
- }
- });
-
- AddRangeInternal(sampleMapping.Sounds);
+ }),
+ sampleContainer
+ };
}
///
@@ -93,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Sprite centreHit;
[Resolved]
- private DrumSampleMapping sampleMappings { get; set; }
+ private DrumSampleContainer sampleContainer { get; set; }
public TaikoHalfDrum(bool flipped)
{
@@ -154,7 +156,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Drawable target = null;
Drawable back = null;
- var drumSample = sampleMappings.SampleAt(Time.Current);
+ var drumSample = sampleContainer.SampleAt(Time.Current);
if (action == CentreAction)
{
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
index 6f25a5f662..9c76aea54c 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
@@ -128,6 +128,13 @@ namespace osu.Game.Rulesets.Taiko.UI
}
private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex)
- => skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}");
+ {
+ var texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}");
+
+ if (frameIndex == 0 && texture == null)
+ texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}");
+
+ return texture;
+ }
}
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 7a89642e11..2d5e4b911e 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -8,6 +8,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
@@ -118,7 +119,7 @@ namespace osu.Game.Tests.Gameplay
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 8deed75a56..ad5b3ec0f6 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -10,6 +10,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
@@ -216,7 +217,7 @@ namespace osu.Game.Tests.Skins
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
- public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs
index 41d1459103..3a19eabe81 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs
@@ -175,13 +175,13 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 50", () => Clock.CurrentTime == 50);
+ AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("Seek(99)", () => Clock.Seek(99));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
- AddAssert("Time = 100", () => Clock.CurrentTime == 100);
+ AddAssert("Time = 100", () => Clock.CurrentTime == 150);
AddStep("Seek(174)", () => Clock.Seek(174));
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
index 2a7f9389d1..09f5ac2224 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
@@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Editing
public class TestSceneTimingScreen : EditorClockTestScene
{
[Cached(typeof(EditorBeatmap))]
+ [Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
public TestSceneTimingScreen()
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 1961a224c1..420bf29429 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -11,6 +11,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
+using osu.Game.Skinning;
using osuTK;
using osuTK.Input;
@@ -221,6 +222,31 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmExited();
}
+ [Test]
+ public void TestPauseSoundLoop()
+ {
+ AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
+
+ SkinnableSound getLoop() => Player.ChildrenOfType().FirstOrDefault()?.ChildrenOfType().FirstOrDefault();
+
+ pauseAndConfirm();
+ AddAssert("loop is playing", () => getLoop().IsPlaying);
+
+ resumeAndConfirm();
+ AddUntilStep("loop is stopped", () => !getLoop().IsPlaying);
+
+ AddUntilStep("pause again", () =>
+ {
+ Player.Pause();
+ return !Player.GameplayClockContainer.GameplayClock.IsRunning;
+ });
+
+ AddAssert("loop is playing", () => getLoop().IsPlaying);
+
+ resumeAndConfirm();
+ AddUntilStep("loop is stopped", () => !getLoop().IsPlaying);
+ }
+
private void pauseAndConfirm()
{
pause();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index c7455583e4..bc1c10e59d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -173,19 +173,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
}
- public override List GetPendingInputs()
+ public override void CollectPendingInputs(List inputs)
{
- return new List
- {
- new MousePositionAbsoluteInput
- {
- Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero)
- },
- new ReplayState
- {
- PressedActions = CurrentFrame?.Actions ?? new List()
- }
- };
+ inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
+ inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index 7822f07957..c0f99db85d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -113,19 +113,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
}
- public override List GetPendingInputs()
+ public override void CollectPendingInputs(List inputs)
{
- return new List
- {
- new MousePositionAbsoluteInput
- {
- Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero)
- },
- new ReplayState
- {
- PressedActions = CurrentFrame?.Actions ?? new List()
- }
- };
+ inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
+ inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 3b91243fee..bed48f3d86 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -10,6 +10,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -295,7 +296,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
: null;
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
@@ -306,7 +307,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
@@ -318,7 +319,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
new file mode 100644
index 0000000000..e0a1f947ec
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
@@ -0,0 +1,105 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Audio;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Audio;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSkinnableSound : OsuTestScene
+ {
+ [Cached]
+ private GameplayClock gameplayClock = new GameplayClock(new FramedClock());
+
+ private SkinnableSound skinnableSound;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ gameplayClock.IsPaused.Value = false;
+
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Clock = gameplayClock,
+ RelativeSizeAxes = Axes.Both,
+ Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide"))
+ },
+ };
+ });
+
+ [Test]
+ public void TestStoppedSoundDoesntResumeAfterPause()
+ {
+ DrawableSample sample = null;
+ AddStep("start sample with looping", () =>
+ {
+ sample = skinnableSound.ChildrenOfType().First();
+
+ skinnableSound.Looping = true;
+ skinnableSound.Play();
+ });
+
+ AddUntilStep("wait for sample to start playing", () => sample.Playing);
+
+ AddStep("stop sample", () => skinnableSound.Stop());
+
+ AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
+
+ AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true);
+ AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false);
+
+ AddWaitStep("wait a bit", 5);
+ AddAssert("sample not playing", () => !sample.Playing);
+ }
+
+ [Test]
+ public void TestLoopingSoundResumesAfterPause()
+ {
+ DrawableSample sample = null;
+ AddStep("start sample with looping", () =>
+ {
+ skinnableSound.Looping = true;
+ skinnableSound.Play();
+ sample = skinnableSound.ChildrenOfType().First();
+ });
+
+ AddUntilStep("wait for sample to start playing", () => sample.Playing);
+
+ AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true);
+ AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
+ }
+
+ [Test]
+ public void TestNonLoopingStopsWithPause()
+ {
+ DrawableSample sample = null;
+ AddStep("start sample", () =>
+ {
+ skinnableSound.Play();
+ sample = skinnableSound.ChildrenOfType().First();
+ });
+
+ AddAssert("sample playing", () => sample.Playing);
+
+ AddStep("pause gameplay clock", () => gameplayClock.IsPaused.Value = true);
+ AddUntilStep("wait for sample to stop playing", () => !sample.Playing);
+
+ AddStep("resume gameplay clock", () => gameplayClock.IsPaused.Value = false);
+
+ AddAssert("sample not playing", () => !sample.Playing);
+ AddAssert("sample not playing", () => !sample.Playing);
+ AddAssert("sample not playing", () => !sample.Playing);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs
index 9fc7c336cb..0023866124 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
@@ -65,11 +64,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void bindHandler(double delay = 0)
{
- var roomScores = new List();
+ var roomScores = new List();
for (int i = 0; i < 10; i++)
{
- roomScores.Add(new RoomScore
+ roomScores.Add(new MultiplayerScore
{
ID = i,
Accuracy = 0.9 - 0.01 * i,
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs
index 997db827f3..d60222fa0b 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs
@@ -30,12 +30,6 @@ namespace osu.Game.Tests.Visual.Online
Add(selector = new SpotlightSelector());
}
- [Test]
- public void TestVisibility()
- {
- AddStep("Toggle Visibility", selector.ToggleVisibility);
- }
-
[Test]
public void TestLocalSpotlights()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
deleted file mode 100644
index 77e77d90c1..0000000000
--- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
+++ /dev/null
@@ -1,84 +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 NUnit.Framework;
-using osu.Game.Overlays;
-using osu.Game.Users;
-
-namespace osu.Game.Tests.Visual.Online
-{
- [TestFixture]
- public class TestSceneSocialOverlay : OsuTestScene
- {
- protected override bool UseOnlineAPI => true;
-
- public TestSceneSocialOverlay()
- {
- SocialOverlay s = new SocialOverlay
- {
- Users = new[]
- {
- new User
- {
- Username = @"flyte",
- Id = 3103765,
- Country = new Country { FlagName = @"JP" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
- },
- new User
- {
- Username = @"Cookiezi",
- Id = 124493,
- Country = new Country { FlagName = @"KR" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
- },
- new User
- {
- Username = @"Angelsim",
- Id = 1777162,
- Country = new Country { FlagName = @"KR" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
- },
- new User
- {
- Username = @"Rafis",
- Id = 2558286,
- Country = new Country { FlagName = @"PL" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg",
- },
- new User
- {
- Username = @"hvick225",
- Id = 50265,
- Country = new Country { FlagName = @"TW" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg",
- },
- new User
- {
- Username = @"peppy",
- Id = 2,
- Country = new Country { FlagName = @"AU" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
- },
- new User
- {
- Username = @"filsdelama",
- Id = 2831793,
- Country = new Country { FlagName = @"FR" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg"
- },
- new User
- {
- Username = @"_index",
- Id = 652457,
- Country = new Country { FlagName = @"RU" },
- CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg"
- },
- },
- };
- Add(s);
-
- AddStep(@"toggle", s.ToggleVisibility);
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index f763e50067..c2e9945c99 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -42,6 +42,19 @@ namespace osu.Game.Tests.Visual.Online
Spacing = new Vector2(10f),
Children = new Drawable[]
{
+ new UserBrickPanel(new User
+ {
+ Username = @"flyte",
+ Id = 3103765,
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
+ }),
+ new UserBrickPanel(new User
+ {
+ Username = @"peppy",
+ Id = 2,
+ Colour = "99EB47",
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ }),
flyte = new UserGridPanel(new User
{
Username = @"flyte",
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoAnimation.cs
similarity index 85%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneLogoAnimation.cs
index 9c5888d072..155d043bf9 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoAnimation.cs
@@ -11,14 +11,14 @@ using osu.Game.Graphics.Sprites;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneHueAnimation : OsuTestScene
+ public class TestSceneLogoAnimation : OsuTestScene
{
[BackgroundDependencyLoader]
private void load(LargeTextureStore textures)
{
- HueAnimation anim2;
+ LogoAnimation anim2;
- Add(anim2 = new HueAnimation
+ Add(anim2 = new LogoAnimation
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
@@ -26,9 +26,9 @@ namespace osu.Game.Tests.Visual.UserInterface
Colour = Colour4.White,
});
- HueAnimation anim;
+ LogoAnimation anim;
- Add(anim = new HueAnimation
+ Add(anim = new LogoAnimation
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs
index 60af5b37ef..2a76b8e265 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs
@@ -36,11 +36,11 @@ namespace osu.Game.Tests.Visual.UserInterface
}
});
- addHeader("Orange OverlayHeader (no background)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange);
- addHeader("Blue OverlayHeader", new TestNoControlHeader(), OverlayColourScheme.Blue);
+ addHeader("Orange OverlayHeader (no background, 100 padding)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange);
+ addHeader("Blue OverlayHeader (default 50 padding)", new TestNoControlHeader(), OverlayColourScheme.Blue);
addHeader("Green TabControlOverlayHeader (string) with ruleset selector", new TestStringTabControlHeader(), OverlayColourScheme.Green);
- addHeader("Pink TabControlOverlayHeader (enum)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink);
- addHeader("Red BreadcrumbControlOverlayHeader (no background)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red);
+ addHeader("Pink TabControlOverlayHeader (enum, 30 padding)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink);
+ addHeader("Red BreadcrumbControlOverlayHeader (no background, 10 padding)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red);
}
private void addHeader(string name, OverlayHeader header, OverlayColourScheme colourScheme)
@@ -86,6 +86,11 @@ namespace osu.Game.Tests.Visual.UserInterface
private class TestNoBackgroundHeader : OverlayHeader
{
protected override OverlayTitle CreateTitle() => new TestTitle();
+
+ public TestNoBackgroundHeader()
+ {
+ ContentSidePadding = 100;
+ }
}
private class TestNoControlHeader : OverlayHeader
@@ -112,6 +117,11 @@ namespace osu.Game.Tests.Visual.UserInterface
private class TestEnumTabControlHeader : TabControlOverlayHeader
{
+ public TestEnumTabControlHeader()
+ {
+ ContentSidePadding = 30;
+ }
+
protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/rankings");
protected override OverlayTitle CreateTitle() => new TestTitle();
@@ -130,6 +140,8 @@ namespace osu.Game.Tests.Visual.UserInterface
public TestBreadcrumbControlHeader()
{
+ ContentSidePadding = 10;
+
TabControl.AddItem("tab1");
TabControl.AddItem("tab2");
TabControl.Current.Value = "tab2";
diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
new file mode 100644
index 0000000000..5e644fbf1c
--- /dev/null
+++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
@@ -0,0 +1,278 @@
+// 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.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Lists;
+using osu.Framework.Threading;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Beatmaps
+{
+ public class BeatmapDifficultyManager : CompositeDrawable
+ {
+ // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes.
+ private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager));
+
+ // A permanent cache to prevent re-computations.
+ private readonly ConcurrentDictionary difficultyCache = new ConcurrentDictionary();
+
+ // All bindables that should be updated along with the current ruleset + mods.
+ private readonly LockedWeakList trackedBindables = new LockedWeakList();
+
+ [Resolved]
+ private BeatmapManager beatmapManager { get; set; }
+
+ [Resolved]
+ private Bindable currentRuleset { get; set; }
+
+ [Resolved]
+ private Bindable> currentMods { get; set; }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ currentRuleset.BindValueChanged(_ => updateTrackedBindables());
+ currentMods.BindValueChanged(_ => updateTrackedBindables(), true);
+ }
+
+ ///
+ /// Retrieves a bindable containing the star difficulty of a that follows the currently-selected ruleset and mods.
+ ///
+ /// The to get the difficulty of.
+ /// An optional which stops updating the star difficulty for the given .
+ /// A bindable that is updated to contain the star difficulty when it becomes available.
+ public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
+ {
+ var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
+ trackedBindables.Add(bindable);
+ return bindable;
+ }
+
+ ///
+ /// Retrieves a bindable containing the star difficulty of a with a given and combination.
+ ///
+ ///
+ /// The bindable will not update to follow the currently-selected ruleset and mods.
+ ///
+ /// The to get the difficulty of.
+ /// The to get the difficulty with. If null, the 's ruleset is used.
+ /// The s to get the difficulty with. If null, no mods will be assumed.
+ /// An optional which stops updating the star difficulty for the given .
+ /// A bindable that is updated to contain the star difficulty when it becomes available.
+ public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods,
+ CancellationToken cancellationToken = default)
+ => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken);
+
+ ///
+ /// Retrieves the difficulty of a .
+ ///
+ /// The to get the difficulty of.
+ /// The to get the difficulty with.
+ /// The s to get the difficulty with.
+ /// An optional which stops computing the star difficulty.
+ /// The .
+ public async Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null,
+ CancellationToken cancellationToken = default)
+ {
+ if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
+ return existing;
+
+ return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken,
+ TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
+ }
+
+ ///
+ /// Retrieves the difficulty of a .
+ ///
+ /// The to get the difficulty of.
+ /// The to get the difficulty with.
+ /// The s to get the difficulty with.
+ /// The .
+ public StarDifficulty GetDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null)
+ {
+ if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
+ return existing;
+
+ return computeDifficulty(key, beatmapInfo, rulesetInfo);
+ }
+
+ private CancellationTokenSource trackedUpdateCancellationSource;
+
+ ///
+ /// Updates all tracked using the current ruleset and mods.
+ ///
+ private void updateTrackedBindables()
+ {
+ trackedUpdateCancellationSource?.Cancel();
+ trackedUpdateCancellationSource = new CancellationTokenSource();
+
+ foreach (var b in trackedBindables)
+ {
+ if (trackedUpdateCancellationSource.IsCancellationRequested)
+ break;
+
+ using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(trackedUpdateCancellationSource.Token, b.CancellationToken))
+ updateBindable(b, currentRuleset.Value, currentMods.Value, linkedSource.Token);
+ }
+ }
+
+ ///
+ /// Updates the value of a with a given ruleset + mods.
+ ///
+ /// The to update.
+ /// The to update with.
+ /// The s to update with.
+ /// A token that may be used to cancel this update.
+ private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default)
+ {
+ GetDifficultyAsync(bindable.Beatmap, rulesetInfo, mods, cancellationToken).ContinueWith(t =>
+ {
+ // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events.
+ Schedule(() =>
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ bindable.Value = t.Result;
+ });
+ }, cancellationToken);
+ }
+
+ ///
+ /// Creates a new and triggers an initial value update.
+ ///
+ /// The that star difficulty should correspond to.
+ /// The initial to get the difficulty with.
+ /// The initial s to get the difficulty with.
+ /// An optional which stops updating the star difficulty for the given .
+ /// The .
+ private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods,
+ CancellationToken cancellationToken)
+ {
+ var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken);
+ updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken);
+ return bindable;
+ }
+
+ ///
+ /// Computes the difficulty defined by a key, and stores it to the timed cache.
+ ///
+ /// The that defines the computation parameters.
+ /// The to compute the difficulty of.
+ /// The to compute the difficulty with.
+ /// The .
+ private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo)
+ {
+ // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
+ rulesetInfo ??= beatmapInfo.Ruleset;
+
+ try
+ {
+ var ruleset = rulesetInfo.CreateInstance();
+ Debug.Assert(ruleset != null);
+
+ var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo));
+ var attributes = calculator.Calculate(key.Mods);
+
+ return difficultyCache[key] = new StarDifficulty(attributes.StarRating);
+ }
+ catch
+ {
+ return difficultyCache[key] = new StarDifficulty(0);
+ }
+ }
+
+ ///
+ /// Attempts to retrieve an existing difficulty for the combination.
+ ///
+ /// The .
+ /// The .
+ /// The s.
+ /// The existing difficulty value, if present.
+ /// The key that was used to perform this lookup. This can be further used to query .
+ /// Whether an existing difficulty was found.
+ private bool tryGetExisting(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IEnumerable mods, out StarDifficulty existingDifficulty, out DifficultyCacheLookup key)
+ {
+ // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
+ rulesetInfo ??= beatmapInfo.Ruleset;
+
+ // Difficulty can only be computed if the beatmap and ruleset are locally available.
+ if (beatmapInfo.ID == 0 || rulesetInfo.ID == null)
+ {
+ // If not, fall back to the existing star difficulty (e.g. from an online source).
+ existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty);
+ key = default;
+
+ return true;
+ }
+
+ key = new DifficultyCacheLookup(beatmapInfo.ID, rulesetInfo.ID.Value, mods);
+ return difficultyCache.TryGetValue(key, out existingDifficulty);
+ }
+
+ private readonly struct DifficultyCacheLookup : IEquatable
+ {
+ public readonly int BeatmapId;
+ public readonly int RulesetId;
+ public readonly Mod[] Mods;
+
+ public DifficultyCacheLookup(int beatmapId, int rulesetId, IEnumerable mods)
+ {
+ BeatmapId = beatmapId;
+ RulesetId = rulesetId;
+ Mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty();
+ }
+
+ public bool Equals(DifficultyCacheLookup other)
+ => BeatmapId == other.BeatmapId
+ && RulesetId == other.RulesetId
+ && Mods.SequenceEqual(other.Mods);
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+
+ hashCode.Add(BeatmapId);
+ hashCode.Add(RulesetId);
+ foreach (var mod in Mods)
+ hashCode.Add(mod.Acronym);
+
+ return hashCode.ToHashCode();
+ }
+ }
+
+ private class BindableStarDifficulty : Bindable
+ {
+ public readonly BeatmapInfo Beatmap;
+ public readonly CancellationToken CancellationToken;
+
+ public BindableStarDifficulty(BeatmapInfo beatmap, CancellationToken cancellationToken)
+ {
+ Beatmap = beatmap;
+ CancellationToken = cancellationToken;
+ }
+ }
+ }
+
+ public readonly struct StarDifficulty
+ {
+ public readonly double Stars;
+
+ public StarDifficulty(double stars)
+ {
+ Stars = stars;
+
+ // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index af6ca24165..e7788b75f3 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -64,49 +64,49 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The time to find the difficulty control point at.
/// The difficulty control point.
- public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time);
+ public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
///
/// Finds the effect control point that is active at .
///
/// The time to find the effect control point at.
/// The effect control point.
- public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time);
+ public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT);
///
/// Finds the sound control point that is active at .
///
/// The time to find the sound control point at.
/// The sound control point.
- public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
+ public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT);
///
/// Finds the timing control point that is active at .
///
/// The time to find the timing control point at.
/// The timing control point.
- public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
+ public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT);
///
/// Finds the maximum BPM represented by any timing control point.
///
[JsonIgnore]
public double BPMMaximum =>
- 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+ 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength;
///
/// Finds the minimum BPM represented by any timing control point.
///
[JsonIgnore]
public double BPMMinimum =>
- 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+ 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength;
///
/// Finds the mode BPM (most common BPM) represented by the control points.
///
[JsonIgnore]
public double BPMMode =>
- 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
+ 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength;
///
/// Remove all s and return to a pristine state.
@@ -170,12 +170,12 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The list to search.
/// The time to find the control point at.
- /// The control point to use when is before any control points. If null, a new control point will be constructed.
+ /// The control point to use when is before any control points.
/// The active control point at , or a fallback if none found.
- private T binarySearchWithFallback(IReadOnlyList list, double time, T prePoint = null)
- where T : ControlPoint, new()
+ private T binarySearchWithFallback(IReadOnlyList list, double time, T fallback)
+ where T : ControlPoint
{
- return binarySearch(list, time) ?? prePoint ?? new T();
+ return binarySearch(list, time) ?? fallback;
}
///
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 2448b2b25c..1d38790f87 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -7,6 +7,11 @@ namespace osu.Game.Beatmaps.ControlPoints
{
public class DifficultyControlPoint : ControlPoint
{
+ public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
+ {
+ SpeedMultiplierBindable = { Disabled = true },
+ };
+
///
/// The speed multiplier at this control point.
///
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 9b69147468..9e8e3978be 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -7,6 +7,12 @@ namespace osu.Game.Beatmaps.ControlPoints
{
public class EffectControlPoint : ControlPoint
{
+ public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
+ {
+ KiaiModeBindable = { Disabled = true },
+ OmitFirstBarLineBindable = { Disabled = true }
+ };
+
///
/// Whether the first bar line of this control point is ignored.
///
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index 61851a00d7..c052c04ea0 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -10,6 +10,12 @@ namespace osu.Game.Beatmaps.ControlPoints
{
public const string DEFAULT_BANK = "normal";
+ public static readonly SampleControlPoint DEFAULT = new SampleControlPoint
+ {
+ SampleBankBindable = { Disabled = true },
+ SampleVolumeBindable = { Disabled = true }
+ };
+
///
/// The default sample bank at this control point.
///
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index 1927dd6575..9345299c3a 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -13,6 +13,21 @@ namespace osu.Game.Beatmaps.ControlPoints
///
public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple };
+ ///
+ /// 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;
+
+ public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
+ {
+ BeatLengthBindable =
+ {
+ Value = default_beat_length,
+ Disabled = true
+ },
+ TimeSignatureBindable = { Disabled = true }
+ };
+
///
/// The time signature at this control point.
///
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 268328272c..a8a8794320 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -99,6 +99,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised);
Set(OsuSetting.IncreaseFirstObjectVisibility, true);
+ Set(OsuSetting.GameplayDisableWinKey, true);
// Update
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
@@ -229,6 +230,7 @@ namespace osu.Game.Configuration
IntroSequence,
UIHoldActivationDelay,
HitLighting,
- MenuBackgroundSource
+ MenuBackgroundSource,
+ GameplayDisableWinKey
}
}
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index dd5c41285a..df063f57d5 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -43,14 +43,6 @@ namespace osu.Game.Graphics.Containers
///
public double MinimumBeatLength { get; 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;
-
protected bool IsBeatSyncedWithTrack { get; private set; }
protected override void Update()
@@ -81,8 +73,8 @@ namespace osu.Game.Graphics.Containers
if (timingPoint == null || !IsBeatSyncedWithTrack)
{
currentTrackTime = Clock.CurrentTime;
- timingPoint = defaultTiming;
- effectPoint = defaultEffect;
+ timingPoint = TimingControlPoint.DEFAULT;
+ effectPoint = EffectControlPoint.DEFAULT;
}
double beatLength = timingPoint.BeatLength / Divisor;
@@ -116,17 +108,6 @@ namespace osu.Game.Graphics.Containers
private void load(IBindable beatmap)
{
Beatmap.BindTo(beatmap);
-
- defaultTiming = new TimingControlPoint
- {
- BeatLength = default_beat_length,
- };
-
- defaultEffect = new EffectControlPoint
- {
- KiaiMode = false,
- OmitFirstBarLine = false
- };
}
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
index b7ea1ba56a..3015c44613 100644
--- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
@@ -1,7 +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.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -55,7 +54,16 @@ namespace osu.Game.Graphics.Cursor
return;
}
- var newTarget = inputManager.HoveredDrawables.OfType().FirstOrDefault(t => t.ProvidingUserCursor) ?? this;
+ IProvideCursor newTarget = this;
+
+ foreach (var d in inputManager.HoveredDrawables)
+ {
+ if (d is IProvideCursor p && p.ProvidingUserCursor)
+ {
+ newTarget = p;
+ break;
+ }
+ }
if (currentTarget == newTarget)
return;
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index 9804aefce8..d1f6fd445e 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -19,6 +19,7 @@ using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Jpeg;
namespace osu.Game.Graphics
{
@@ -119,7 +120,9 @@ namespace osu.Game.Graphics
break;
case ScreenshotFormat.Jpg:
- image.SaveAsJpeg(stream);
+ const int jpeg_quality = 92;
+
+ image.SaveAsJpeg(stream, new JpegEncoder { Quality = jpeg_quality });
break;
default:
diff --git a/osu.Game/Graphics/Sprites/HueAnimation.cs b/osu.Game/Graphics/Sprites/LogoAnimation.cs
similarity index 79%
rename from osu.Game/Graphics/Sprites/HueAnimation.cs
rename to osu.Game/Graphics/Sprites/LogoAnimation.cs
index 8ad68ace05..b1383065fe 100644
--- a/osu.Game/Graphics/Sprites/HueAnimation.cs
+++ b/osu.Game/Graphics/Sprites/LogoAnimation.cs
@@ -11,13 +11,13 @@ using osu.Framework.Graphics.Textures;
namespace osu.Game.Graphics.Sprites
{
- public class HueAnimation : Sprite
+ public class LogoAnimation : Sprite
{
[BackgroundDependencyLoader]
private void load(ShaderManager shaders, TextureStore textures)
{
- TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"HueAnimation");
- RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"HueAnimation"); // Masking isn't supported for now
+ TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation");
+ RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); // Masking isn't supported for now
}
private float animationProgress;
@@ -36,15 +36,15 @@ namespace osu.Game.Graphics.Sprites
public override bool IsPresent => true;
- protected override DrawNode CreateDrawNode() => new HueAnimationDrawNode(this);
+ protected override DrawNode CreateDrawNode() => new LogoAnimationDrawNode(this);
- private class HueAnimationDrawNode : SpriteDrawNode
+ private class LogoAnimationDrawNode : SpriteDrawNode
{
- private HueAnimation source => (HueAnimation)Source;
+ private LogoAnimation source => (LogoAnimation)Source;
private float progress;
- public HueAnimationDrawNode(HueAnimation source)
+ public LogoAnimationDrawNode(LogoAnimation source)
: base(source)
{
}
diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
index 8977f014b6..f77a3109c9 100644
--- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
@@ -67,6 +67,8 @@ namespace osu.Game.Graphics.UserInterface
public bool OnPressed(GlobalAction action)
{
+ if (!HasFocus) return false;
+
if (action == GlobalAction.Back)
{
if (Text.Length > 0)
diff --git a/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs b/osu.Game/Online/Multiplayer/APICreatedRoom.cs
similarity index 78%
rename from osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs
rename to osu.Game/Online/Multiplayer/APICreatedRoom.cs
index a554101bc7..2a3bb39647 100644
--- a/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs
+++ b/osu.Game/Online/Multiplayer/APICreatedRoom.cs
@@ -2,9 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
-using osu.Game.Online.Multiplayer;
-namespace osu.Game.Online.API.Requests.Responses
+namespace osu.Game.Online.Multiplayer
{
public class APICreatedRoom : Room
{
diff --git a/osu.Game/Online/API/APIPlaylistBeatmap.cs b/osu.Game/Online/Multiplayer/APIPlaylistBeatmap.cs
similarity index 94%
rename from osu.Game/Online/API/APIPlaylistBeatmap.cs
rename to osu.Game/Online/Multiplayer/APIPlaylistBeatmap.cs
index 4f7786e880..98972ef36d 100644
--- a/osu.Game/Online/API/APIPlaylistBeatmap.cs
+++ b/osu.Game/Online/Multiplayer/APIPlaylistBeatmap.cs
@@ -6,7 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
-namespace osu.Game.Online.API
+namespace osu.Game.Online.Multiplayer
{
public class APIPlaylistBeatmap : APIBeatmap
{
diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs b/osu.Game/Online/Multiplayer/APIScoreToken.cs
similarity index 85%
rename from osu.Game/Online/API/Requests/Responses/APIScoreToken.cs
rename to osu.Game/Online/Multiplayer/APIScoreToken.cs
index 1d2465bedf..1f0063d94e 100644
--- a/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs
+++ b/osu.Game/Online/Multiplayer/APIScoreToken.cs
@@ -3,7 +3,7 @@
using Newtonsoft.Json;
-namespace osu.Game.Online.API.Requests.Responses
+namespace osu.Game.Online.Multiplayer
{
public class APIScoreToken
{
diff --git a/osu.Game/Online/API/Requests/CreateRoomRequest.cs b/osu.Game/Online/Multiplayer/CreateRoomRequest.cs
similarity index 86%
rename from osu.Game/Online/API/Requests/CreateRoomRequest.cs
rename to osu.Game/Online/Multiplayer/CreateRoomRequest.cs
index c848c55cc6..dcb4ed51ea 100644
--- a/osu.Game/Online/API/Requests/CreateRoomRequest.cs
+++ b/osu.Game/Online/Multiplayer/CreateRoomRequest.cs
@@ -4,10 +4,9 @@
using System.Net.Http;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Online.Multiplayer;
+using osu.Game.Online.API;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class CreateRoomRequest : APIRequest
{
diff --git a/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs
similarity index 90%
rename from osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs
rename to osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs
index e6246b4f1f..f973f96b37 100644
--- a/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs
+++ b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs
@@ -3,9 +3,9 @@
using System.Net.Http;
using osu.Framework.IO.Network;
-using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.API;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class CreateRoomScoreRequest : APIRequest
{
diff --git a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs
similarity index 85%
rename from osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs
rename to osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs
index 38f852870b..3d3bd20ff3 100644
--- a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs
+++ b/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs
@@ -3,8 +3,9 @@
using System.Collections.Generic;
using Newtonsoft.Json;
+using osu.Game.Online.API;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class GetRoomPlaylistScoresRequest : APIRequest
{
@@ -23,6 +24,6 @@ namespace osu.Game.Online.API.Requests
public class RoomPlaylistScores
{
[JsonProperty("scores")]
- public List Scores { get; set; }
+ public List Scores { get; set; }
}
}
diff --git a/osu.Game/Online/API/Requests/GetRoomRequest.cs b/osu.Game/Online/Multiplayer/GetRoomRequest.cs
similarity index 84%
rename from osu.Game/Online/API/Requests/GetRoomRequest.cs
rename to osu.Game/Online/Multiplayer/GetRoomRequest.cs
index 531e1857de..2907b49f1d 100644
--- a/osu.Game/Online/API/Requests/GetRoomRequest.cs
+++ b/osu.Game/Online/Multiplayer/GetRoomRequest.cs
@@ -1,9 +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.Online.Multiplayer;
+using osu.Game.Online.API;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class GetRoomRequest : APIRequest
{
diff --git a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs b/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs
similarity index 89%
rename from osu.Game/Online/API/Requests/GetRoomScoresRequest.cs
rename to osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs
index eb53369d18..bc913030dd 100644
--- a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs
+++ b/osu.Game/Online/Multiplayer/GetRoomScoresRequest.cs
@@ -2,9 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class GetRoomScoresRequest : APIRequest>
{
diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/Multiplayer/GetRoomsRequest.cs
similarity index 94%
rename from osu.Game/Online/API/Requests/GetRoomsRequest.cs
rename to osu.Game/Online/Multiplayer/GetRoomsRequest.cs
index c47ed20909..64e0386f77 100644
--- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs
+++ b/osu.Game/Online/Multiplayer/GetRoomsRequest.cs
@@ -4,10 +4,10 @@
using System.Collections.Generic;
using Humanizer;
using osu.Framework.IO.Network;
-using osu.Game.Online.Multiplayer;
+using osu.Game.Online.API;
using osu.Game.Screens.Multi.Lounge.Components;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class GetRoomsRequest : APIRequest>
{
diff --git a/osu.Game/Online/API/Requests/JoinRoomRequest.cs b/osu.Game/Online/Multiplayer/JoinRoomRequest.cs
similarity index 90%
rename from osu.Game/Online/API/Requests/JoinRoomRequest.cs
rename to osu.Game/Online/Multiplayer/JoinRoomRequest.cs
index b0808afa45..74375af856 100644
--- a/osu.Game/Online/API/Requests/JoinRoomRequest.cs
+++ b/osu.Game/Online/Multiplayer/JoinRoomRequest.cs
@@ -3,9 +3,9 @@
using System.Net.Http;
using osu.Framework.IO.Network;
-using osu.Game.Online.Multiplayer;
+using osu.Game.Online.API;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class JoinRoomRequest : APIRequest
{
diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/Multiplayer/MultiplayerScore.cs
similarity index 95%
rename from osu.Game/Online/API/RoomScore.cs
rename to osu.Game/Online/Multiplayer/MultiplayerScore.cs
index 3c7f8c9833..3bbf19b11f 100644
--- a/osu.Game/Online/API/RoomScore.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerScore.cs
@@ -6,15 +6,15 @@ using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
-using osu.Game.Online.Multiplayer;
+using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Users;
-namespace osu.Game.Online.API
+namespace osu.Game.Online.Multiplayer
{
- public class RoomScore
+ public class MultiplayerScore
{
[JsonProperty("id")]
public int ID { get; set; }
diff --git a/osu.Game/Online/API/Requests/PartRoomRequest.cs b/osu.Game/Online/Multiplayer/PartRoomRequest.cs
similarity index 90%
rename from osu.Game/Online/API/Requests/PartRoomRequest.cs
rename to osu.Game/Online/Multiplayer/PartRoomRequest.cs
index c988cd5c9e..54bb005d96 100644
--- a/osu.Game/Online/API/Requests/PartRoomRequest.cs
+++ b/osu.Game/Online/Multiplayer/PartRoomRequest.cs
@@ -3,9 +3,9 @@
using System.Net.Http;
using osu.Framework.IO.Network;
-using osu.Game.Online.Multiplayer;
+using osu.Game.Online.API;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
public class PartRoomRequest : APIRequest
{
diff --git a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs
similarity index 90%
rename from osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs
rename to osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs
index 8eb2952159..d31aef2ea5 100644
--- a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs
+++ b/osu.Game/Online/Multiplayer/SubmitRoomScoreRequest.cs
@@ -4,11 +4,12 @@
using System.Net.Http;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
+using osu.Game.Online.API;
using osu.Game.Scoring;
-namespace osu.Game.Online.API.Requests
+namespace osu.Game.Online.Multiplayer
{
- public class SubmitRoomScoreRequest : APIRequest
+ public class SubmitRoomScoreRequest : APIRequest
{
private readonly int scoreId;
private readonly int roomId;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 618049e72c..d6a07651e2 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -18,6 +18,7 @@ using osu.Game.Screens.Menu;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Humanizer;
using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Bindables;
@@ -63,7 +64,8 @@ namespace osu.Game
private ChannelManager channelManager;
- private NotificationOverlay notifications;
+ [NotNull]
+ private readonly NotificationOverlay notifications = new NotificationOverlay();
private NowPlayingOverlay nowPlaying;
@@ -82,7 +84,7 @@ namespace osu.Game
public virtual Storage GetStorageForStableInstall() => null;
- public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
+ public float ToolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0);
private IdleTracker idleTracker;
@@ -250,7 +252,7 @@ namespace osu.Game
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:
case LinkAction.Spectate:
- waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification
+ waitForReady(() => notifications, _ => notifications.Post(new SimpleNotification
{
Text = @"This link type is not yet supported!",
Icon = FontAwesome.Solid.LifeRing,
@@ -536,14 +538,14 @@ namespace osu.Game
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
// todo: all archive managers should be able to be looped here.
- SkinManager.PostNotification = n => notifications?.Post(n);
+ SkinManager.PostNotification = n => notifications.Post(n);
SkinManager.GetStableStorage = GetStorageForStableInstall;
- BeatmapManager.PostNotification = n => notifications?.Post(n);
+ BeatmapManager.PostNotification = n => notifications.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
- ScoreManager.PostNotification = n => notifications?.Post(n);
+ ScoreManager.PostNotification = n => notifications.Post(n);
ScoreManager.GetStableStorage = GetStorageForStableInstall;
ScoreManager.PresentImport = items => PresentScore(items.First());
@@ -615,12 +617,12 @@ namespace osu.Game
loadComponentSingleFile(MusicController = new MusicController(), Add, true);
- loadComponentSingleFile(notifications = new NotificationOverlay
+ loadComponentSingleFile(notifications.With(d =>
{
- GetToolbarHeight = () => ToolbarOffset,
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- }, rightFloatingOverlayContent.Add, true);
+ d.GetToolbarHeight = () => ToolbarOffset;
+ d.Anchor = Anchor.TopRight;
+ d.Origin = Anchor.TopRight;
+ }), rightFloatingOverlayContent.Add, true);
loadComponentSingleFile(screenshotManager, Add);
@@ -758,7 +760,7 @@ namespace osu.Game
Schedule(() => notifications.Post(new SimpleNotification
{
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
- Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
+ Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
}));
}
else if (recentLogCount == short_term_display_limit)
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index dd120937af..fe5c0704b7 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -199,6 +199,10 @@ namespace osu.Game
ScoreManager.Undelete(getBeatmapScores(item), true);
});
+ var difficultyManager = new BeatmapDifficultyManager();
+ dependencies.Cache(difficultyManager);
+ AddInternal(difficultyManager);
+
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
diff --git a/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs b/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs
new file mode 100644
index 0000000000..48f34e8f59
--- /dev/null
+++ b/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs
@@ -0,0 +1,48 @@
+// 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.Graphics.Containers;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics.Sprites;
+using osuTK;
+using osu.Framework.Allocation;
+
+namespace osu.Game.Overlays.Comments.Buttons
+{
+ public class ChevronButton : OsuHoverContainer
+ {
+ public readonly BindableBool Expanded = new BindableBool(true);
+
+ private readonly SpriteIcon icon;
+
+ public ChevronButton()
+ {
+ Size = new Vector2(40, 22);
+ Child = icon = new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(12),
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ IdleColour = HoverColour = colourProvider.Foreground1;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Action = Expanded.Toggle;
+ Expanded.BindValueChanged(onExpandedChanged, true);
+ }
+
+ private void onExpandedChanged(ValueChangedEvent expanded)
+ {
+ icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs
index f7e0cb0a6c..53438ca421 100644
--- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs
+++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs
@@ -32,10 +32,6 @@ namespace osu.Game.Overlays.Comments.Buttons
protected CommentRepliesButton()
{
AutoSizeAxes = Axes.Both;
- Margin = new MarginPadding
- {
- Vertical = 2
- };
InternalChildren = new Drawable[]
{
new CircularContainer
diff --git a/osu.Game/Overlays/Comments/GetCommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs
similarity index 69%
rename from osu.Game/Overlays/Comments/GetCommentRepliesButton.cs
rename to osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs
index a3817ba416..2c363564d2 100644
--- a/osu.Game/Overlays/Comments/GetCommentRepliesButton.cs
+++ b/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs
@@ -8,38 +8,42 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using System.Collections.Generic;
using osuTK;
+using osu.Framework.Allocation;
-namespace osu.Game.Overlays.Comments
+namespace osu.Game.Overlays.Comments.Buttons
{
- public abstract class GetCommentRepliesButton : LoadingButton
+ public class ShowMoreButton : LoadingButton
{
- private const int duration = 200;
-
protected override IEnumerable EffectTargets => new[] { text };
private OsuSpriteText text;
- protected GetCommentRepliesButton()
+ public ShowMoreButton()
{
AutoSizeAxes = Axes.Both;
LoadingAnimationSize = new Vector2(8);
}
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ IdleColour = colourProvider.Light2;
+ HoverColour = colourProvider.Light1;
+ }
+
protected override Drawable CreateContent() => new Container
{
AutoSizeAxes = Axes.Both,
Child = text = new OsuSpriteText
{
AlwaysPresent = true,
- Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
- Text = GetText()
+ Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
+ Text = "show more"
}
};
- protected abstract string GetText();
+ protected override void OnLoadStarted() => text.FadeOut(200, Easing.OutQuint);
- protected override void OnLoadStarted() => text.FadeOut(duration, Easing.OutQuint);
-
- protected override void OnLoadFinished() => text.FadeIn(duration, Easing.OutQuint);
+ protected override void OnLoadFinished() => text.FadeIn(200, Easing.OutQuint);
}
}
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index f71808ba89..2a78748be6 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -78,21 +78,22 @@ namespace osu.Game.Overlays.Comments
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background4
- },
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
+ Margin = new MarginPadding { Bottom = 20 },
Children = new Drawable[]
{
deletedCommentsCounter = new DeletedCommentsCounter
{
- ShowDeleted = { BindTarget = ShowDeleted }
+ ShowDeleted = { BindTarget = ShowDeleted },
+ Margin = new MarginPadding
+ {
+ Horizontal = 70,
+ Vertical = 10
+ }
},
new Container
{
@@ -102,7 +103,10 @@ namespace osu.Game.Overlays.Comments
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Margin = new MarginPadding(5),
+ Margin = new MarginPadding
+ {
+ Vertical = 10
+ },
Action = getComments,
IsLoading = true,
}
diff --git a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs
index f22086bf23..56588ef0a8 100644
--- a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs
+++ b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs
@@ -23,8 +23,6 @@ namespace osu.Game.Overlays.Comments
public DeletedCommentsCounter()
{
AutoSizeAxes = Axes.Both;
- Margin = new MarginPadding { Vertical = 10, Left = 80 };
-
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs
index 3cdc0a0cbd..9c0a48ec29 100644
--- a/osu.Game/Overlays/Comments/DrawableComment.cs
+++ b/osu.Game/Overlays/Comments/DrawableComment.cs
@@ -28,7 +28,6 @@ namespace osu.Game.Overlays.Comments
public class DrawableComment : CompositeDrawable
{
private const int avatar_size = 40;
- private const int margin = 10;
public Action RepliesRequested;
@@ -58,7 +57,7 @@ namespace osu.Game.Overlays.Comments
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OverlayColourProvider colourProvider)
{
LinkFlowContainer username;
FillFlowContainer info;
@@ -70,25 +69,25 @@ namespace osu.Game.Overlays.Comments
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
- new FillFlowContainer
+ new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
+ Padding = getPadding(Comment.IsTopLevel),
+ Child = new FillFlowContainer
{
- new Container
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding(margin) { Left = margin + 5, Top = Comment.IsTopLevel ? 10 : 0 },
- Child = content = new GridContainer
+ content = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
- new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Absolute, size: avatar_size + 10),
new Dimension(),
},
RowDimensions = new[]
@@ -99,93 +98,84 @@ namespace osu.Game.Overlays.Comments
{
new Drawable[]
{
- new FillFlowContainer
+ new Container
{
- AutoSizeAxes = Axes.Both,
- Margin = new MarginPadding { Horizontal = margin },
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5, 0),
+ Size = new Vector2(avatar_size),
Children = new Drawable[]
{
- new Container
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Width = 40,
- AutoSizeAxes = Axes.Y,
- Child = votePill = new VotePill(Comment)
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- }
- },
new UpdateableAvatar(Comment.User)
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
Size = new Vector2(avatar_size),
Masking = true,
CornerRadius = avatar_size / 2f,
CornerExponent = 2,
},
+ votePill = new VotePill(Comment)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreRight,
+ Margin = new MarginPadding
+ {
+ Right = 5
+ }
+ }
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Spacing = new Vector2(0, 3),
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 4),
+ Margin = new MarginPadding
+ {
+ Vertical = 2
+ },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
- Spacing = new Vector2(7, 0),
+ Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true))
+ username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold))
{
- AutoSizeAxes = Axes.Both,
+ AutoSizeAxes = Axes.Both
},
new ParentUsername(Comment),
new OsuSpriteText
{
Alpha = Comment.IsDeleted ? 1 : 0,
- Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
- Text = @"deleted",
+ Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
+ Text = "deleted"
}
}
},
message = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14))
{
RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Right = 40 }
+ AutoSizeAxes = Axes.Y
},
- new FillFlowContainer
+ info = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- info = new FillFlowContainer
+ new DrawableDate(Comment.CreatedAt, 12, false)
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(size: 12),
- Colour = OsuColour.Gray(0.7f),
- Text = HumanizerUtils.Humanize(Comment.CreatedAt)
- },
- }
- },
+ Colour = colourProvider.Foreground1
+ }
+ }
+ },
+ new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
showRepliesButton = new ShowRepliesButton(Comment.RepliesCount)
{
Expanded = { BindTarget = childrenExpanded }
@@ -200,41 +190,51 @@ namespace osu.Game.Overlays.Comments
}
}
}
- }
- },
- childCommentsVisibilityContainer = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
+ },
+ childCommentsVisibilityContainer = new FillFlowContainer
{
- childCommentsContainer = new FillFlowContainer
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = new MarginPadding { Left = 20 },
+ Children = new Drawable[]
{
- Padding = new MarginPadding { Left = 20 },
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical
- },
- deletedCommentsCounter = new DeletedCommentsCounter
- {
- ShowDeleted = { BindTarget = ShowDeleted }
- },
- showMoreButton = new ShowMoreButton
- {
- Action = () => RepliesRequested(this, ++currentPage)
+ childCommentsContainer = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical
+ },
+ deletedCommentsCounter = new DeletedCommentsCounter
+ {
+ ShowDeleted = { BindTarget = ShowDeleted },
+ Margin = new MarginPadding
+ {
+ Top = 10
+ }
+ },
+ showMoreButton = new ShowMoreButton
+ {
+ Action = () => RepliesRequested(this, ++currentPage)
+ }
}
- }
- },
+ },
+ }
}
},
- chevronButton = new ChevronButton
+ new Container
{
+ Size = new Vector2(70, 40),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
- Margin = new MarginPadding { Right = 30, Top = margin },
- Expanded = { BindTarget = childrenExpanded },
- Alpha = 0
+ Margin = new MarginPadding { Horizontal = 5 },
+ Child = chevronButton = new ChevronButton
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Expanded = { BindTarget = childrenExpanded },
+ Alpha = 0
+ }
}
};
@@ -247,10 +247,9 @@ namespace osu.Game.Overlays.Comments
{
info.Add(new OsuSpriteText
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(size: 12),
- Text = $@"edited {HumanizerUtils.Humanize(Comment.EditedAt.Value)} by {Comment.EditedUser.Username}"
+ Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
+ Text = $@"edited {HumanizerUtils.Humanize(Comment.EditedAt.Value)} by {Comment.EditedUser.Username}",
+ Colour = colourProvider.Foreground1
});
}
@@ -357,35 +356,21 @@ namespace osu.Game.Overlays.Comments
showMoreButton.IsLoading = loadRepliesButton.IsLoading = false;
}
- private class ChevronButton : ShowChildrenButton
+ private MarginPadding getPadding(bool isTopLevel)
{
- private readonly SpriteIcon icon;
-
- public ChevronButton()
+ if (isTopLevel)
{
- Child = icon = new SpriteIcon
+ return new MarginPadding
{
- Size = new Vector2(12),
+ Horizontal = 70,
+ Vertical = 15
};
}
- protected override void OnExpandedChanged(ValueChangedEvent expanded)
+ return new MarginPadding
{
- icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown;
- }
- }
-
- private class ShowMoreButton : GetCommentRepliesButton
- {
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- Margin = new MarginPadding { Vertical = 10, Left = 80 };
- IdleColour = colourProvider.Light2;
- HoverColour = colourProvider.Light1;
- }
-
- protected override string GetText() => @"Show More";
+ Top = 10
+ };
}
private class ParentUsername : FillFlowContainer, IHasTooltip
diff --git a/osu.Game/Overlays/Comments/ShowChildrenButton.cs b/osu.Game/Overlays/Comments/ShowChildrenButton.cs
deleted file mode 100644
index 5ec7c1d471..0000000000
--- a/osu.Game/Overlays/Comments/ShowChildrenButton.cs
+++ /dev/null
@@ -1,33 +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.Framework.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Framework.Bindables;
-using osuTK.Graphics;
-using osu.Game.Graphics;
-
-namespace osu.Game.Overlays.Comments
-{
- public abstract class ShowChildrenButton : OsuHoverContainer
- {
- public readonly BindableBool Expanded = new BindableBool(true);
-
- protected ShowChildrenButton()
- {
- AutoSizeAxes = Axes.Both;
- IdleColour = OsuColour.Gray(0.7f);
- HoverColour = Color4.White;
- }
-
- protected override void LoadComplete()
- {
- Action = Expanded.Toggle;
-
- Expanded.BindValueChanged(OnExpandedChanged, true);
- base.LoadComplete();
- }
-
- protected abstract void OnExpandedChanged(ValueChangedEvent expanded);
- }
-}
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
index 79fda99c73..41b25ee1a5 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -225,6 +225,9 @@ namespace osu.Game.Overlays.Dashboard.Friends
case OverlayPanelDisplayStyle.List:
return new UserListPanel(user);
+
+ case OverlayPanelDisplayStyle.Brick:
+ return new UserBrickPanel(user);
}
}
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
index dbc934bde9..cc7f798c4a 100644
--- a/osu.Game/Overlays/OverlayHeader.cs
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -12,9 +12,26 @@ namespace osu.Game.Overlays
{
public abstract class OverlayHeader : Container
{
- public const int CONTENT_X_MARGIN = 50;
+ private float contentSidePadding;
+
+ ///
+ /// Horizontal padding of the header content.
+ ///
+ protected float ContentSidePadding
+ {
+ get => contentSidePadding;
+ set
+ {
+ contentSidePadding = value;
+ content.Padding = new MarginPadding
+ {
+ Horizontal = value
+ };
+ }
+ }
private readonly Box titleBackground;
+ private readonly Container content;
protected readonly FillFlowContainer HeaderInfo;
@@ -50,14 +67,10 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
},
- new Container
+ content = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding
- {
- Horizontal = CONTENT_X_MARGIN,
- },
Children = new[]
{
CreateTitle().With(title =>
@@ -79,6 +92,8 @@ namespace osu.Game.Overlays
CreateContent()
}
});
+
+ ContentSidePadding = 50;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs
index 7269007b41..87b9d89d4d 100644
--- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs
+++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs
@@ -34,6 +34,10 @@ namespace osu.Game.Overlays
{
Icon = FontAwesome.Solid.Bars
});
+ AddTabItem(new PanelDisplayTabItem(OverlayPanelDisplayStyle.Brick)
+ {
+ Icon = FontAwesome.Solid.Th
+ });
}
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
@@ -96,6 +100,7 @@ namespace osu.Game.Overlays
public enum OverlayPanelDisplayStyle
{
Card,
- List
+ List,
+ Brick
}
}
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 0161d91daa..2e5f1071f2 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -23,6 +23,8 @@ namespace osu.Game.Overlays.Profile
public ProfileHeader()
{
+ ContentSidePadding = UserProfileOverlay.CONTENT_X_MARGIN;
+
User.ValueChanged += e => updateDisplay(e.NewValue);
TabControl.AddItem("info");
diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs
index f112c1ec43..422373d099 100644
--- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs
@@ -18,10 +18,8 @@ using osu.Game.Online.API.Requests;
namespace osu.Game.Overlays.Rankings
{
- public class SpotlightSelector : VisibilityContainer, IHasCurrentValue
+ public class SpotlightSelector : CompositeDrawable, IHasCurrentValue
{
- private const int duration = 300;
-
private readonly BindableWithCurrent current = new BindableWithCurrent();
public readonly Bindable Sort = new Bindable();
@@ -37,10 +35,7 @@ namespace osu.Game.Overlays.Rankings
set => dropdown.Items = value;
}
- protected override bool StartHidden => true;
-
private readonly Box background;
- private readonly Container content;
private readonly SpotlightsDropdown dropdown;
private readonly InfoColumn startDateColumn;
private readonly InfoColumn endDateColumn;
@@ -51,73 +46,68 @@ namespace osu.Game.Overlays.Rankings
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Add(content = new Container
+ InternalChildren = new Drawable[]
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
+ background = new Box
{
- background = new Box
- {
- RelativeSizeAxes = Axes.Both,
- },
- new Container
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN },
+ Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN },
- Child = new FillFlowContainer
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
+ new Container
{
- new Container
- {
- Margin = new MarginPadding { Vertical = 20 },
- RelativeSizeAxes = Axes.X,
- Height = 40,
- Depth = -float.MaxValue,
- Child = dropdown = new SpotlightsDropdown
- {
- RelativeSizeAxes = Axes.X,
- Current = Current
- }
- },
- new Container
+ Margin = new MarginPadding { Vertical = 20 },
+ RelativeSizeAxes = Axes.X,
+ Height = 40,
+ Depth = -float.MaxValue,
+ Child = dropdown = new SpotlightsDropdown
{
RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
+ Current = Current
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
{
- new FillFlowContainer
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10, 0),
+ Margin = new MarginPadding { Bottom = 5 },
+ Children = new Drawable[]
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- Margin = new MarginPadding { Bottom = 5 },
- Children = new Drawable[]
- {
- startDateColumn = new InfoColumn(@"Start Date"),
- endDateColumn = new InfoColumn(@"End Date"),
- mapCountColumn = new InfoColumn(@"Map Count"),
- participantsColumn = new InfoColumn(@"Participants")
- }
- },
- new RankingsSortTabControl
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Current = Sort
+ startDateColumn = new InfoColumn(@"Start Date"),
+ endDateColumn = new InfoColumn(@"End Date"),
+ mapCountColumn = new InfoColumn(@"Map Count"),
+ participantsColumn = new InfoColumn(@"Participants")
}
+ },
+ new RankingsSortTabControl
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Current = Sort
}
}
}
}
}
}
- });
+ };
}
[BackgroundDependencyLoader]
@@ -134,10 +124,6 @@ namespace osu.Game.Overlays.Rankings
participantsColumn.Value = response.Spotlight.Participants?.ToString("N0");
}
- protected override void PopIn() => content.FadeIn(duration, Easing.OutQuint);
-
- protected override void PopOut() => content.FadeOut(duration, Easing.OutQuint);
-
private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd");
private class InfoColumn : FillFlowContainer
diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
index 0f9b07bf89..61339df76f 100644
--- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
@@ -81,8 +81,6 @@ namespace osu.Game.Overlays.Rankings
{
base.LoadComplete();
- selector.Show();
-
selectedSpotlight.BindValueChanged(_ => onSpotlightChanged());
sort.BindValueChanged(_ => onSpotlightChanged());
Ruleset.BindValueChanged(onRulesetChanged);
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index 93a02ea0e4..0149e6c3a6 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.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;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Configuration;
@@ -78,6 +79,15 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode)
}
};
+
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
+ {
+ Add(new SettingsCheckbox
+ {
+ LabelText = "Disable Windows key during gameplay",
+ Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey)
+ });
+ }
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 52b712a40e..34e5da4ef4 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved]
private OsuColour colours { get; set; }
- private UserPanel panel;
+ private UserGridPanel panel;
private UserDropdown dropdown;
///
diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs
deleted file mode 100644
index 93fcc3c401..0000000000
--- a/osu.Game/Overlays/Social/FilterControl.cs
+++ /dev/null
@@ -1,33 +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.Framework.Extensions.Color4Extensions;
-using osuTK.Graphics;
-using osu.Framework.Graphics;
-using osu.Game.Overlays.SearchableList;
-
-namespace osu.Game.Overlays.Social
-{
- public class FilterControl : SearchableListFilterControl
- {
- protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"47253a");
- protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank;
- protected override SortDirection DefaultCategory => SortDirection.Ascending;
-
- public FilterControl()
- {
- Tabs.Margin = new MarginPadding { Top = 10 };
- }
- }
-
- public enum SocialSortCriteria
- {
- Rank,
- Name,
- Location,
- //[Description("Time Zone")]
- //TimeZone,
- //[Description("World Map")]
- //WorldMap,
- }
-}
diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs
deleted file mode 100644
index 22e0fdcd56..0000000000
--- a/osu.Game/Overlays/Social/Header.cs
+++ /dev/null
@@ -1,67 +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.Overlays.SearchableList;
-using osuTK.Graphics;
-using osu.Framework.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Framework.Allocation;
-using System.ComponentModel;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics.Sprites;
-
-namespace osu.Game.Overlays.Social
-{
- public class Header : SearchableListHeader
- {
- private OsuSpriteText browser;
-
- protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"38202e");
-
- protected override SocialTab DefaultTab => SocialTab.AllPlayers;
- protected override IconUsage Icon => FontAwesome.Solid.Users;
-
- protected override Drawable CreateHeaderText()
- {
- return new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Children = new[]
- {
- new OsuSpriteText
- {
- Text = "social ",
- Font = OsuFont.GetFont(size: 25),
- },
- browser = new OsuSpriteText
- {
- Text = "browser",
- Font = OsuFont.GetFont(size: 25, weight: FontWeight.Light),
- },
- },
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- browser.Colour = colours.Pink;
- }
- }
-
- public enum SocialTab
- {
- [Description("All Players")]
- AllPlayers,
-
- [Description("Friends")]
- Friends,
- //[Description("Team Members")]
- //TeamMembers,
- //[Description("Chat Channels")]
- //ChatChannels,
- }
-}
diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs
deleted file mode 100644
index 1b05142192..0000000000
--- a/osu.Game/Overlays/SocialOverlay.cs
+++ /dev/null
@@ -1,242 +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;
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Bindables;
-using osuTK;
-using osuTK.Graphics;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
-using osu.Game.Overlays.SearchableList;
-using osu.Game.Overlays.Social;
-using osu.Game.Users;
-using System.Threading;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Threading;
-
-namespace osu.Game.Overlays
-{
- public class SocialOverlay : SearchableListOverlay
- {
- private readonly LoadingSpinner loading;
- private FillFlowContainer panels;
-
- protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"60284b");
- protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"672b51");
- protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"5c2648");
-
- protected override SearchableListHeader CreateHeader() => new Header();
- protected override SearchableListFilterControl CreateFilterControl() => new FilterControl();
-
- private User[] users = Array.Empty();
-
- public User[] Users
- {
- get => users;
- set
- {
- if (users == value)
- return;
-
- users = value ?? Array.Empty();
-
- if (LoadState >= LoadState.Ready)
- recreatePanels();
- }
- }
-
- public SocialOverlay()
- : base(OverlayColourScheme.Pink)
- {
- Add(loading = new LoadingSpinner());
-
- Filter.Search.Current.ValueChanged += text =>
- {
- if (!string.IsNullOrEmpty(text.NewValue))
- {
- // force searching in players until searching for friends is supported
- Header.Tabs.Current.Value = SocialTab.AllPlayers;
-
- if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank)
- Filter.Tabs.Current.Value = SocialSortCriteria.Rank;
- }
- };
-
- Header.Tabs.Current.ValueChanged += _ => queueUpdate();
- Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate();
-
- Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels();
- Filter.Dropdown.Current.ValueChanged += _ => recreatePanels();
-
- currentQuery.BindTo(Filter.Search.Current);
- currentQuery.ValueChanged += query =>
- {
- queryChangedDebounce?.Cancel();
-
- if (string.IsNullOrEmpty(query.NewValue))
- queueUpdate();
- else
- queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500);
- };
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- recreatePanels();
- }
-
- private APIRequest getUsersRequest;
-
- private readonly Bindable currentQuery = new Bindable();
-
- private ScheduledDelegate queryChangedDebounce;
-
- private void queueUpdate() => Scheduler.AddOnce(updateSearch);
-
- private CancellationTokenSource loadCancellation;
-
- private void updateSearch()
- {
- queryChangedDebounce?.Cancel();
-
- if (!IsLoaded)
- return;
-
- Users = null;
- clearPanels();
- getUsersRequest?.Cancel();
-
- if (API?.IsLoggedIn != true)
- return;
-
- switch (Header.Tabs.Current.Value)
- {
- case SocialTab.Friends:
- var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
- friendRequest.Success += users => Users = users.ToArray();
- API.Queue(getUsersRequest = friendRequest);
- break;
-
- default:
- var userRequest = new GetUsersRequest(); // TODO filter arguments!
- userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray();
- API.Queue(getUsersRequest = userRequest);
- break;
- }
- }
-
- private void recreatePanels()
- {
- clearPanels();
-
- if (Users == null)
- {
- loading.Hide();
- return;
- }
-
- IEnumerable sortedUsers = Users;
-
- switch (Filter.Tabs.Current.Value)
- {
- case SocialSortCriteria.Location:
- sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName);
- break;
-
- case SocialSortCriteria.Name:
- sortedUsers = sortedUsers.OrderBy(u => u.Username);
- break;
- }
-
- if (Filter.Dropdown.Current.Value == SortDirection.Descending)
- sortedUsers = sortedUsers.Reverse();
-
- var newPanels = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Spacing = new Vector2(10f),
- Margin = new MarginPadding { Top = 10 },
- ChildrenEnumerable = sortedUsers.Select(u =>
- {
- UserPanel panel;
-
- switch (Filter.DisplayStyleControl.DisplayStyle.Value)
- {
- case PanelDisplayStyle.Grid:
- panel = new UserGridPanel(u)
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Width = 290,
- };
- break;
-
- default:
- panel = new UserListPanel(u);
- break;
- }
-
- panel.Status.BindTo(u.Status);
- panel.Activity.BindTo(u.Activity);
- return panel;
- })
- };
-
- LoadComponentAsync(newPanels, f =>
- {
- if (panels != null)
- ScrollFlow.Remove(panels);
-
- loading.Hide();
- ScrollFlow.Add(panels = newPanels);
- }, (loadCancellation = new CancellationTokenSource()).Token);
- }
-
- private void onFilterUpdate()
- {
- if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank)
- {
- queueUpdate();
- return;
- }
-
- recreatePanels();
- }
-
- private void clearPanels()
- {
- loading.Show();
-
- loadCancellation?.Cancel();
-
- if (panels != null)
- {
- panels.Expire();
- panels = null;
- }
- }
-
- public override void APIStateChanged(IAPIProvider api, APIState state)
- {
- switch (state)
- {
- case APIState.Online:
- queueUpdate();
- break;
-
- default:
- Users = null;
- clearPanels();
- break;
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs
index e8e000f441..61605d9e9e 100644
--- a/osu.Game/Overlays/TabControlOverlayHeader.cs
+++ b/osu.Game/Overlays/TabControlOverlayHeader.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Overlays
protected OsuTabControl TabControl;
private readonly Box controlBackground;
+ private readonly Container tabControlContainer;
private readonly BindableWithCurrent current = new BindableWithCurrent();
public Bindable Current
@@ -30,6 +31,16 @@ namespace osu.Game.Overlays
set => current.Current = value;
}
+ protected new float ContentSidePadding
+ {
+ get => base.ContentSidePadding;
+ set
+ {
+ base.ContentSidePadding = value;
+ tabControlContainer.Padding = new MarginPadding { Horizontal = value };
+ }
+ }
+
protected TabControlOverlayHeader()
{
HeaderInfo.Add(new Container
@@ -42,11 +53,16 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.Both,
},
- TabControl = CreateTabControl().With(control =>
+ tabControlContainer = new Container
{
- control.Margin = new MarginPadding { Left = CONTENT_X_MARGIN };
- control.Current = Current;
- })
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Horizontal = ContentSidePadding },
+ Child = TabControl = CreateTabControl().With(control =>
+ {
+ control.Current = Current;
+ })
+ }
}
});
}
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index 052aaa3c65..d24c81536e 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
@@ -130,7 +130,11 @@ namespace osu.Game.Rulesets.Judgements
if (type == currentDrawableType)
return;
- InternalChild = JudgementBody = new Container
+ // sub-classes might have added their own children that would be removed here if .InternalChild was used.
+ if (JudgementBody != null)
+ RemoveInternal(JudgementBody);
+
+ AddInternal(JudgementBody = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -142,7 +146,7 @@ namespace osu.Game.Rulesets.Judgements
Colour = colours.ForHitResult(type),
Scale = new Vector2(0.85f, 1),
}, confineMode: ConfineMode.NoScaling)
- };
+ });
currentDrawableType = type;
}
diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs
index 7fe606d584..65f1a972ed 100644
--- a/osu.Game/Rulesets/Mods/ModPerfect.cs
+++ b/osu.Game/Rulesets/Mods/ModPerfect.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> !(result.Judgement is IgnoreJudgement)
+ && result.Judgement.AffectsCombo
&& result.Type != result.Judgement.MaxResult;
}
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index b633cb0860..581617b567 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -126,12 +126,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (Result == null)
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
- loadSamples();
+ LoadSamples();
}
- protected override void LoadComplete()
+ protected override void LoadAsyncComplete()
{
- base.LoadComplete();
+ base.LoadAsyncComplete();
HitObject.DefaultsApplied += onDefaultsApplied;
@@ -145,14 +145,19 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
samplesBindable = HitObject.SamplesBindable.GetBoundCopy();
- samplesBindable.CollectionChanged += (_, __) => loadSamples();
+ samplesBindable.CollectionChanged += (_, __) => LoadSamples();
apply(HitObject);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
updateState(ArmedState.Idle, true);
}
- private void loadSamples()
+ protected virtual void LoadSamples()
{
if (Samples != null)
{
@@ -353,17 +358,32 @@ namespace osu.Game.Rulesets.Objects.Drawables
[Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
+ ///
+ /// Calculate the position to be used for sample playback at a specified X position (0..1).
+ ///
+ /// The lookup X position. Generally should be .
+ ///
+ protected double CalculateSamplePlaybackBalance(double position)
+ {
+ const float balance_adjust_amount = 0.4f;
+
+ return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0);
+ }
+
+ ///
+ /// Whether samples should currently be playing. Will be false during seek operations.
+ ///
+ protected bool ShouldPlaySamples => gameplayClock?.IsSeeking != true;
+
///
/// Plays all the hit sounds for this .
/// This is invoked automatically when this is hit.
///
public virtual void PlaySamples()
{
- const float balance_adjust_amount = 0.4f;
-
- if (Samples != null && gameplayClock?.IsSeeking != true)
+ if (Samples != null && ShouldPlaySamples)
{
- Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
+ Samples.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition);
Samples.Play();
}
}
diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
index 55d82c4083..cf5c88b8fd 100644
--- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
+++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
-using osu.Framework.Input.StateChanges;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
@@ -69,8 +68,6 @@ namespace osu.Game.Rulesets.Replays
return true;
}
- public override List GetPendingInputs() => new List();
-
private const double sixty_frame_time = 1000.0 / 60;
protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2;
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 0dc3324559..bf64175468 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Framework.Threading;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -17,7 +19,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
{
private readonly IBindable timeRange = new BindableDouble();
private readonly IBindable direction = new Bindable();
- private readonly Dictionary hitObjectInitialStateCache = new Dictionary();
+ private readonly Dictionary hitObjectInitialStateCache = new Dictionary();
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
@@ -175,10 +177,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
{
// The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame).
// In such a case, combinedObjCache will take care of updating the hitobject.
- if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var objCache))
+ if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var state))
{
combinedObjCache.Invalidate();
- objCache.Invalidate();
+ state.Cache.Invalidate();
}
}
@@ -190,8 +192,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
if (!layoutCache.IsValid)
{
- foreach (var cached in hitObjectInitialStateCache.Values)
- cached.Invalidate();
+ foreach (var state in hitObjectInitialStateCache.Values)
+ state.Cache.Invalidate();
combinedObjCache.Invalidate();
scrollingInfo.Algorithm.Reset();
@@ -215,16 +217,18 @@ namespace osu.Game.Rulesets.UI.Scrolling
foreach (var obj in Objects)
{
- if (!hitObjectInitialStateCache.TryGetValue(obj, out var objCache))
- objCache = hitObjectInitialStateCache[obj] = new Cached();
+ if (!hitObjectInitialStateCache.TryGetValue(obj, out var state))
+ state = hitObjectInitialStateCache[obj] = new InitialState(new Cached());
- if (objCache.IsValid)
+ if (state.Cache.IsValid)
continue;
- computeLifetimeStartRecursive(obj);
- computeInitialStateRecursive(obj);
+ state.ScheduledComputation?.Cancel();
+ state.ScheduledComputation = computeInitialStateRecursive(obj);
- objCache.Validate();
+ computeLifetimeStartRecursive(obj);
+
+ state.Cache.Validate();
}
combinedObjCache.Validate();
@@ -267,8 +271,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength);
}
- // Cant use AddOnce() since the delegate is re-constructed every invocation
- private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
+ private ScheduledDelegate computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
{
if (hitObject.HitObject is IHasDuration e)
{
@@ -325,5 +328,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
break;
}
}
+
+ private class InitialState
+ {
+ [NotNull]
+ public readonly Cached Cache;
+
+ [CanBeNull]
+ public ScheduledDelegate ScheduledComputation;
+
+ public InitialState(Cached cache)
+ {
+ Cache = cache;
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index c6e228262f..fcff672045 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -64,6 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
DragBox = CreateDragBox(select),
selectionHandler,
SelectionBlueprints = CreateSelectionBlueprintContainer(),
+ selectionHandler.CreateProxy(),
DragBox.CreateProxy().With(p => p.Depth = float.MinValue)
});
@@ -121,14 +122,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
return e.Button == MouseButton.Left;
}
+ private SelectionBlueprint clickedBlueprint;
+
protected override bool OnClick(ClickEvent e)
{
if (e.Button == MouseButton.Right)
return false;
+ // store for double-click handling
+ clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
+
// Deselection should only occur if no selected blueprints are hovered
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
- if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
+ if (endClickSelection() || clickedBlueprint != null)
return true;
deselectAll();
@@ -140,9 +146,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (e.Button == MouseButton.Right)
return false;
- SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
-
- if (clickedBlueprint == null)
+ // ensure the blueprint which was hovered for the first click is still the hovered blueprint.
+ if (clickedBlueprint == null || selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint)
return false;
editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index 38893f90a8..9700cb8c8e 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -15,6 +15,7 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.States;
using osu.Game.Audio;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
@@ -35,7 +36,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject);
- private Drawable outline;
+ private Drawable content;
+
+ private OsuSpriteText selectionDetailsText;
[Resolved(CanBeNull = true)]
protected EditorBeatmap EditorBeatmap { get; private set; }
@@ -55,16 +58,42 @@ namespace osu.Game.Screens.Edit.Compose.Components
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- InternalChild = outline = new Container
+ InternalChild = content = new Container
{
- Masking = true,
- BorderThickness = BORDER_RADIUS,
- BorderColour = colours.Yellow,
- Child = new Box
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- AlwaysPresent = true,
- Alpha = 0
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = BORDER_RADIUS,
+ BorderColour = colours.YellowDark,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ AlwaysPresent = true,
+ Alpha = 0
+ }
+ },
+ new Container
+ {
+ Name = "info text",
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = colours.YellowDark,
+ RelativeSizeAxes = Axes.Both,
+ },
+ selectionDetailsText = new OsuSpriteText
+ {
+ Padding = new MarginPadding(2),
+ Colour = colours.Gray0,
+ Font = OsuFont.Default.With(size: 11)
+ }
+ }
+ }
}
};
}
@@ -131,9 +160,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
selectedBlueprints.Remove(blueprint);
EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
- // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection
- if (selectedBlueprints.Count == 0)
- UpdateVisibility();
+ UpdateVisibility();
}
///
@@ -179,7 +206,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
internal void UpdateVisibility()
{
- if (selectedBlueprints.Count > 0)
+ int count = selectedBlueprints.Count;
+
+ selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty;
+
+ if (count > 0)
Show();
else
Hide();
@@ -205,8 +236,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
topLeft -= new Vector2(5);
bottomRight += new Vector2(5);
- outline.Size = bottomRight - topLeft;
- outline.Position = topLeft;
+ content.Size = bottomRight - topLeft;
+ content.Position = topLeft;
}
#endregion
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 9f61589c36..d92f3922c3 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -275,11 +275,22 @@ namespace osu.Game.Screens.Edit
protected override bool OnScroll(ScrollEvent e)
{
- scrollAccumulation += (e.ScrollDelta.X + e.ScrollDelta.Y) * (e.IsPrecise ? 0.1 : 1);
+ const double precision = 1;
- const int precision = 1;
+ double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y;
- while (Math.Abs(scrollAccumulation) > precision)
+ double scrollDirection = Math.Sign(scrollComponent);
+
+ // this is a special case to handle the "pivot" scenario.
+ // if we are precise scrolling in one direction then change our mind and scroll backwards,
+ // the existing accumulation should be applied in the inverse direction to maintain responsiveness.
+ if (Math.Sign(scrollAccumulation) != scrollDirection)
+ scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation));
+
+ scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1);
+
+ // because we are doing snapped seeking, we need to add up precise scrolls until they accumulate to an arbitrary cut-off.
+ while (Math.Abs(scrollAccumulation) >= precision)
{
if (scrollAccumulation > 0)
seek(e, -1);
diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs
index dd934c10cd..d4d0feb813 100644
--- a/osu.Game/Screens/Edit/EditorClock.cs
+++ b/osu.Game/Screens/Edit/EditorClock.cs
@@ -118,9 +118,14 @@ namespace osu.Game.Screens.Edit
seekTime = timingPoint.Time + closestBeat * seekAmount;
+ // limit forward seeking to only up to the next timing point's start time.
+ var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
+ if (seekTime > nextTimingPoint?.Time)
+ seekTime = nextTimingPoint.Time;
+
// Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this.
// Instead, we'll go to the next beat in the direction when this is the case
- if (Precision.AlmostEquals(current, seekTime))
+ if (Precision.AlmostEquals(current, seekTime, 0.5f))
{
closestBeat += direction > 0 ? 1 : -1;
seekTime = timingPoint.Time + closestBeat * seekAmount;
@@ -129,10 +134,6 @@ namespace osu.Game.Screens.Edit
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
seekTime = timingPoint.Time;
- var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
- if (seekTime > nextTimingPoint?.Time)
- seekTime = nextTimingPoint.Time;
-
// Ensure the sought point is within the boundaries
seekTime = Math.Clamp(seekTime, 0, TrackLength);
SeekTo(seekTime);
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index b56ba6c8a4..a9ef20436f 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -260,7 +260,7 @@ namespace osu.Game.Screens.Menu
private class LazerLogo : CompositeDrawable
{
- private HueAnimation highlight, background;
+ private LogoAnimation highlight, background;
public float Progress
{
@@ -282,13 +282,13 @@ namespace osu.Game.Screens.Menu
{
InternalChildren = new Drawable[]
{
- highlight = new HueAnimation
+ highlight = new LogoAnimation
{
RelativeSizeAxes = Axes.Both,
Texture = textures.Get(@"Intro/Triangles/logo-highlight"),
Colour = Color4.White,
},
- background = new HueAnimation
+ background = new LogoAnimation
{
RelativeSizeAxes = Axes.Both,
Texture = textures.Get(@"Intro/Triangles/logo-background"),
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index 089906c342..f5e4b078da 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -330,7 +330,7 @@ namespace osu.Game.Screens.Menu
if (Beatmap.Value.Track.IsRunning)
{
var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0;
- logoAmplitudeContainer.ScaleTo(1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 75, Easing.OutQuint);
+ logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed));
if (maxAmplitude > velocity_adjust_cutoff)
triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50;
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
index 571bbde716..1afbf5c32a 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Online.Multiplayer;
diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
index cf0197d26b..c2381fe219 100644
--- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
+++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
@@ -10,7 +10,6 @@ using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Scoring;
diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs
index 5cafc974f1..f367d44347 100644
--- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs
+++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs
index 491be2e946..2a96fa536d 100644
--- a/osu.Game/Screens/Multi/RoomManager.cs
+++ b/osu.Game/Screens/Multi/RoomManager.cs
@@ -14,7 +14,6 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Online;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Lounge.Components;
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index 6b37135c86..57403a0987 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -24,7 +24,8 @@ namespace osu.Game.Screens.Play
{
public abstract class GameplayMenuOverlay : OverlayContainer, IKeyBindingHandler
{
- private const int transition_duration = 200;
+ protected const int TRANSITION_DURATION = 200;
+
private const int button_height = 70;
private const float background_alpha = 0.75f;
@@ -156,8 +157,8 @@ namespace osu.Game.Screens.Play
}
}
- protected override void PopIn() => this.FadeIn(transition_duration, Easing.In);
- protected override void PopOut() => this.FadeOut(transition_duration, Easing.In);
+ protected override void PopIn() => this.FadeIn(TRANSITION_DURATION, Easing.In);
+ protected override void PopOut() => this.FadeOut(TRANSITION_DURATION, Easing.In);
// Don't let mouse down events through the overlay or people can click circles while paused.
protected override bool OnMouseDown(MouseDownEvent e) => true;
diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs
index 84dbb35f68..847b8a53cf 100644
--- a/osu.Game/Screens/Play/HUD/FailingLayer.cs
+++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HUD
private const float max_alpha = 0.4f;
private const int fade_time = 400;
- private const float gradient_size = 0.3f;
+ private const float gradient_size = 0.2f;
///
/// The threshold under which the current player life should be considered low and the layer should start fading in.
@@ -56,16 +56,16 @@ namespace osu.Game.Screens.Play.HUD
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0)),
- Height = gradient_size,
+ Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.White.Opacity(0)),
+ Width = gradient_size,
},
new Box
{
RelativeSizeAxes = Axes.Both,
- Height = gradient_size,
- Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0), Color4.White),
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
+ Width = gradient_size,
+ Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White),
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
},
}
},
diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs
index 6cc6027a03..fa917cda32 100644
--- a/osu.Game/Screens/Play/PauseOverlay.cs
+++ b/osu.Game/Screens/Play/PauseOverlay.cs
@@ -4,7 +4,10 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Audio;
using osu.Game.Graphics;
+using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
@@ -13,17 +16,46 @@ namespace osu.Game.Screens.Play
{
public Action OnResume;
+ public override bool IsPresent => base.IsPresent || pauseLoop.IsPlaying;
+
public override string Header => "paused";
public override string Description => "you're not going to do what i think you're going to do, are ya?";
+ private SkinnableSound pauseLoop;
+
protected override Action BackAction => () => InternalButtons.Children.First().Click();
+ private const float minimum_volume = 0.0001f;
+
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AddButton("Continue", colours.Green, () => OnResume?.Invoke());
AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke());
AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke());
+
+ AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop"))
+ {
+ Looping = true,
+ });
+
+ // SkinnableSound only plays a sound if its aggregate volume is > 0, so the volume must be turned up before playing it
+ pauseLoop.VolumeTo(minimum_volume);
+ }
+
+ protected override void PopIn()
+ {
+ base.PopIn();
+
+ pauseLoop.VolumeTo(1.0f, TRANSITION_DURATION, Easing.InQuint);
+ pauseLoop.Play();
+ }
+
+ protected override void PopOut()
+ {
+ base.PopOut();
+
+ pauseLoop.VolumeTo(minimum_volume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
}
}
}
diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs
index 9633f5c533..5da432d5b2 100644
--- a/osu.Game/Screens/Ranking/ScorePanel.cs
+++ b/osu.Game/Screens/Ranking/ScorePanel.cs
@@ -13,6 +13,7 @@ using osu.Framework.Input.Events;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Contracted;
using osu.Game.Screens.Ranking.Expanded;
+using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@@ -142,7 +143,16 @@ namespace osu.Game.Screens.Ranking
CornerRadius = 20,
CornerExponent = 2.5f,
Masking = true,
- Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }
+ Children = new[]
+ {
+ middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both },
+ new UserCoverBackground
+ {
+ RelativeSizeAxes = Axes.Both,
+ User = Score.User,
+ Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0))
+ },
+ }
},
middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both }
}
@@ -155,18 +165,10 @@ namespace osu.Game.Screens.Ranking
{
base.LoadComplete();
- if (state == PanelState.Expanded)
- {
- topLayerBackground.FadeColour(expanded_top_layer_colour);
- middleLayerBackground.FadeColour(expanded_middle_layer_colour);
- }
- else
- {
- topLayerBackground.FadeColour(contracted_top_layer_colour);
- middleLayerBackground.FadeColour(contracted_middle_layer_colour);
- }
-
updateState();
+
+ topLayerBackground.FinishTransforms(false, nameof(Colour));
+ middleLayerBackground.FinishTransforms(false, nameof(Colour));
}
private PanelState state = PanelState.Contracted;
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
index 3e4798a812..c559b4f8f5 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
+using System.Threading;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -41,6 +43,12 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved(CanBeNull = true)]
private BeatmapSetOverlay beatmapOverlay { get; set; }
+ [Resolved]
+ private BeatmapDifficultyManager difficultyManager { get; set; }
+
+ private IBindable starDifficultyBindable;
+ private CancellationTokenSource starDifficultyCancellationSource;
+
public DrawableCarouselBeatmap(CarouselBeatmap panel)
: base(panel)
{
@@ -137,7 +145,6 @@ namespace osu.Game.Screens.Select.Carousel
},
starCounter = new StarCounter
{
- Current = (float)beatmap.StarDifficulty,
Scale = new Vector2(0.8f),
}
}
@@ -181,6 +188,16 @@ namespace osu.Game.Screens.Select.Carousel
if (Item.State.Value != CarouselItemState.Collapsed && Alpha == 0)
starCounter.ReplayAnimation();
+ starDifficultyCancellationSource?.Cancel();
+
+ // Only compute difficulty when the item is visible.
+ if (Item.State.Value != CarouselItemState.Collapsed)
+ {
+ // We've potentially cancelled the computation above so a new bindable is required.
+ starDifficultyBindable = difficultyManager.GetBindableDifficulty(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token);
+ starDifficultyBindable.BindValueChanged(d => starCounter.Current = (float)d.NewValue.Stars, true);
+ }
+
base.ApplyState();
}
@@ -205,5 +222,11 @@ namespace osu.Game.Screens.Select.Carousel
return items.ToArray();
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ starDifficultyCancellationSource?.Cancel();
+ }
}
}
diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs
index 02822ea608..44c328187f 100644
--- a/osu.Game/Screens/Select/Details/AdvancedStats.cs
+++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs
@@ -14,10 +14,12 @@ using osu.Framework.Bindables;
using System.Collections.Generic;
using osu.Game.Rulesets.Mods;
using System.Linq;
+using System.Threading;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets;
namespace osu.Game.Screens.Select.Details
{
@@ -26,6 +28,12 @@ namespace osu.Game.Screens.Select.Details
[Resolved]
private IBindable> mods { get; set; }
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
+ [Resolved]
+ private BeatmapDifficultyManager difficultyManager { get; set; }
+
protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
private readonly StatisticRow starDifficulty;
@@ -71,6 +79,7 @@ namespace osu.Game.Screens.Select.Details
{
base.LoadComplete();
+ ruleset.BindValueChanged(_ => updateStatistics());
mods.BindValueChanged(modsChanged, true);
}
@@ -132,11 +141,39 @@ namespace osu.Game.Screens.Select.Details
break;
}
- starDifficulty.Value = ((float)(Beatmap?.StarDifficulty ?? 0), null);
-
HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate);
Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty);
ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate);
+
+ updateStarDifficulty();
+ }
+
+ private IBindable normalStarDifficulty;
+ private IBindable moddedStarDifficulty;
+ private CancellationTokenSource starDifficultyCancellationSource;
+
+ private void updateStarDifficulty()
+ {
+ starDifficultyCancellationSource?.Cancel();
+
+ if (Beatmap == null)
+ return;
+
+ starDifficultyCancellationSource = new CancellationTokenSource();
+
+ normalStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token);
+ moddedStarDifficulty = difficultyManager.GetBindableDifficulty(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token);
+
+ normalStarDifficulty.BindValueChanged(_ => updateDisplay());
+ moddedStarDifficulty.BindValueChanged(_ => updateDisplay(), true);
+
+ void updateDisplay() => starDifficulty.Value = ((float)normalStarDifficulty.Value.Stars, (float)moddedStarDifficulty.Value.Stars);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ starDifficultyCancellationSource?.Cancel();
}
public class StatisticRow : Container, IHasAccentColour
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index 6b4af21b37..61d0112c89 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osuTK.Graphics;
@@ -21,7 +22,7 @@ namespace osu.Game.Skinning
public override Drawable GetDrawableComponent(ISkinComponent component) => null;
- public override Texture GetTexture(string componentName) => null;
+ public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public override SampleChannel GetSample(ISampleInfo sampleInfo) => null;
diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs
index 4ed5ef64c3..7e52bb8176 100644
--- a/osu.Game/Skinning/IAnimationTimeReference.cs
+++ b/osu.Game/Skinning/IAnimationTimeReference.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Timing;
namespace osu.Game.Skinning
@@ -11,7 +12,7 @@ namespace osu.Game.Skinning
///
///
/// This should not be used to start an animation immediately at the current time.
- /// To do so, use with startAtCurrentTime = true instead.
+ /// To do so, use with startAtCurrentTime = true instead.
///
[Cached]
public interface IAnimationTimeReference
diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs
index cb2a379b8e..5abd963773 100644
--- a/osu.Game/Skinning/ISkin.cs
+++ b/osu.Game/Skinning/ISkin.cs
@@ -5,6 +5,7 @@ using JetBrains.Annotations;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -29,7 +30,17 @@ namespace osu.Game.Skinning
/// The requested texture.
/// A matching texture, or null if unavailable.
[CanBeNull]
- Texture GetTexture(string componentName);
+ Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
+
+ ///
+ /// Retrieve a .
+ ///
+ /// The requested texture.
+ /// The texture wrap mode in horizontal direction.
+ /// The texture wrap mode in vertical direction.
+ /// A matching texture, or null if unavailable.
+ [CanBeNull]
+ Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
///
/// Retrieve a .
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 4b70ccc6ad..3bbeff9918 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -11,6 +11,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Audio;
@@ -311,17 +312,17 @@ namespace osu.Game.Skinning
return this.GetAnimation(component.LookupName, false, false);
}
- public override Texture GetTexture(string componentName)
+ public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
foreach (var name in getFallbackNames(componentName))
{
float ratio = 2;
- var texture = Textures?.Get($"{name}@2x");
+ var texture = Textures?.Get($"{name}@2x", wrapModeS, wrapModeT);
if (texture == null)
{
ratio = 1;
- texture = Textures?.Get(name);
+ texture = Textures?.Get(name, wrapModeS, wrapModeT);
}
if (texture == null)
diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs
index 549571dec4..7cf41ef3c1 100644
--- a/osu.Game/Skinning/LegacySkinExtensions.cs
+++ b/osu.Game/Skinning/LegacySkinExtensions.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
@@ -15,6 +16,11 @@ namespace osu.Game.Skinning
{
public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
bool startAtCurrentTime = true, double? frameLength = null)
+ => source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength);
+
+ public static Drawable GetAnimation(this ISkin source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, bool looping, bool applyConfigFrameRate = false,
+ string animationSeparator = "-",
+ bool startAtCurrentTime = true, double? frameLength = null)
{
Texture texture;
@@ -38,7 +44,7 @@ namespace osu.Game.Skinning
}
// if an animation was not allowed or not found, fall back to a sprite retrieval.
- if ((texture = source.GetTexture(componentName)) != null)
+ if ((texture = source.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
return new Sprite { Texture = texture };
return null;
@@ -47,7 +53,7 @@ namespace osu.Game.Skinning
{
for (int i = 0; true; i++)
{
- if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}")) == null)
+ if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}", wrapModeS, wrapModeT)) == null)
break;
yield return texture;
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 94a7a32f05..786056b932 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -4,6 +4,7 @@
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Legacy;
@@ -27,7 +28,10 @@ namespace osu.Game.Skinning
public abstract Drawable GetDrawableComponent(ISkinComponent component);
- public Texture GetTexture(string componentName) => Source.GetTexture(componentName);
+ public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
+
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
+ => Source.GetTexture(componentName, wrapModeS, wrapModeT);
public virtual SampleChannel GetSample(ISampleInfo sampleInfo)
{
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index fa4aebd8a5..4b0cf02c0a 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -20,7 +21,9 @@ namespace osu.Game.Skinning
public abstract SampleChannel GetSample(ISampleInfo sampleInfo);
- public abstract Texture GetTexture(string componentName);
+ public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
+
+ public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
public abstract IBindable GetConfig(TLookup lookup);
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index d65c74ef62..e1f713882a 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -13,6 +13,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
@@ -130,7 +131,7 @@ namespace osu.Game.Skinning
public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component);
- public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => CurrentSkin.Value.GetTexture(componentName, wrapModeS, wrapModeT);
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs
index 1c01bbf1ab..adf62ed452 100644
--- a/osu.Game/Skinning/SkinProvidingContainer.cs
+++ b/osu.Game/Skinning/SkinProvidingContainer.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -47,13 +48,13 @@ namespace osu.Game.Skinning
return fallbackSource?.GetDrawableComponent(component);
}
- public Texture GetTexture(string componentName)
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
Texture sourceTexture;
- if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName)) != null)
+ if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
return sourceTexture;
- return fallbackSource?.GetTexture(componentName);
+ return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT);
}
public SampleChannel GetSample(ISampleInfo sampleInfo)
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index 24d6648273..27f6c37895 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Game.Audio;
+using osu.Game.Screens.Play;
namespace osu.Game.Skinning
{
@@ -22,6 +23,13 @@ namespace osu.Game.Skinning
[Resolved]
private ISampleStore samples { get; set; }
+ private bool requestedPlaying;
+
+ public override bool RemoveWhenNotAlive => false;
+ public override bool RemoveCompletedTransforms => false;
+
+ private readonly AudioContainer samplesContainer;
+
public SkinnableSound(ISampleInfo hitSamples)
: this(new[] { hitSamples })
{
@@ -33,9 +41,99 @@ namespace osu.Game.Skinning
InternalChild = samplesContainer = new AudioContainer();
}
+ private Bindable gameplayClockPaused;
+
+ [BackgroundDependencyLoader(true)]
+ private void load(GameplayClock gameplayClock)
+ {
+ // if in a gameplay context, pause sample playback when gameplay is paused.
+ gameplayClockPaused = gameplayClock?.IsPaused.GetBoundCopy();
+ gameplayClockPaused?.BindValueChanged(paused =>
+ {
+ if (requestedPlaying)
+ {
+ if (paused.NewValue)
+ stop();
+ // it's not easy to know if a sample has finished playing (to end).
+ // to keep things simple only resume playing looping samples.
+ else if (Looping)
+ play();
+ }
+ });
+ }
+
private bool looping;
- private readonly AudioContainer samplesContainer;
+ public bool Looping
+ {
+ get => looping;
+ set
+ {
+ if (value == looping) return;
+
+ looping = value;
+
+ samplesContainer.ForEach(c => c.Looping = looping);
+ }
+ }
+
+ public void Play()
+ {
+ requestedPlaying = true;
+ play();
+ }
+
+ private void play()
+ {
+ samplesContainer.ForEach(c =>
+ {
+ if (c.AggregateVolume.Value > 0)
+ c.Play();
+ });
+ }
+
+ public void Stop()
+ {
+ requestedPlaying = false;
+ stop();
+ }
+
+ private void stop()
+ {
+ samplesContainer.ForEach(c => c.Stop());
+ }
+
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ var channels = hitSamples.Select(s =>
+ {
+ var ch = skin.GetSample(s);
+
+ if (ch == null && allowFallback)
+ {
+ foreach (var lookup in s.LookupNames)
+ {
+ if ((ch = samples.Get($"Gameplay/{lookup}")) != null)
+ break;
+ }
+ }
+
+ if (ch != null)
+ {
+ ch.Looping = looping;
+ ch.Volume.Value = s.Volume / 100.0;
+ }
+
+ return ch;
+ }).Where(c => c != null);
+
+ samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
+
+ if (requestedPlaying)
+ Play();
+ }
+
+ #region Re-expose AudioContainer
public BindableNumber Volume => samplesContainer.Volume;
@@ -45,6 +143,8 @@ namespace osu.Game.Skinning
public BindableNumber Tempo => samplesContainer.Tempo;
+ public bool IsPlaying => samplesContainer.Any(s => s.Playing);
+
///
/// Smoothly adjusts over time.
///
@@ -73,54 +173,6 @@ namespace osu.Game.Skinning
public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) =>
samplesContainer.TempoTo(newTempo, duration, easing);
- public bool Looping
- {
- get => looping;
- set
- {
- if (value == looping) return;
-
- looping = value;
-
- samplesContainer.ForEach(c => c.Looping = looping);
- }
- }
-
- public void Play() => samplesContainer.ForEach(c =>
- {
- if (c.AggregateVolume.Value > 0)
- c.Play();
- });
-
- public void Stop() => samplesContainer.ForEach(c => c.Stop());
-
- public override bool IsPresent => Scheduler.HasPendingTasks;
-
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
- {
- var channels = hitSamples.Select(s =>
- {
- var ch = skin.GetSample(s);
-
- if (ch == null && allowFallback)
- {
- foreach (var lookup in s.LookupNames)
- {
- if ((ch = samples.Get($"Gameplay/{lookup}")) != null)
- break;
- }
- }
-
- if (ch != null)
- {
- ch.Looping = looping;
- ch.Volume.Value = s.Volume / 100.0;
- }
-
- return ch;
- }).Where(c => c != null);
-
- samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
- }
+ #endregion
}
}
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index cb9ed40b00..866fc215d6 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -305,8 +305,10 @@ namespace osu.Game.Tests.Visual
{
double refTime = referenceClock.CurrentTime;
- if (lastReferenceTime.HasValue)
- accumulated += (refTime - lastReferenceTime.Value) * Rate;
+ double? lastRefTime = lastReferenceTime;
+
+ if (lastRefTime != null)
+ accumulated += (refTime - lastRefTime.Value) * Rate;
lastReferenceTime = refTime;
}
diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs
index ea7cdaaac6..81c13112d0 100644
--- a/osu.Game/Tests/Visual/SkinnableTestScene.cs
+++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
@@ -157,7 +158,7 @@ namespace osu.Game.Tests.Visual
this.extrapolateAnimations = extrapolateAnimations;
}
- public override Texture GetTexture(string componentName)
+ public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
// extrapolate frames to test longer animations
if (extrapolateAnimations)
@@ -165,10 +166,10 @@ namespace osu.Game.Tests.Visual
var match = Regex.Match(componentName, "-([0-9]*)");
if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60)
- return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"));
+ return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT);
}
- return base.GetTexture(componentName);
+ return base.GetTexture(componentName, wrapModeS, wrapModeT);
}
}
}
diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs
new file mode 100644
index 0000000000..2604815751
--- /dev/null
+++ b/osu.Game/Users/ExtendedUserPanel.cs
@@ -0,0 +1,148 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Users.Drawables;
+using osu.Framework.Input.Events;
+
+namespace osu.Game.Users
+{
+ public abstract class ExtendedUserPanel : UserPanel
+ {
+ public readonly Bindable Status = new Bindable();
+
+ public readonly IBindable Activity = new Bindable();
+
+ protected TextFlowContainer LastVisitMessage { get; private set; }
+
+ private SpriteIcon statusIcon;
+ private OsuSpriteText statusMessage;
+
+ protected ExtendedUserPanel(User user)
+ : base(user)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter;
+
+ Status.ValueChanged += status => displayStatus(status.NewValue, Activity.Value);
+ Activity.ValueChanged += activity => displayStatus(Status.Value, activity.NewValue);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Status.TriggerChange();
+
+ // Colour should be applied immediately on first load.
+ statusIcon.FinishTransforms();
+ }
+
+ protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar
+ {
+ User = User,
+ OpenOnClick = { Value = false }
+ };
+
+ protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country)
+ {
+ Size = new Vector2(39, 26)
+ };
+
+ protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon
+ {
+ Icon = FontAwesome.Regular.Circle,
+ Size = new Vector2(25)
+ };
+
+ protected FillFlowContainer CreateStatusMessage(bool rightAlignedChildren)
+ {
+ var statusContainer = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical
+ };
+
+ var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft;
+
+ statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text =>
+ {
+ text.Anchor = alignment;
+ text.Origin = alignment;
+ text.AutoSizeAxes = Axes.Both;
+ text.Alpha = 0;
+
+ if (User.LastVisit.HasValue)
+ {
+ text.AddText(@"Last seen ");
+ text.AddText(new DrawableDate(User.LastVisit.Value, italic: false)
+ {
+ Shadow = false
+ });
+ }
+ }));
+
+ statusContainer.Add(statusMessage = new OsuSpriteText
+ {
+ Anchor = alignment,
+ Origin = alignment,
+ Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold)
+ });
+
+ return statusContainer;
+ }
+
+ private void displayStatus(UserStatus status, UserActivity activity = null)
+ {
+ if (status != null)
+ {
+ LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0);
+
+ // Set status message based on activity (if we have one) and status is not offline
+ if (activity != null && !(status is UserStatusOffline))
+ {
+ statusMessage.Text = activity.Status;
+ statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint);
+ return;
+ }
+
+ // Otherwise use only status
+ statusMessage.Text = status.Message;
+ statusIcon.FadeColour(status.GetAppropriateColour(Colours), 500, Easing.OutQuint);
+
+ return;
+ }
+
+ // Fallback to web status if local one is null
+ if (User.IsOnline)
+ {
+ Status.Value = new UserStatusOnline();
+ return;
+ }
+
+ Status.Value = new UserStatusOffline();
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ BorderThickness = 2;
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ BorderThickness = 0;
+ base.OnHoverLost(e);
+ }
+ }
+}
diff --git a/osu.Game/Users/UserBrickPanel.cs b/osu.Game/Users/UserBrickPanel.cs
new file mode 100644
index 0000000000..9ca7768187
--- /dev/null
+++ b/osu.Game/Users/UserBrickPanel.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 osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osuTK;
+
+namespace osu.Game.Users
+{
+ public class UserBrickPanel : UserPanel
+ {
+ public UserBrickPanel(User user)
+ : base(user)
+ {
+ AutoSizeAxes = Axes.Both;
+ CornerRadius = 6;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Background.FadeTo(0.2f);
+ }
+
+ protected override Drawable CreateLayout() => new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(5, 0),
+ Margin = new MarginPadding
+ {
+ Horizontal = 10,
+ Vertical = 3,
+ },
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ new CircularContainer
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Masking = true,
+ Width = 4,
+ Height = 13,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = string.IsNullOrEmpty(User.Colour) ? Color4Extensions.FromHex("0087ca") : Color4Extensions.FromHex(User.Colour)
+ }
+ },
+ CreateUsername().With(u =>
+ {
+ u.Anchor = Anchor.CentreLeft;
+ u.Origin = Anchor.CentreLeft;
+ u.Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold);
+ })
+ }
+ };
+ }
+}
diff --git a/osu.Game/Users/UserGridPanel.cs b/osu.Game/Users/UserGridPanel.cs
index e62a834d6d..44dcbc305d 100644
--- a/osu.Game/Users/UserGridPanel.cs
+++ b/osu.Game/Users/UserGridPanel.cs
@@ -9,7 +9,7 @@ using osuTK;
namespace osu.Game.Users
{
- public class UserGridPanel : UserPanel
+ public class UserGridPanel : ExtendedUserPanel
{
private const int margin = 10;
diff --git a/osu.Game/Users/UserListPanel.cs b/osu.Game/Users/UserListPanel.cs
index 1c3ae20577..9c95eff739 100644
--- a/osu.Game/Users/UserListPanel.cs
+++ b/osu.Game/Users/UserListPanel.cs
@@ -12,7 +12,7 @@ using osu.Game.Overlays.Profile.Header.Components;
namespace osu.Game.Users
{
- public class UserListPanel : UserPanel
+ public class UserListPanel : ExtendedUserPanel
{
public UserListPanel(User user)
: base(user)
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 6f59f9e443..94c0c31cfc 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -2,9 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osuTK;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -14,11 +12,8 @@ using osu.Game.Overlays;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Containers;
-using osu.Game.Users.Drawables;
using JetBrains.Annotations;
-using osu.Framework.Input.Events;
namespace osu.Game.Users
{
@@ -26,21 +21,12 @@ namespace osu.Game.Users
{
public readonly User User;
- public readonly Bindable Status = new Bindable();
-
- public readonly IBindable Activity = new Bindable();
-
public new Action Action;
protected Action ViewProfile { get; private set; }
protected DelayedLoadUnloadWrapper Background { get; private set; }
- protected TextFlowContainer LastVisitMessage { get; private set; }
-
- private SpriteIcon statusIcon;
- private OsuSpriteText statusMessage;
-
protected UserPanel(User user)
{
if (user == null)
@@ -53,23 +39,22 @@ namespace osu.Game.Users
private UserProfileOverlay profileOverlay { get; set; }
[Resolved(canBeNull: true)]
- private OverlayColourProvider colourProvider { get; set; }
+ protected OverlayColourProvider ColourProvider { get; private set; }
[Resolved]
- private OsuColour colours { get; set; }
+ protected OsuColour Colours { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
Masking = true;
- BorderColour = colourProvider?.Light1 ?? colours.GreyVioletLighter;
AddRange(new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = colourProvider?.Background5 ?? colours.Gray1
+ Colour = ColourProvider?.Background5 ?? Colours.Gray1
},
Background = new DelayedLoadUnloadWrapper(() => new UserCoverBackground
{
@@ -86,9 +71,6 @@ namespace osu.Game.Users
CreateLayout()
});
- Status.ValueChanged += status => displayStatus(status.NewValue, Activity.Value);
- Activity.ValueChanged += activity => displayStatus(Status.Value, activity.NewValue);
-
base.Action = ViewProfile = () =>
{
Action?.Invoke();
@@ -96,41 +78,9 @@ namespace osu.Game.Users
};
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- Status.TriggerChange();
-
- // Colour should be applied immediately on first load.
- statusIcon.FinishTransforms();
- }
-
- protected override bool OnHover(HoverEvent e)
- {
- BorderThickness = 2;
- return base.OnHover(e);
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- BorderThickness = 0;
- base.OnHoverLost(e);
- }
-
[NotNull]
protected abstract Drawable CreateLayout();
- protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar
- {
- User = User,
- OpenOnClick = { Value = false }
- };
-
- protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country)
- {
- Size = new Vector2(39, 26)
- };
-
protected OsuSpriteText CreateUsername() => new OsuSpriteText
{
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold),
@@ -138,80 +88,6 @@ namespace osu.Game.Users
Text = User.Username,
};
- protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon
- {
- Icon = FontAwesome.Regular.Circle,
- Size = new Vector2(25)
- };
-
- protected FillFlowContainer CreateStatusMessage(bool rightAlignedChildren)
- {
- var statusContainer = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical
- };
-
- var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft;
-
- statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text =>
- {
- text.Anchor = alignment;
- text.Origin = alignment;
- text.AutoSizeAxes = Axes.Both;
- text.Alpha = 0;
-
- if (User.LastVisit.HasValue)
- {
- text.AddText(@"Last seen ");
- text.AddText(new DrawableDate(User.LastVisit.Value, italic: false)
- {
- Shadow = false
- });
- }
- }));
-
- statusContainer.Add(statusMessage = new OsuSpriteText
- {
- Anchor = alignment,
- Origin = alignment,
- Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold)
- });
-
- return statusContainer;
- }
-
- private void displayStatus(UserStatus status, UserActivity activity = null)
- {
- if (status != null)
- {
- LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0);
-
- // Set status message based on activity (if we have one) and status is not offline
- if (activity != null && !(status is UserStatusOffline))
- {
- statusMessage.Text = activity.Status;
- statusIcon.FadeColour(activity.GetAppropriateColour(colours), 500, Easing.OutQuint);
- return;
- }
-
- // Otherwise use only status
- statusMessage.Text = status.Message;
- statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint);
-
- return;
- }
-
- // Fallback to web status if local one is null
- if (User.IsOnline)
- {
- Status.Value = new UserStatusOnline();
- return;
- }
-
- Status.Value = new UserStatusOffline();
- }
-
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("View Profile", MenuItemType.Highlighted, ViewProfile),
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 2f3d08c528..5ac54a853f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -24,10 +24,10 @@
-
-
-
-
+
+
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 2bb3914c25..8b2d1346be 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -80,8 +80,8 @@
-
-
+
+