Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModBadApple.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
});
}
}
1 change: 1 addition & 0 deletions osu.Game.Rulesets.Catch/CatchRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new CatchModMuted(),
new CatchModNoScope(),
new CatchModMovingFast(),
new CatchModBadApple(),
};

case ModType.System:
Expand Down
69 changes: 69 additions & 0 deletions osu.Game.Rulesets.Catch/Mods/CatchModBadApple.cs
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mod should be in "Conversion" category since it fundamentally alters goals of gameplay. This has tagging / medal implications.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yea I guess that makes sense, will adjust

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<CatchHitObject>, 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<double> hitSoundVolume = new BindableDouble(0);

public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> 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);
Copy link
Copy Markdown
Collaborator

@bdach bdach Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what this is going for, but the visual effects of this change mean that this mod looks terrible on argon and legacy skins, and a little less terrible on triangles.

See video below. Fruit disappear instantly on miss and slowly fade out on catch which is all types of wrong IMO.

Screen.Recording.2026-03-23.at.08.48.18.mov

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to change the behaviour and see what I can do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of constructive feedback, in a perfect world I think dodged fruits could explode in some way to indicate hit, while missed fruits... not sure. Maybe missed fruits could stay on the catcher? Could be a fun idea as a way of punishing the player for catches, but maybe overly punishing if you catch too much.

Might not be very easy to pull any of that though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026-03-24.23-35-41.mp4

For now this is how I was able to get it to work, i should probably delete the hitlightning too since its kinda off personally but im not sure

}

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)

Check warning

Code scanning / InspectCode

Incorrect blank lines: Blank lines are missing elsewhere Warning

Blank lines are missing, expected minimum 1 instead of 0
{
// Block hyperdashing to avoid hyperdashes when two objects appear at the same time.
catcher.SetHyperDashState(1, -1);
Comment on lines +65 to +66
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty terrible and in general I don't know that having hyperfruit at all here is a good idea.

Rather than doing whatever this is doing, I'd implement IApplicableToBeatmap and forcefully set HyperDash = false, HyperDashTarget = null to all objects in the beatmap to just turn off hyperdash mechanics entirely.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea that makes more sense, will do

}
}
}
Loading