mirror of
https://github.com/ppy/osu.git
synced 2026-05-25 17:10:09 +08:00
131f828e6a
In stable mania, Hard Rock and Easy mods do not work the same way as they do on all of the rulesets. The difference is that mania HR and EZ, rather than apply a multiplier to the map's original Overall Difficulty, apply multipliers to *the durations of hit windows themselves*. Prior to the last release, lazer was oblivious to this reality and just treated mania HR / EZ as it did every other ruleset. Last release, for the sake for gameplay parity across rulesets, the mods in question were adjusted to match stable, but in the process, it started looking like HR / EZ did not change OD anymore. The problem is that they do, but applying a multiplier to the map's OD and applying a multiplier to the hit window duration is not the same thing. The second thing is actually *much harsher* in magnitude, to the point where applying HR to any map is almost guaranteed to exceed "the effective OD" of 10, and applying EZ to any map is almost guaranteed to result in "negative effective OD". This change attempts to convey that reality by displaying "effective OD", similar to what's already done in other rulesets when rate-changing mods are active. Note that the values this will display *do not match* stable *and that is correct*, because stable song select *lies* about the actual impact on OD by just assuming it can treat all rulesets in the same way. --- Would close https://github.com/ppy/osu/issues/34150 I guess. And yes I would like *all of the above* to land on the changelog if possible if this is merged. For further convincing that this makes any semblance of sense please see the following: https://www.desmos.com/calculator/yigt7jycdv
284 lines
10 KiB
C#
284 lines
10 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.Collections.Generic;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Graphics.Sprites;
|
|
using osu.Framework.Input.Bindings;
|
|
using osu.Framework.Localisation;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Beatmaps.Legacy;
|
|
using osu.Game.Graphics;
|
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
|
using osu.Game.Rulesets.Catch.Difficulty;
|
|
using osu.Game.Rulesets.Catch.Edit;
|
|
using osu.Game.Rulesets.Catch.Edit.Setup;
|
|
using osu.Game.Rulesets.Catch.Mods;
|
|
using osu.Game.Rulesets.Catch.Objects;
|
|
using osu.Game.Rulesets.Catch.Replays;
|
|
using osu.Game.Rulesets.Catch.Scoring;
|
|
using osu.Game.Rulesets.Catch.Skinning.Argon;
|
|
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
|
using osu.Game.Rulesets.Catch.UI;
|
|
using osu.Game.Rulesets.Difficulty;
|
|
using osu.Game.Rulesets.Edit;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Rulesets.Replays.Types;
|
|
using osu.Game.Rulesets.Scoring;
|
|
using osu.Game.Rulesets.Scoring.Legacy;
|
|
using osu.Game.Rulesets.UI;
|
|
using osu.Game.Scoring;
|
|
using osu.Game.Screens.Edit.Setup;
|
|
using osu.Game.Screens.Ranking.Statistics;
|
|
using osu.Game.Skinning;
|
|
using osu.Game.Utils;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.Catch
|
|
{
|
|
public class CatchRuleset : Ruleset, ILegacyRuleset
|
|
{
|
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
|
|
|
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
|
|
|
|
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new CatchHealthProcessor(drainStartTime);
|
|
|
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
|
|
|
|
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
|
|
|
|
public const string SHORT_NAME = "fruits";
|
|
|
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
|
|
|
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
|
{
|
|
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
|
|
new KeyBinding(InputKey.Left, CatchAction.MoveLeft),
|
|
new KeyBinding(InputKey.X, CatchAction.MoveRight),
|
|
new KeyBinding(InputKey.Right, CatchAction.MoveRight),
|
|
new KeyBinding(InputKey.Shift, CatchAction.Dash),
|
|
new KeyBinding(InputKey.MouseLeft, CatchAction.Dash),
|
|
};
|
|
|
|
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
|
{
|
|
if (mods.HasFlag(LegacyMods.Nightcore))
|
|
yield return new CatchModNightcore();
|
|
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
|
yield return new CatchModDoubleTime();
|
|
|
|
if (mods.HasFlag(LegacyMods.Perfect))
|
|
yield return new CatchModPerfect();
|
|
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
|
yield return new CatchModSuddenDeath();
|
|
|
|
if (mods.HasFlag(LegacyMods.Cinema))
|
|
yield return new CatchModCinema();
|
|
else if (mods.HasFlag(LegacyMods.Autoplay))
|
|
yield return new CatchModAutoplay();
|
|
|
|
if (mods.HasFlag(LegacyMods.Easy))
|
|
yield return new CatchModEasy();
|
|
|
|
if (mods.HasFlag(LegacyMods.Flashlight))
|
|
yield return new CatchModFlashlight();
|
|
|
|
if (mods.HasFlag(LegacyMods.HalfTime))
|
|
yield return new CatchModHalfTime();
|
|
|
|
if (mods.HasFlag(LegacyMods.HardRock))
|
|
yield return new CatchModHardRock();
|
|
|
|
if (mods.HasFlag(LegacyMods.Hidden))
|
|
yield return new CatchModHidden();
|
|
|
|
if (mods.HasFlag(LegacyMods.NoFail))
|
|
yield return new CatchModNoFail();
|
|
|
|
if (mods.HasFlag(LegacyMods.Relax))
|
|
yield return new CatchModRelax();
|
|
|
|
if (mods.HasFlag(LegacyMods.ScoreV2))
|
|
yield return new ModScoreV2();
|
|
}
|
|
|
|
public override IEnumerable<Mod> GetModsFor(ModType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case ModType.DifficultyReduction:
|
|
return new Mod[]
|
|
{
|
|
new CatchModEasy(),
|
|
new CatchModNoFail(),
|
|
new MultiMod(new CatchModHalfTime(), new CatchModDaycore())
|
|
};
|
|
|
|
case ModType.DifficultyIncrease:
|
|
return new Mod[]
|
|
{
|
|
new CatchModHardRock(),
|
|
new MultiMod(new CatchModSuddenDeath(), new CatchModPerfect()),
|
|
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
|
|
new CatchModHidden(),
|
|
new CatchModFlashlight(),
|
|
new ModAccuracyChallenge(),
|
|
};
|
|
|
|
case ModType.Conversion:
|
|
return new Mod[]
|
|
{
|
|
new CatchModDifficultyAdjust(),
|
|
new CatchModClassic(),
|
|
new CatchModMirror(),
|
|
};
|
|
|
|
case ModType.Automation:
|
|
return new Mod[]
|
|
{
|
|
new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
|
|
new CatchModRelax(),
|
|
};
|
|
|
|
case ModType.Fun:
|
|
return new Mod[]
|
|
{
|
|
new MultiMod(new ModWindUp(), new ModWindDown()),
|
|
new CatchModFloatingFruits(),
|
|
new CatchModMuted(),
|
|
new CatchModNoScope(),
|
|
};
|
|
|
|
case ModType.System:
|
|
return new Mod[]
|
|
{
|
|
new ModScoreV2(),
|
|
};
|
|
|
|
default:
|
|
return Array.Empty<Mod>();
|
|
}
|
|
}
|
|
|
|
public override string Description => "osu!catch";
|
|
|
|
public override string ShortName => SHORT_NAME;
|
|
|
|
public override string PlayingVerb => "Catching fruit";
|
|
|
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
|
|
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
|
{
|
|
return new[]
|
|
{
|
|
HitResult.Great,
|
|
|
|
HitResult.LargeTickHit,
|
|
HitResult.SmallTickHit,
|
|
HitResult.LargeBonus,
|
|
};
|
|
}
|
|
|
|
public override LocalisableString GetDisplayNameForHitResult(HitResult result)
|
|
{
|
|
switch (result)
|
|
{
|
|
case HitResult.LargeTickHit:
|
|
return "Large droplet";
|
|
|
|
case HitResult.SmallTickHit:
|
|
return "Small droplet";
|
|
|
|
case HitResult.LargeBonus:
|
|
return "Banana";
|
|
}
|
|
|
|
return base.GetDisplayNameForHitResult(result);
|
|
}
|
|
|
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(RulesetInfo, beatmap);
|
|
|
|
public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap)
|
|
{
|
|
switch (skin)
|
|
{
|
|
case LegacySkin:
|
|
return new CatchLegacySkinTransformer(skin);
|
|
|
|
case ArgonSkin:
|
|
return new CatchArgonSkinTransformer(skin);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator();
|
|
|
|
public int LegacyID => 2;
|
|
|
|
public ILegacyScoreSimulator CreateLegacyScoreSimulator() => new CatchLegacyScoreSimulator();
|
|
|
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
|
|
|
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
|
|
|
public override IEnumerable<Drawable> CreateEditorSetupSections() =>
|
|
[
|
|
new MetadataSection(),
|
|
new CatchDifficultySection(),
|
|
new FillFlowContainer
|
|
{
|
|
AutoSizeAxes = Axes.Y,
|
|
Direction = FillDirection.Vertical,
|
|
Spacing = new Vector2(SetupScreen.SPACING),
|
|
Children = new Drawable[]
|
|
{
|
|
new ResourcesSection
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
},
|
|
new ColoursSection
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
}
|
|
}
|
|
},
|
|
new DesignSection(),
|
|
];
|
|
|
|
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
|
|
|
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
|
{
|
|
return new[]
|
|
{
|
|
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y
|
|
}),
|
|
};
|
|
}
|
|
|
|
/// <seealso cref="CatchHitObject.ApplyDefaultsToSelf"/>
|
|
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
|
|
{
|
|
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
|
double rate = ModUtils.CalculateRateWithMods(mods);
|
|
|
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
|
|
preempt /= rate;
|
|
adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
|
|
|
|
return adjustedDifficulty;
|
|
}
|
|
|
|
public override bool EditorShowScrollSpeed => false;
|
|
}
|
|
}
|