1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 04:02:49 +08:00
Files
osu-lazer/osu.Game/Rulesets/Mods/ModMuted.cs
T
Linus Genz dfa7ac2082 Specialise mod setting hover text in song select scoreboard (#36391)
- resolves #35992

**Changes**

Introduced GetSettingTooltipText, which returns the value of the
respective setting. The function can be overwritten, allowing to
implement specific behavior. This allows you to define that a value of 0
for the “Muted” mod will instead display “always muted.”
In the same step, I also adjusted the "NoScope" mod, where we had the
same problem.
Wherever we want to implement specialization like this, we can simply
overwrite GetSettingTooltipText to customize the behavior for the mod
settings of the mod.

**Result**
<img width="2560" height="1440" alt="2026-01-19-122307_hyprshot"
src="https://github.com/user-attachments/assets/20e3aa9a-aa6f-4284-9cf1-3092f52c021d"
/>
<img width="2560" height="1440" alt="2026-01-19-122324_hyprshot"
src="https://github.com/user-attachments/assets/6f3fac5b-2a5d-4dd9-a56c-4b09c02cbcca"
/>
<img width="2560" height="1440" alt="2026-01-19-155307_hyprshot"
src="https://github.com/user-attachments/assets/f9ee75d3-c200-4536-9ee9-d20ddbd9fa44"
/>

Signed-off-by: Linus Genz <linuslinuxgenz@gmail.com>
2026-01-20 16:51:23 +09:00

119 lines
4.8 KiB
C#

// 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.Linq;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModMuted : Mod
{
public override string Name => "Muted";
public override string Acronym => "MU";
public override IconUsage? Icon => OsuIcon.ModMuted;
public override LocalisableString Description => "Can you still feel the rhythm without music?";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override bool ValidForFreestyleAsRequiredMod => true;
}
public abstract class ModMuted<TObject> : ModMuted, IApplicableToDrawableRuleset<TObject>, IApplicableToTrack, IApplicableToScoreProcessor
where TObject : HitObject
{
private readonly BindableNumber<double> mainVolumeAdjust = new BindableDouble(0.5);
private readonly BindableNumber<double> metronomeVolumeAdjust = new BindableDouble(0.5);
private readonly BindableNumber<int> currentCombo = new BindableInt();
[SettingSource("Start muted", "Increase volume as combo builds.")]
public BindableBool InverseMuting { get; } = new BindableBool();
[SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")]
public BindableBool EnableMetronome { get; } = new BindableBool(true);
[SettingSource("Final volume at combo", "The combo count at which point the track reaches its final volume.", SettingControlType = typeof(SettingsSlider<int, MuteComboSlider>))]
public BindableInt MuteComboCount { get; } = new BindableInt(100)
{
MinValue = 0,
MaxValue = 500,
};
[SettingSource("Mute hit sounds", "Hit sounds are also muted alongside the track.")]
public BindableBool AffectsHitSounds { get; } = new BindableBool(true);
protected ModMuted()
{
InverseMuting.BindValueChanged(i => MuteComboCount.MinValue = i.NewValue ? 1 : 0, true);
}
public void ApplyToTrack(IAdjustableAudioComponent track)
{
track.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
}
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
{
if (EnableMetronome.Value)
{
MetronomeBeat metronomeBeat;
// Importantly, this is added to FrameStableComponents and not Overlays as the latter would cause it to be self-muted by the mod's volume adjustment.
drawableRuleset.FrameStableComponents.Add(metronomeBeat = new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime));
metronomeBeat.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust);
}
if (AffectsHitSounds.Value)
drawableRuleset.Audio.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
}
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
currentCombo.BindTo(scoreProcessor.Combo);
currentCombo.BindValueChanged(combo =>
{
double dimFactor = MuteComboCount.Value == 0 ? 1 : (double)combo.NewValue / MuteComboCount.Value;
if (InverseMuting.Value)
dimFactor = 1 - dimFactor;
scoreProcessor.TransformBindableTo(metronomeVolumeAdjust, dimFactor, 500, Easing.OutQuint);
scoreProcessor.TransformBindableTo(mainVolumeAdjust, 1 - dimFactor, 500, Easing.OutQuint);
}, true);
}
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
protected override LocalisableString GetSettingTooltipText(IBindable bindable)
{
if (ReferenceEquals(bindable, MuteComboCount))
return MuteComboSlider.FormatMuteComboValue(MuteComboCount.Value);
return base.GetSettingTooltipText(bindable);
}
}
public partial class MuteComboSlider : RoundedSliderBar<int>
{
public override LocalisableString TooltipText => FormatMuteComboValue(Current.Value);
public static LocalisableString FormatMuteComboValue(int value)
{
return value == 0 ? "always muted" : value.ToString();
}
}
}