mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 16:20:14 +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
288 lines
10 KiB
C#
288 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 System.Linq;
|
|
using System.Threading;
|
|
using JetBrains.Annotations;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Extensions;
|
|
using osu.Framework.Extensions.LocalisationExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Sprites;
|
|
using osu.Framework.Localisation;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Configuration;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Graphics.Sprites;
|
|
using osu.Game.Localisation;
|
|
using osu.Game.Localisation.SkinComponents;
|
|
using osu.Game.Resources.Localisation.Web;
|
|
using osu.Game.Rulesets;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Utils;
|
|
|
|
namespace osu.Game.Skinning.Components
|
|
{
|
|
[UsedImplicitly]
|
|
public partial class BeatmapAttributeText : FontAdjustableSkinComponent
|
|
{
|
|
[SettingSource(typeof(BeatmapAttributeTextStrings), nameof(BeatmapAttributeTextStrings.Attribute))]
|
|
public Bindable<BeatmapAttribute> Attribute { get; } = new Bindable<BeatmapAttribute>(BeatmapAttribute.StarRating);
|
|
|
|
[SettingSource(typeof(BeatmapAttributeTextStrings), nameof(BeatmapAttributeTextStrings.Template), nameof(BeatmapAttributeTextStrings.TemplateDescription))]
|
|
public Bindable<string> Template { get; } = new Bindable<string>("{Label}: {Value}");
|
|
|
|
[Resolved]
|
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
|
|
|
private readonly OsuSpriteText text;
|
|
private IBindable<StarDifficulty>? difficultyBindable;
|
|
private CancellationTokenSource? difficultyCancellationSource;
|
|
private ModSettingChangeTracker? modSettingTracker;
|
|
private StarDifficulty? starDifficulty;
|
|
|
|
public BeatmapAttributeText()
|
|
{
|
|
AutoSizeAxes = Axes.Both;
|
|
|
|
InternalChildren = new Drawable[]
|
|
{
|
|
text = new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.CentreLeft,
|
|
Origin = Anchor.CentreLeft,
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
Attribute.BindValueChanged(_ => updateText());
|
|
Template.BindValueChanged(_ => updateText());
|
|
|
|
beatmap.BindValueChanged(b =>
|
|
{
|
|
difficultyCancellationSource?.Cancel();
|
|
difficultyCancellationSource = new CancellationTokenSource();
|
|
|
|
difficultyBindable?.UnbindAll();
|
|
difficultyBindable = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, difficultyCancellationSource.Token);
|
|
difficultyBindable.BindValueChanged(d =>
|
|
{
|
|
starDifficulty = d.NewValue;
|
|
updateText();
|
|
});
|
|
|
|
updateText();
|
|
}, true);
|
|
|
|
mods.BindValueChanged(m =>
|
|
{
|
|
modSettingTracker?.Dispose();
|
|
modSettingTracker = new ModSettingChangeTracker(m.NewValue)
|
|
{
|
|
SettingChanged = _ => updateText()
|
|
};
|
|
|
|
updateText();
|
|
}, true);
|
|
|
|
ruleset.BindValueChanged(_ => updateText());
|
|
|
|
updateText();
|
|
}
|
|
|
|
private void updateText()
|
|
{
|
|
string numberedTemplate = Template.Value
|
|
.Replace("{", "{{")
|
|
.Replace("}", "}}")
|
|
.Replace(@"{{Label}}", "{0}")
|
|
.Replace(@"{{Value}}", "{1}");
|
|
|
|
List<object?> values = new List<object?>
|
|
{
|
|
getLabelString(Attribute.Value),
|
|
getValueString(Attribute.Value)
|
|
};
|
|
|
|
foreach (var type in Enum.GetValues<BeatmapAttribute>())
|
|
{
|
|
string replaced = numberedTemplate.Replace($@"{{{{{type}}}}}", $@"{{{values.Count}}}");
|
|
|
|
if (numberedTemplate != replaced)
|
|
{
|
|
numberedTemplate = replaced;
|
|
values.Add(getValueString(type));
|
|
}
|
|
}
|
|
|
|
text.Text = LocalisableString.Format(numberedTemplate, values.ToArray());
|
|
}
|
|
|
|
private LocalisableString getLabelString(BeatmapAttribute attribute)
|
|
{
|
|
switch (attribute)
|
|
{
|
|
case BeatmapAttribute.CircleSize:
|
|
return BeatmapsetsStrings.ShowStatsCs;
|
|
|
|
case BeatmapAttribute.Accuracy:
|
|
return BeatmapsetsStrings.ShowStatsAccuracy;
|
|
|
|
case BeatmapAttribute.HPDrain:
|
|
return BeatmapsetsStrings.ShowStatsDrain;
|
|
|
|
case BeatmapAttribute.ApproachRate:
|
|
return BeatmapsetsStrings.ShowStatsAr;
|
|
|
|
case BeatmapAttribute.StarRating:
|
|
return BeatmapsetsStrings.ShowStatsStars;
|
|
|
|
case BeatmapAttribute.Title:
|
|
return EditorSetupStrings.Title;
|
|
|
|
case BeatmapAttribute.Artist:
|
|
return EditorSetupStrings.Artist;
|
|
|
|
case BeatmapAttribute.DifficultyName:
|
|
return EditorSetupStrings.DifficultyHeader;
|
|
|
|
case BeatmapAttribute.Creator:
|
|
return EditorSetupStrings.Creator;
|
|
|
|
case BeatmapAttribute.Source:
|
|
return EditorSetupStrings.Source;
|
|
|
|
case BeatmapAttribute.Length:
|
|
return ArtistStrings.TracklistLength.ToTitle();
|
|
|
|
case BeatmapAttribute.RankedStatus:
|
|
return BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault;
|
|
|
|
case BeatmapAttribute.BPM:
|
|
return BeatmapsetsStrings.ShowStatsBpm;
|
|
|
|
case BeatmapAttribute.MaxPP:
|
|
return BeatmapAttributeTextStrings.MaxPP;
|
|
|
|
default:
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private LocalisableString getValueString(BeatmapAttribute attribute)
|
|
{
|
|
switch (attribute)
|
|
{
|
|
case BeatmapAttribute.Title:
|
|
return new RomanisableString(beatmap.Value.BeatmapInfo.Metadata.TitleUnicode, beatmap.Value.BeatmapInfo.Metadata.Title);
|
|
|
|
case BeatmapAttribute.Artist:
|
|
return new RomanisableString(beatmap.Value.BeatmapInfo.Metadata.ArtistUnicode, beatmap.Value.BeatmapInfo.Metadata.Artist);
|
|
|
|
case BeatmapAttribute.DifficultyName:
|
|
return beatmap.Value.BeatmapInfo.DifficultyName;
|
|
|
|
case BeatmapAttribute.Creator:
|
|
return beatmap.Value.BeatmapInfo.Metadata.Author.Username;
|
|
|
|
case BeatmapAttribute.Source:
|
|
return beatmap.Value.BeatmapInfo.Metadata.Source;
|
|
|
|
case BeatmapAttribute.Length:
|
|
return Math.Round(beatmap.Value.BeatmapInfo.Length / ModUtils.CalculateRateWithMods(mods.Value)).ToFormattedDuration();
|
|
|
|
case BeatmapAttribute.RankedStatus:
|
|
return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription();
|
|
|
|
case BeatmapAttribute.BPM:
|
|
return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"0.##");
|
|
|
|
case BeatmapAttribute.CircleSize:
|
|
return computeDifficulty().CircleSize.ToLocalisableString(@"0.##");
|
|
|
|
case BeatmapAttribute.HPDrain:
|
|
return computeDifficulty().DrainRate.ToLocalisableString(@"0.##");
|
|
|
|
case BeatmapAttribute.Accuracy:
|
|
return computeDifficulty().OverallDifficulty.ToLocalisableString(@"0.##");
|
|
|
|
case BeatmapAttribute.ApproachRate:
|
|
return computeDifficulty().ApproachRate.ToLocalisableString(@"0.##");
|
|
|
|
case BeatmapAttribute.StarRating:
|
|
return (starDifficulty?.Stars ?? 0).FormatStarRating();
|
|
|
|
case BeatmapAttribute.MaxPP:
|
|
return Math.Round(starDifficulty?.PerformanceAttributes?.Total ?? 0, MidpointRounding.AwayFromZero).ToLocalisableString();
|
|
|
|
default:
|
|
return string.Empty;
|
|
}
|
|
|
|
BeatmapDifficulty computeDifficulty()
|
|
{
|
|
BeatmapDifficulty difficulty = new BeatmapDifficulty(beatmap.Value.BeatmapInfo.Difficulty);
|
|
|
|
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
|
|
mod.ApplyToDifficulty(difficulty);
|
|
|
|
if (ruleset.Value is RulesetInfo rulesetInfo)
|
|
difficulty = rulesetInfo.CreateInstance().GetAdjustedDisplayDifficulty(difficulty, mods.Value);
|
|
|
|
return difficulty;
|
|
}
|
|
}
|
|
|
|
protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40);
|
|
|
|
protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour;
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
|
|
difficultyCancellationSource?.Cancel();
|
|
difficultyCancellationSource?.Dispose();
|
|
difficultyCancellationSource = null;
|
|
|
|
modSettingTracker?.Dispose();
|
|
}
|
|
}
|
|
|
|
// WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END.
|
|
// Doing so will break existing user skins.
|
|
public enum BeatmapAttribute
|
|
{
|
|
CircleSize,
|
|
HPDrain,
|
|
Accuracy,
|
|
ApproachRate,
|
|
StarRating,
|
|
Title,
|
|
Artist,
|
|
DifficultyName,
|
|
Creator,
|
|
Length,
|
|
RankedStatus,
|
|
BPM,
|
|
Source,
|
|
MaxPP
|
|
}
|
|
}
|