diff --git a/osu.Android.props b/osu.Android.props
index 85e8cb9ceb..969eb205e0 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,6 @@
osu.licenseheader
-
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 2d940479f3..32c9e913c6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
+
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) };
+
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index 62b5ecfd58..e786ec86f9 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
- public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
new file mode 100644
index 0000000000..7e20feba02
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -0,0 +1,73 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Bindables;
+using System.Collections.Generic;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ internal class OsuModTraceable : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
+ {
+ public override string Name => "Traceable";
+ public override string Acronym => "TC";
+ public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "Put your faith in the approach circles...";
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) };
+ private Bindable increaseFirstObjectVisibility = new Bindable();
+
+ public void ReadFromConfig(OsuConfigManager config)
+ {
+ increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
+ }
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
+ drawable.ApplyCustomUpdateState += ApplyTraceableState;
+ }
+
+ protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state)
+ {
+ if (!(drawable is DrawableOsuHitObject drawableOsu))
+ return;
+
+ var h = drawableOsu.HitObject;
+
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ // we only want to see the approach circle
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ circle.CirclePiece.Hide();
+
+ break;
+
+ case DrawableSlider slider:
+ slider.AccentColour.BindValueChanged(_ =>
+ {
+ //will trigger on skin change.
+ slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
+ slider.Body.BorderColour = slider.AccentColour.Value;
+ }, true);
+
+ break;
+
+ case DrawableSpinner spinner:
+ spinner.Disc.Hide();
+ spinner.Background.Hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
index e926ade41b..923278f484 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private Bindable increaseFirstObjectVisibility = new Bindable();
- public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) };
public void ReadFromConfig(OsuConfigManager config)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 83646c561d..c90f230f93 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{
- public ApproachCircle ApproachCircle;
+ public ApproachCircle ApproachCircle { get; }
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly HitArea hitArea;
- private readonly SkinnableDrawable mainContent;
+ public SkinnableDrawable CirclePiece { get; }
public DrawableHitCircle(HitCircle h)
: base(h)
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
- mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
+ CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateInitialTransforms();
- mainContent.FadeInFromZero(HitObject.TimeFadeIn);
+ CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 08b43b0345..643a0f7336 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private float sliderPathRadius;
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{
- base.SkinChanged(skin, allowFallback);
+ base.ApplySkin(skin, allowFallback);
Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index df2ae81a5a..fa69cec78d 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -139,6 +139,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModSpinIn(),
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
new MultiMod(new ModWindUp(), new ModWindDown()),
+ new OsuModTraceable(),
};
case ModType.System:
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index d57ec44f39..cca6301b02 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -6,8 +6,6 @@ using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
-using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
@@ -17,9 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty();
-
- var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty());
- return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
+ return new FailPlayer();
}
protected override void AddCheckSteps()
@@ -29,16 +25,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
}
- private class FailPlayer : ReplayPlayer
+ private class FailPlayer : TestPlayer
{
- public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
-
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
- protected override bool PauseOnFocusLost => false;
-
- public FailPlayer(Score score)
- : base(score, false, false)
+ public FailPlayer()
+ : base(false, false)
{
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
index 3fbce9d43c..36335bc54a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
@@ -26,12 +26,14 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
+ AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
+ public new bool AllowFail => base.AllowFail;
protected override bool PauseOnFocusLost => false;
diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
index 539601c359..5b528c5ab2 100644
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ b/osu.Game/Overlays/Music/PlaylistList.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
@@ -18,7 +19,6 @@ namespace osu.Game.Overlays.Music
public class PlaylistList : CompositeDrawable
{
public Action Selected;
- public Action OrderChanged;
private readonly ItemsScrollContainer items;
@@ -28,7 +28,6 @@ namespace osu.Game.Overlays.Music
{
RelativeSizeAxes = Axes.Both,
Selected = set => Selected?.Invoke(set),
- OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
};
}
@@ -45,13 +44,17 @@ namespace osu.Game.Overlays.Music
private class ItemsScrollContainer : OsuScrollContainer
{
public Action Selected;
- public Action OrderChanged;
private readonly SearchContainer search;
private readonly FillFlowContainer items;
private readonly IBindable beatmapBacking = new Bindable();
+ private IBindableList beatmaps;
+
+ [Resolved]
+ private MusicController musicController { get; set; }
+
public ItemsScrollContainer()
{
Children = new Drawable[]
@@ -73,27 +76,35 @@ namespace osu.Game.Overlays.Music
}
[BackgroundDependencyLoader]
- private void load(BeatmapManager beatmaps, IBindable beatmap)
+ private void load(IBindable beatmap)
{
- beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet);
- beatmaps.ItemAdded += addBeatmapSet;
- beatmaps.ItemRemoved += removeBeatmapSet;
+ beatmaps = musicController.BeatmapSets.GetBoundCopy();
+ beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet);
+ beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet);
+ beatmaps.ForEach(addBeatmapSet);
beatmapBacking.BindTo(beatmap);
beatmapBacking.ValueChanged += _ => updateSelectedSet();
}
- private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() =>
+ private void addBeatmapSet(BeatmapSetInfo obj)
{
- items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) });
- });
+ if (obj == draggedItem?.BeatmapSetInfo) return;
- private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() =>
+ Schedule(() => items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }));
+ }
+
+ private void removeBeatmapSet(BeatmapSetInfo obj)
{
- var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
- if (itemToRemove != null)
- items.Remove(itemToRemove);
- });
+ if (obj == draggedItem?.BeatmapSetInfo) return;
+
+ Schedule(() =>
+ {
+ var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
+ if (itemToRemove != null)
+ items.Remove(itemToRemove);
+ });
+ }
private void updateSelectedSet()
{
@@ -112,6 +123,8 @@ namespace osu.Game.Overlays.Music
private Vector2 nativeDragPosition;
private PlaylistItem draggedItem;
+ private int? dragDestination;
+
protected override bool OnDragStart(DragStartEvent e)
{
nativeDragPosition = e.ScreenSpaceMousePosition;
@@ -131,10 +144,17 @@ namespace osu.Game.Overlays.Music
protected override bool OnDragEnd(DragEndEvent e)
{
nativeDragPosition = e.ScreenSpaceMousePosition;
- var handled = draggedItem != null || base.OnDragEnd(e);
- draggedItem = null;
- return handled;
+ if (draggedItem == null)
+ return base.OnDragEnd(e);
+
+ if (dragDestination != null)
+ musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value);
+
+ draggedItem = null;
+ dragDestination = null;
+
+ return true;
}
protected override void Update()
@@ -210,7 +230,7 @@ namespace osu.Game.Overlays.Music
}
items.SetLayoutPosition(draggedItem, dstIndex);
- OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex);
+ dragDestination = dstIndex;
}
private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index ec3d708645..ae81a6c117 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.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;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -22,12 +21,6 @@ namespace osu.Game.Overlays.Music
private const float transition_duration = 600;
private const float playlist_height = 510;
- ///
- /// Invoked when the order of an item in the list has changed.
- /// The second parameter indicates the new index of the item.
- ///
- public Action OrderChanged;
-
private readonly Bindable beatmap = new Bindable();
private BeatmapManager beatmaps;
@@ -65,7 +58,6 @@ namespace osu.Game.Overlays.Music
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
Selected = itemSelected,
- OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
},
filter = new FilterControl
{
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index 6ad147735b..db94b0278f 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Framework.MathUtils;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
@@ -24,7 +25,9 @@ namespace osu.Game.Overlays
[Resolved]
private BeatmapManager beatmaps { get; set; }
- private List beatmapSets;
+ public IBindableList BeatmapSets => beatmapSets;
+
+ private readonly BindableList beatmapSets = new BindableList();
public bool IsUserPaused { get; private set; }
@@ -46,7 +49,7 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load()
{
- beatmapSets = beatmaps.GetAllUsableBeatmapSets();
+ beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
}
@@ -140,7 +143,7 @@ namespace osu.Game.Overlays
{
queuedDirection = TrackChangeDirection.Prev;
- var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
+ var playable = BeatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? BeatmapSets.LastOrDefault();
if (playable != null)
{
@@ -165,7 +168,7 @@ namespace osu.Game.Overlays
if (!instant)
queuedDirection = TrackChangeDirection.Next;
- var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
+ var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault();
if (playable != null)
{
@@ -200,8 +203,8 @@ namespace osu.Game.Overlays
else
{
//figure out the best direction based on order in playlist.
- var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
- var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
+ var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
+ var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
}
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index cf42c8005a..6b79f2af07 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -81,7 +81,6 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.X,
Y = player_height + 10,
- OrderChanged = musicController.ChangeBeatmapSetPosition
},
playerContainer = new Container
{
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 00b57f7249..b94de0df89 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
#endregion
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
@@ -250,6 +250,20 @@ namespace osu.Game.Rulesets.Objects.Drawables
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
}
+
+ ApplySkin(skin, allowFallback);
+
+ if (IsLoaded)
+ updateState(State.Value, true);
+ }
+
+ ///
+ /// Called when a change is made to the skin.
+ ///
+ /// The new skin.
+ /// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
+ protected virtual void ApplySkin(ISkinSource skin, bool allowFallback)
+ {
}
///
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index dac5561a45..44be73b089 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -86,6 +86,12 @@ namespace osu.Game.Screens.Play
[Cached(Type = typeof(IBindable>))]
protected new readonly Bindable> Mods = new Bindable>(Array.Empty());
+ ///
+ /// Whether failing should be allowed.
+ /// By default, this checks whether all selected mods allow failing.
+ ///
+ protected virtual bool AllowFail => Mods.Value.OfType().All(m => m.AllowFail);
+
private readonly bool allowPause;
private readonly bool showResults;
@@ -360,9 +366,7 @@ namespace osu.Game.Screens.Play
private bool onFail()
{
- var failOverrideMods = Mods.Value.OfType();
-
- if (failOverrideMods.Any(m => !m.AllowFail))
+ if (!AllowFail)
return false;
HasFailed = true;
@@ -374,7 +378,8 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide();
failAnimation.Start();
- if (failOverrideMods.Any(m => m.RestartOnFail))
+
+ if (Mods.Value.OfType().Any(m => m.RestartOnFail))
Restart();
return true;
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index a9c0ee3a15..b040549efc 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -9,6 +9,9 @@ namespace osu.Game.Screens.Play
{
private readonly Score score;
+ // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
+ protected override bool AllowFail => false;
+
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
: base(allowPause, showResults)
{
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index 98f158c725..4b6eea6b6e 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -13,6 +13,13 @@ namespace osu.Game.Skinning
: base(Info, storage, audioManager, string.Empty)
{
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
+ Configuration.ComboColours.AddRange(new[]
+ {
+ new Color4(255, 192, 0, 255),
+ new Color4(0, 202, 0, 255),
+ new Color4(18, 124, 255, 255),
+ new Color4(242, 24, 57, 255),
+ });
}
public static SkinInfo Info { get; } = new SkinInfo