mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 03:19:52 +08:00
ba2ae3218e
Before: https://github.com/user-attachments/assets/95b19bdb-12c5-4ee7-b7e2-f9098aecd2fa After: https://github.com/user-attachments/assets/937081c5-92cb-4bea-a774-e53afbdbb1fb --- This is a subjective diff and can be closed on the spot with no discussion if deemed incorrect. I mostly just want to get it off my list of unresolved forum threads. The catalyst for this change is [this thread](https://osu.ppy.sh/community/forums/topics/2171493?n=1), wherein the complaint is that you can get jumpscared by a slider repeat in a combo because the slider repeats do not fade in with the rest of the combo. Which means that you don't immediately know whether a slider will need to be repeated or not and you have to watch out for potential slider repeats fading in, which could happen at an *arbitrary* time because it's still dependent on the map's original AR. This seems pretty anti-user so I made it fade in with the rest of the combo. The original comment cited "broken layering" which I guess refers to the fact that sliders can repeat *more* than once and the second repeat will show *under the slider head*. It's pretty broken, sure, but I don't think it's *that* bad, and I *do* think that it's better than the current behaviour. The reason it shows only the first two repeats is that there's no point in showing more for reasons that are hopefully obvious.
110 lines
4.0 KiB
C#
110 lines
4.0 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;
|
|
using System.Linq;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Sprites;
|
|
using osu.Framework.Localisation;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Graphics;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Rulesets.Objects.Drawables;
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
|
|
namespace osu.Game.Rulesets.Osu.Mods
|
|
{
|
|
public class OsuModFreezeFrame : Mod, IApplicableToDrawableHitObject, IApplicableToBeatmap
|
|
{
|
|
public override string Name => "Freeze Frame";
|
|
|
|
public override string Acronym => "FR";
|
|
|
|
public override IconUsage? Icon => OsuIcon.ModFreezeFrame;
|
|
|
|
public override double ScoreMultiplier => 1;
|
|
|
|
public override LocalisableString Description => "Burn the notes into your memory.";
|
|
|
|
/// <remarks>
|
|
/// Incompatible with all mods that directly modify or indirectly depend on <see cref="OsuHitObject.TimePreempt"/>, or alter the behaviour of approach circles.
|
|
/// </remarks>
|
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth), typeof(OsuModHidden) }).ToArray();
|
|
|
|
public override ModType Type => ModType.Fun;
|
|
|
|
//mod breaks normal approach circle preempt
|
|
private double originalPreempt;
|
|
|
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
|
{
|
|
var firstHitObject = beatmap.HitObjects.OfType<OsuHitObject>().FirstOrDefault();
|
|
if (firstHitObject == null)
|
|
return;
|
|
|
|
double lastNewComboTime = 0;
|
|
|
|
originalPreempt = firstHitObject.TimePreempt;
|
|
|
|
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
|
{
|
|
if (obj.NewCombo)
|
|
{
|
|
lastNewComboTime = obj.StartTime;
|
|
}
|
|
|
|
applyFadeInAdjustment(obj);
|
|
}
|
|
|
|
void applyFadeInAdjustment(OsuHitObject osuObject)
|
|
{
|
|
if (osuObject is not Spinner)
|
|
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
|
|
|
int repeatCount = 0;
|
|
|
|
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
|
{
|
|
switch (nested)
|
|
{
|
|
// Freezing the SliderTicks doesnt play well with snaking sliders
|
|
case SliderTick:
|
|
break;
|
|
|
|
case SliderRepeat:
|
|
if (repeatCount > 2)
|
|
break;
|
|
|
|
applyFadeInAdjustment(nested);
|
|
repeatCount++;
|
|
break;
|
|
|
|
default:
|
|
applyFadeInAdjustment(nested);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
|
{
|
|
drawableObject.ApplyCustomUpdateState += (drawableHitObject, _) =>
|
|
{
|
|
if (drawableHitObject is not DrawableHitCircle drawableHitCircle) return;
|
|
|
|
var hitCircle = drawableHitCircle.HitObject;
|
|
var approachCircle = drawableHitCircle.ApproachCircle;
|
|
|
|
// Reapply scale, ensuring the AR isn't changed due to the new preempt.
|
|
approachCircle.ClearTransforms(targetMember: nameof(approachCircle.Scale));
|
|
approachCircle.ScaleTo(4 * (float)(hitCircle.TimePreempt / originalPreempt));
|
|
|
|
using (drawableHitCircle.ApproachCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
|
|
approachCircle.ScaleTo(1, hitCircle.TimePreempt).Then().Expire();
|
|
};
|
|
}
|
|
}
|
|
}
|