1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 03:25:11 +08:00

Add LegacyContext

This commit is contained in:
OliBomby 2023-04-25 12:12:46 +02:00
parent a4c6850ab2
commit ea1e6e9798
8 changed files with 155 additions and 29 deletions

View File

@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
@ -51,8 +52,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
double beatLength;
if (hitObject.LegacyBpmMultiplier.HasValue)
beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value;
if (hitObject.HasContext<LegacyContext>())
beatLength = timingPoint.BeatLength * hitObject.GetContext<LegacyContext>().BpmMultiplier;
else if (hitObject is IHasSliderVelocity hasSliderVelocity)
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
else

View File

@ -138,11 +138,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public double SliderVelocity { get; set; } = 1;
/// <summary>
/// Whether to generate ticks on this <see cref="Slider"/>.
/// </summary>
public bool GenerateTicks = true;
[JsonIgnore]
public SliderHeadCircle HeadCircle { get; protected set; }
@ -162,9 +157,10 @@ namespace osu.Game.Rulesets.Osu.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity;
bool generateTicks = !HasContext<LegacyContext>() || GetContext<LegacyContext>().GenerateTicks;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
}
protected override void ApplyLegacyInfoToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
@ -172,12 +168,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyLegacyInfoToSelf(controlPointInfo, difficulty);
DifficultyControlPoint difficultyControlPoint = controlPointInfo is LegacyControlPointInfo legacyInfo ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT;
#pragma warning disable 618
var legacyDifficultyPoint = difficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint;
#pragma warning restore 618
SliderVelocity = difficultyControlPoint.SliderVelocity;
GenerateTicks = legacyDifficultyPoint?.GenerateTicks ?? true;
}
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)

View File

@ -16,6 +16,7 @@ using JetBrains.Annotations;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Rulesets.Taiko.Beatmaps
{
@ -179,8 +180,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
double beatLength;
if (obj.LegacyBpmMultiplier.HasValue)
beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value;
if (obj.HasContext<LegacyContext>())
beatLength = timingPoint.BeatLength * obj.GetContext<LegacyContext>().BpmMultiplier;
else if (obj is IHasSliderVelocity hasSliderVelocity)
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
else

View File

@ -15,6 +15,7 @@ using osu.Game.Beatmaps.Legacy;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy;
namespace osu.Game.Beatmaps.Formats
@ -86,11 +87,24 @@ namespace osu.Game.Beatmaps.Formats
foreach (var hitObject in this.beatmap.HitObjects)
{
hitObject.ApplyLegacyInfo(this.beatmap.ControlPointInfo, this.beatmap.Difficulty);
applyLegacyInfoToHitObject(hitObject);
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty);
}
}
private void applyLegacyInfoToHitObject(HitObject hitObject)
{
var legacyInfo = beatmap.ControlPointInfo as LegacyControlPointInfo;
DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(hitObject.StartTime) : DifficultyControlPoint.DEFAULT;
#pragma warning disable 618
if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint)
#pragma warning restore 618
hitObject.SetContext(new LegacyContext(legacyDifficultyControlPoint.BpmMultiplier, legacyDifficultyControlPoint.GenerateTicks));
hitObject.ApplyLegacyInfo(beatmap.ControlPointInfo, beatmap.Difficulty);
}
/// <summary>
/// Some `BeatmapInfo` members have default values that differ from the default values used by stable.
/// In addition, legacy beatmaps will sometimes not contain some configuration keys, in which case

View File

@ -0,0 +1,32 @@
// 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 osu.Game.Context;
namespace osu.Game.Beatmaps.Legacy;
public class LegacyContext : IContext
{
public LegacyContext(double bpmMultiplier, bool generateTicks)
{
BpmMultiplier = bpmMultiplier;
GenerateTicks = generateTicks;
}
/// <summary>
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
/// DO NOT USE THIS UNLESS 100% SURE.
/// </summary>
public double BpmMultiplier { get; }
/// <summary>
/// Whether or not slider ticks should be generated at this control point.
/// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991).
/// </summary>
public bool GenerateTicks { get; }
public IContext Copy()
{
return new LegacyContext(BpmMultiplier, GenerateTicks);
}
}

View File

@ -0,0 +1,10 @@
namespace osu.Game.Context;
public interface IContext
{
/// <summary>
/// Makes a deep copy of this context.
/// </summary>
/// <returns>The deep copy of this context.</returns>
public IContext Copy();
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
namespace osu.Game.Context
{
public abstract class ContextContainer
{
/// <summary>
/// The contexts of this container.
/// The objects always have the type of their key.
/// </summary>
private readonly Dictionary<Type, IContext> contexts;
protected ContextContainer()
{
contexts = new Dictionary<Type, IContext>();
}
/// <summary>
/// Checks whether this object has the context with type T.
/// </summary>
/// <typeparam name="T">The type to check the context of.</typeparam>
/// <returns>Whether the context object with type T exists in this object.</returns>
public bool HasContext<T>() where T : IContext
{
return contexts.ContainsKey(typeof(T));
}
/// <summary>
/// Gets the context with type T.
/// </summary>
/// <typeparam name="T">The type to get the context of.</typeparam>
/// <exception cref="KeyNotFoundException">If the context does not exist in this hit object.</exception>
/// <returns>The context object with type T.</returns>
public T GetContext<T>() where T : IContext
{
return (T)contexts[typeof(T)];
}
/// <summary>
/// Tries to get the context with type T.
/// </summary>
/// <param name="context">The found context with type T.</param>
/// <typeparam name="T">The type to get the context of.</typeparam>
/// <returns>Whether the context exists in this object.</returns>
public bool TryGetContext<T>(out T context) where T : IContext
{
if (contexts.TryGetValue(typeof(T), out var context2))
{
context = (T)context2;
return true;
}
context = default!;
return false;
}
/// <summary>
/// Sets the context object of type T.
/// </summary>
/// <typeparam name="T">The context type to set.</typeparam>
/// <param name="context">The context object to store in this object.</param>
public void SetContext<T>(T context) where T : IContext
{
contexts[typeof(T)] = context;
}
/// <summary>
/// Removes the context of type T from this object.
/// </summary>
/// <typeparam name="T">The type to remove the context of.</typeparam>
/// <returns>Whether a context was removed.</returns>
public bool RemoveContext<T>() where T : IContext
{
return RemoveContext(typeof(T));
}
/// <summary>
/// Removes the context of type T from this object.
/// </summary>
/// <param name="t">The type to remove the context of.</param>
/// <returns>Whether a context was removed.</returns>
public bool RemoveContext(Type t)
{
return contexts.Remove(t);
}
}
}

View File

@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Context;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Objects
/// HitObjects may contain more properties for which you should be checking through the IHas* types.
/// </para>
/// </summary>
public class HitObject
public class HitObject : ContextContainer
{
/// <summary>
/// A small adjustment to the start time of control points to account for rounding/precision errors.
@ -81,12 +82,6 @@ namespace osu.Game.Rulesets.Objects
public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT;
public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT;
/// <summary>
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
/// DO NOT USE THIS UNLESS 100% SURE.
/// </summary>
public double? LegacyBpmMultiplier { get; private set; }
/// <summary>
/// Whether this <see cref="HitObject"/> is in Kiai time.
/// </summary>
@ -174,12 +169,6 @@ namespace osu.Game.Rulesets.Objects
{
var legacyInfo = controlPointInfo as LegacyControlPointInfo;
DifficultyControlPoint difficultyControlPoint = legacyInfo != null ? legacyInfo.DifficultyPointAt(StartTime) : DifficultyControlPoint.DEFAULT;
#pragma warning disable 618
if (difficultyControlPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyControlPoint)
#pragma warning restore 618
LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier;
ApplyLegacyInfoToSelf(controlPointInfo, difficulty);
// This is done here after ApplyLegacyInfoToSelf as we may require custom defaults to be applied to have an accurate end time.