diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModBadApple.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModBadApple.cs new file mode 100644 index 000000000000..1a2dea4d55e5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModBadApple.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + public partial class TestSceneCatchModBadApple : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + [Test] + public void TestBadApple() => CreateModTest(new ModTestData + { + Mod = new CatchModBadApple(), + PassCondition = () => true + }); + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index eb8cf137faf7..427a16bc0049 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -155,6 +155,7 @@ public override IEnumerable GetModsFor(ModType type) new CatchModMuted(), new CatchModNoScope(), new CatchModMovingFast(), + new CatchModBadApple(), }; case ModType.System: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModBadApple.cs b/osu.Game.Rulesets.Catch/Mods/CatchModBadApple.cs new file mode 100644 index 000000000000..c8afbe3d2a93 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModBadApple.cs @@ -0,0 +1,69 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Framework.Audio; +using osu.Game.Configuration; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModBadApple : Mod, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset, IUpdatableByPlayfield + { + public override string Name => "Bad Apple"; + public override LocalisableString Description => "The fruit has gone bad... dodge it!"; + public override double ScoreMultiplier => 1; + public override string Acronym => "BA"; + public override Type[] IncompatibleMods => new[] { typeof(CatchModAutoplay) }; + + [SettingSource("Mute hit sounds", "Hit sounds become muted.")] + public BindableBool AffectsHitSounds { get; } = new BindableBool(true); + + private Catcher catcher = null!; + private readonly BindableNumber hitSoundVolume = new BindableDouble(0); + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + catcher = ((CatchPlayfield)drawableRuleset.Playfield).Catcher; + + // Don't show caught fruits as they aren't technically being caught. + catcher.CatchFruitOnPlate = false; + + if (AffectsHitSounds.Value) + { + drawableRuleset.Audio.AddAdjustment(AdjustableProperty.Volume, hitSoundVolume); + } + } + + public void ApplyToDrawableHitObject(DrawableHitObject drawable) + { + if (drawable is DrawableCatchHitObject catchHitObject) + { + catchHitObject.CheckPosition = hitObject => !catcher.CanCatch(hitObject); + } + + drawable.ApplyCustomUpdateState += (dho, state) => + { + // Keep the existing transforms when hit. + if (state is not ArmedState.Hit) + return; + + // When "hit", the DHO is faded out, so to let fruits fall after being caught we fade them back in. + dho.FadeIn(); + }; + } + public void Update(Playfield playfield) + { + // Block hyperdashing to avoid hyperdashes when two objects appear at the same time. + catcher.SetHyperDashState(1, -1); + } + } +}