mirror of
https://github.com/ppy/osu.git
synced 2025-03-22 22:17:46 +08:00
Merge pull request #18668 from smoogipoo/editor-controlpoint-undo-redo
This commit is contained in:
commit
97fcf8cec9
@ -374,7 +374,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
int i = 0;
|
||||
double currentTime = timingPoint.Time;
|
||||
|
||||
while (!definitelyBigger(currentTime, mapEndTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint)
|
||||
while (!definitelyBigger(currentTime, mapEndTime) && ReferenceEquals(controlPointInfo.TimingPointAt(currentTime), timingPoint))
|
||||
{
|
||||
beats.Add(Math.Floor(currentTime));
|
||||
i++;
|
||||
|
@ -117,8 +117,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
// After placement these must be non-default as defaults are read-only.
|
||||
AddAssert("Placed object has non-default control points", () =>
|
||||
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) &&
|
||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
@ -126,8 +126,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
// After placement these must be non-default as defaults are read-only.
|
||||
AddAssert("Placed object still has non-default control points", () =>
|
||||
EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
|
||||
EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
|
||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) &&
|
||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
|
||||
{
|
||||
if (timingPoints[^1] == current)
|
||||
if (ReferenceEquals(timingPoints[^1], current))
|
||||
return current;
|
||||
|
||||
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
|
||||
@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
if (timingPoints.Count == 0) return 0;
|
||||
|
||||
if (timingPoints[^1] == current)
|
||||
if (ReferenceEquals(timingPoints[^1], current))
|
||||
{
|
||||
Debug.Assert(BeatSyncSource.Clock != null);
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Graphics;
|
||||
@ -11,7 +9,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>, IEquatable<ControlPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
@ -30,7 +28,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// </summary>
|
||||
/// <param name="existing">An existing control point to compare with.</param>
|
||||
/// <returns>Whether this <see cref="ControlPoint"/> is redundant when placed alongside <paramref name="existing"/>.</returns>
|
||||
public abstract bool IsRedundant(ControlPoint existing);
|
||||
public abstract bool IsRedundant(ControlPoint? existing);
|
||||
|
||||
/// <summary>
|
||||
/// Create an unbound copy of this control point.
|
||||
@ -48,5 +46,20 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
Time = other.Time;
|
||||
}
|
||||
|
||||
public sealed override bool Equals(object? obj)
|
||||
=> obj is ControlPoint otherControlPoint
|
||||
&& Equals(otherControlPoint);
|
||||
|
||||
public virtual bool Equals(ControlPoint? other)
|
||||
{
|
||||
if (ReferenceEquals(other, null)) return false;
|
||||
if (ReferenceEquals(other, this)) return true;
|
||||
|
||||
return Time == other.Time;
|
||||
}
|
||||
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
public override int GetHashCode() => Time.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,16 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class ControlPointGroup : IComparable<ControlPointGroup>
|
||||
public class ControlPointGroup : IComparable<ControlPointGroup>, IEquatable<ControlPointGroup>
|
||||
{
|
||||
public event Action<ControlPoint> ItemAdded;
|
||||
public event Action<ControlPoint> ItemRemoved;
|
||||
public event Action<ControlPoint>? ItemAdded;
|
||||
public event Action<ControlPoint>? ItemRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
@ -48,5 +46,23 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
controlPoints.Remove(point);
|
||||
ItemRemoved?.Invoke(point);
|
||||
}
|
||||
|
||||
public sealed override bool Equals(object? obj)
|
||||
=> obj is ControlPointGroup otherGroup
|
||||
&& Equals(otherGroup);
|
||||
|
||||
public virtual bool Equals(ControlPointGroup? other)
|
||||
=> other != null
|
||||
&& Time == other.Time
|
||||
&& ControlPoints.SequenceEqual(other.ControlPoints);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hashCode = new HashCode();
|
||||
hashCode.Add(Time);
|
||||
foreach (var point in controlPoints)
|
||||
hashCode.Add(point);
|
||||
return hashCode.ToHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
@ -12,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <remarks>
|
||||
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
|
||||
/// </remarks>
|
||||
public class DifficultyControlPoint : ControlPoint
|
||||
public class DifficultyControlPoint : ControlPoint, IEquatable<DifficultyControlPoint>
|
||||
{
|
||||
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
|
||||
{
|
||||
@ -41,7 +40,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => SliderVelocityBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
public override bool IsRedundant(ControlPoint? existing)
|
||||
=> existing is DifficultyControlPoint existingDifficulty
|
||||
&& SliderVelocity == existingDifficulty.SliderVelocity;
|
||||
|
||||
@ -51,5 +50,15 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
|
||||
public override bool Equals(ControlPoint? other)
|
||||
=> other is DifficultyControlPoint otherDifficultyControlPoint
|
||||
&& Equals(otherDifficultyControlPoint);
|
||||
|
||||
public bool Equals(DifficultyControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& SliderVelocity == other.SliderVelocity;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class EffectControlPoint : ControlPoint
|
||||
public class EffectControlPoint : ControlPoint, IEquatable<EffectControlPoint>
|
||||
{
|
||||
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
||||
{
|
||||
@ -68,7 +67,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => KiaiModeBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
public override bool IsRedundant(ControlPoint? existing)
|
||||
=> !OmitFirstBarLine
|
||||
&& existing is EffectControlPoint existingEffect
|
||||
&& KiaiMode == existingEffect.KiaiMode
|
||||
@ -83,5 +82,17 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
|
||||
public override bool Equals(ControlPoint? other)
|
||||
=> other is EffectControlPoint otherEffectControlPoint
|
||||
&& Equals(otherEffectControlPoint);
|
||||
|
||||
public bool Equals(EffectControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& OmitFirstBarLine == other.OmitFirstBarLine
|
||||
&& ScrollSpeed == other.ScrollSpeed
|
||||
&& KiaiMode == other.KiaiMode;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), OmitFirstBarLine, ScrollSpeed, KiaiMode);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
@ -13,7 +12,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <remarks>
|
||||
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
|
||||
/// </remarks>
|
||||
public class SampleControlPoint : ControlPoint
|
||||
public class SampleControlPoint : ControlPoint, IEquatable<SampleControlPoint>
|
||||
{
|
||||
public const string DEFAULT_BANK = "normal";
|
||||
|
||||
@ -73,7 +72,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
|
||||
=> hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume);
|
||||
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
public override bool IsRedundant(ControlPoint? existing)
|
||||
=> existing is SampleControlPoint existingSample
|
||||
&& SampleBank == existingSample.SampleBank
|
||||
&& SampleVolume == existingSample.SampleVolume;
|
||||
@ -85,5 +84,16 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
|
||||
public override bool Equals(ControlPoint? other)
|
||||
=> other is SampleControlPoint otherSampleControlPoint
|
||||
&& Equals(otherSampleControlPoint);
|
||||
|
||||
public bool Equals(SampleControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& SampleBank == other.SampleBank
|
||||
&& SampleVolume == other.SampleVolume;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SampleBank, SampleVolume);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Graphics;
|
||||
@ -8,7 +9,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public class TimingControlPoint : ControlPoint
|
||||
public class TimingControlPoint : ControlPoint, IEquatable<TimingControlPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time signature at this control point.
|
||||
@ -68,7 +69,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public double BPM => 60000 / BeatLength;
|
||||
|
||||
// Timing points are never redundant as they can change the time signature.
|
||||
public override bool IsRedundant(ControlPoint existing) => false;
|
||||
public override bool IsRedundant(ControlPoint? existing) => false;
|
||||
|
||||
public override void CopyFrom(ControlPoint other)
|
||||
{
|
||||
@ -77,5 +78,16 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
|
||||
public override bool Equals(ControlPoint? other)
|
||||
=> other is TimingControlPoint otherTimingControlPoint
|
||||
&& Equals(otherTimingControlPoint);
|
||||
|
||||
public bool Equals(TimingControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& TimeSignature.Equals(other.TimeSignature)
|
||||
&& BeatLength.Equals(other.BeatLength);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions;
|
||||
@ -145,7 +143,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath();
|
||||
|
||||
protected enum Section
|
||||
public enum Section
|
||||
{
|
||||
General,
|
||||
Editor,
|
||||
@ -162,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
|
||||
[Obsolete("Do not use unless you're a legacy ruleset and 100% sure.")]
|
||||
public class LegacyDifficultyControlPoint : DifficultyControlPoint
|
||||
public class LegacyDifficultyControlPoint : DifficultyControlPoint, IEquatable<LegacyDifficultyControlPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
|
||||
@ -188,9 +186,20 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier;
|
||||
}
|
||||
|
||||
public override bool Equals(ControlPoint? other)
|
||||
=> other is LegacyDifficultyControlPoint otherLegacyDifficultyControlPoint
|
||||
&& Equals(otherLegacyDifficultyControlPoint);
|
||||
|
||||
public bool Equals(LegacyDifficultyControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& BpmMultiplier == other.BpmMultiplier;
|
||||
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier);
|
||||
}
|
||||
|
||||
internal class LegacySampleControlPoint : SampleControlPoint
|
||||
internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint>
|
||||
{
|
||||
public int CustomSampleBank;
|
||||
|
||||
@ -204,7 +213,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
return baseInfo;
|
||||
}
|
||||
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
public override bool IsRedundant(ControlPoint? existing)
|
||||
=> base.IsRedundant(existing)
|
||||
&& existing is LegacySampleControlPoint existingSample
|
||||
&& CustomSampleBank == existingSample.CustomSampleBank;
|
||||
@ -215,6 +224,17 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
|
||||
}
|
||||
|
||||
public override bool Equals(ControlPoint? other)
|
||||
=> other is LegacySampleControlPoint otherLegacySampleControlPoint
|
||||
&& Equals(otherLegacySampleControlPoint);
|
||||
|
||||
public bool Equals(LegacySampleControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& CustomSampleBank == other.CustomSampleBank;
|
||||
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
||||
|
||||
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
||||
if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat)
|
||||
return;
|
||||
|
||||
// as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat.
|
||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
|
||||
if (legacyInfo != null)
|
||||
DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone();
|
||||
else if (DifficultyControlPoint == DifficultyControlPoint.DEFAULT)
|
||||
else if (ReferenceEquals(DifficultyControlPoint, DifficultyControlPoint.DEFAULT))
|
||||
DifficultyControlPoint = new DifficultyControlPoint();
|
||||
|
||||
DifficultyControlPoint.Time = StartTime;
|
||||
@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
// This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time.
|
||||
if (legacyInfo != null)
|
||||
SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone();
|
||||
else if (SampleControlPoint == SampleControlPoint.DEFAULT)
|
||||
else if (ReferenceEquals(SampleControlPoint, SampleControlPoint.DEFAULT))
|
||||
SampleControlPoint = new SampleControlPoint();
|
||||
|
||||
SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var group in args.OldItems.OfType<ControlPointGroup>())
|
||||
{
|
||||
var matching = Children.SingleOrDefault(gv => gv.Group == group);
|
||||
var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group));
|
||||
|
||||
if (matching != null)
|
||||
matching.Expire();
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var group in args.OldItems.OfType<ControlPointGroup>())
|
||||
{
|
||||
var matching = Children.SingleOrDefault(gv => gv.Group == group);
|
||||
var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group));
|
||||
|
||||
matching?.Expire();
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
updateRepeats(repeats);
|
||||
}
|
||||
|
||||
if (difficultyControlPoint != Item.DifficultyControlPoint)
|
||||
if (!ReferenceEquals(difficultyControlPoint, Item.DifficultyControlPoint))
|
||||
{
|
||||
difficultyControlPoint = Item.DifficultyControlPoint;
|
||||
difficultyOverrideDisplay?.Expire();
|
||||
@ -220,7 +220,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleControlPoint != Item.SampleControlPoint)
|
||||
if (!ReferenceEquals(sampleControlPoint, Item.SampleControlPoint))
|
||||
{
|
||||
sampleControlPoint = Item.SampleControlPoint;
|
||||
sampleOverrideDisplay?.Expire();
|
||||
@ -393,7 +393,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
if (e.CurrentState.Keyboard.ShiftPressed)
|
||||
{
|
||||
if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT)
|
||||
if (ReferenceEquals(hitObject.DifficultyControlPoint, DifficultyControlPoint.DEFAULT))
|
||||
hitObject.DifficultyControlPoint = new DifficultyControlPoint();
|
||||
|
||||
double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration);
|
||||
|
@ -73,31 +73,7 @@ namespace osu.Game.Screens.Edit
|
||||
public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null)
|
||||
{
|
||||
PlayableBeatmap = playableBeatmap;
|
||||
|
||||
// ensure we are not working with legacy control points.
|
||||
// if we leave the legacy points around they will be applied over any local changes on
|
||||
// ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter.
|
||||
if (PlayableBeatmap.ControlPointInfo is LegacyControlPointInfo)
|
||||
{
|
||||
var newControlPoints = new ControlPointInfo();
|
||||
|
||||
foreach (var controlPoint in PlayableBeatmap.ControlPointInfo.AllControlPoints)
|
||||
{
|
||||
switch (controlPoint)
|
||||
{
|
||||
case DifficultyControlPoint _:
|
||||
case SampleControlPoint _:
|
||||
// skip legacy types.
|
||||
continue;
|
||||
|
||||
default:
|
||||
newControlPoints.Add(controlPoint.Time, controlPoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
playableBeatmap.ControlPointInfo = newControlPoints;
|
||||
}
|
||||
PlayableBeatmap.ControlPointInfo = ConvertControlPoints(PlayableBeatmap.ControlPointInfo);
|
||||
|
||||
this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo;
|
||||
|
||||
@ -110,6 +86,39 @@ namespace osu.Game.Screens.Edit
|
||||
trackStartTime(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="ControlPointInfo"/> such that the resultant <see cref="ControlPointInfo"/> is non-legacy.
|
||||
/// </summary>
|
||||
/// <param name="incoming">The <see cref="ControlPointInfo"/> to convert.</param>
|
||||
/// <returns>The non-legacy <see cref="ControlPointInfo"/>. <paramref name="incoming"/> is returned if already non-legacy.</returns>
|
||||
public static ControlPointInfo ConvertControlPoints(ControlPointInfo incoming)
|
||||
{
|
||||
// ensure we are not working with legacy control points.
|
||||
// if we leave the legacy points around they will be applied over any local changes on
|
||||
// ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter.
|
||||
if (!(incoming is LegacyControlPointInfo))
|
||||
return incoming;
|
||||
|
||||
var newControlPoints = new ControlPointInfo();
|
||||
|
||||
foreach (var controlPoint in incoming.AllControlPoints)
|
||||
{
|
||||
switch (controlPoint)
|
||||
{
|
||||
case DifficultyControlPoint _:
|
||||
case SampleControlPoint _:
|
||||
// skip legacy types.
|
||||
continue;
|
||||
|
||||
default:
|
||||
newControlPoints.Add(controlPoint.Time, controlPoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newControlPoints;
|
||||
}
|
||||
|
||||
public BeatmapInfo BeatmapInfo
|
||||
{
|
||||
get => beatmapInfo;
|
||||
|
@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit
|
||||
seekTime = timingPoint.Time + closestBeat * seekAmount;
|
||||
}
|
||||
|
||||
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
|
||||
if (seekTime < timingPoint.Time && !ReferenceEquals(timingPoint, ControlPointInfo.TimingPoints.First()))
|
||||
seekTime = timingPoint.Time;
|
||||
|
||||
SeekSmoothlyTo(seekTime);
|
||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Screens.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
hoveredBackground.Colour = colourHover = colours.Background1;
|
||||
colourHover = colours.Background1;
|
||||
colourSelected = colours.Colour3;
|
||||
}
|
||||
|
||||
@ -105,8 +105,7 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// Reduce flicker of rows when offset is being changed rapidly.
|
||||
// Probably need to reconsider this.
|
||||
updateState();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,16 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using DiffPlex;
|
||||
using DiffPlex.Model;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Skinning;
|
||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||
@ -34,61 +37,107 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
// Diff the beatmaps
|
||||
var result = new Differ().CreateLineDiffs(readString(currentState), readString(newState), true, false);
|
||||
IBeatmap newBeatmap = null;
|
||||
|
||||
// Find the index of [HitObject] sections. Lines changed prior to this index are ignored.
|
||||
int oldHitObjectsIndex = Array.IndexOf(result.PiecesOld, "[HitObjects]");
|
||||
int newHitObjectsIndex = Array.IndexOf(result.PiecesNew, "[HitObjects]");
|
||||
editorBeatmap.BeginChange();
|
||||
processHitObjects(result, () => newBeatmap ??= readBeatmap(newState));
|
||||
processTimingPoints(() => newBeatmap ??= readBeatmap(newState));
|
||||
editorBeatmap.EndChange();
|
||||
}
|
||||
|
||||
Debug.Assert(oldHitObjectsIndex >= 0);
|
||||
Debug.Assert(newHitObjectsIndex >= 0);
|
||||
private void processTimingPoints(Func<IBeatmap> getNewBeatmap)
|
||||
{
|
||||
ControlPointInfo newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo);
|
||||
|
||||
var toRemove = new List<int>();
|
||||
var toAdd = new List<int>();
|
||||
// Remove all groups from the current beatmap which don't have a corresponding equal group in the new beatmap.
|
||||
foreach (var oldGroup in editorBeatmap.ControlPointInfo.Groups.ToArray())
|
||||
{
|
||||
var newGroup = newControlPoints.GroupAt(oldGroup.Time);
|
||||
|
||||
if (!oldGroup.Equals(newGroup))
|
||||
editorBeatmap.ControlPointInfo.RemoveGroup(oldGroup);
|
||||
}
|
||||
|
||||
// Add all groups from the new beatmap which don't have a corresponding equal group in the old beatmap.
|
||||
foreach (var newGroup in newControlPoints.Groups)
|
||||
{
|
||||
var oldGroup = editorBeatmap.ControlPointInfo.GroupAt(newGroup.Time);
|
||||
|
||||
if (!newGroup.Equals(oldGroup))
|
||||
{
|
||||
foreach (var point in newGroup.ControlPoints)
|
||||
editorBeatmap.ControlPointInfo.Add(newGroup.Time, point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processHitObjects(DiffResult result, Func<IBeatmap> getNewBeatmap)
|
||||
{
|
||||
findChangedIndices(result, LegacyDecoder<Beatmap>.Section.HitObjects, out var removedIndices, out var addedIndices);
|
||||
|
||||
for (int i = removedIndices.Count - 1; i >= 0; i--)
|
||||
editorBeatmap.RemoveAt(removedIndices[i]);
|
||||
|
||||
if (addedIndices.Count > 0)
|
||||
{
|
||||
var newBeatmap = getNewBeatmap();
|
||||
|
||||
foreach (int i in addedIndices)
|
||||
editorBeatmap.Insert(i, newBeatmap.HitObjects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void findChangedIndices(DiffResult result, LegacyDecoder<Beatmap>.Section section, out List<int> removedIndices, out List<int> addedIndices)
|
||||
{
|
||||
removedIndices = new List<int>();
|
||||
addedIndices = new List<int>();
|
||||
|
||||
// Find the start and end indices of the relevant section headers in both the old and the new beatmap file. Lines changed outside of the modified ranges are ignored.
|
||||
int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]");
|
||||
if (oldSectionStartIndex == -1)
|
||||
return;
|
||||
|
||||
int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('['));
|
||||
if (oldSectionEndIndex == -1)
|
||||
oldSectionEndIndex = result.PiecesOld.Length;
|
||||
|
||||
int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]");
|
||||
if (newSectionStartIndex == -1)
|
||||
return;
|
||||
|
||||
int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('['));
|
||||
if (newSectionEndIndex == -1)
|
||||
newSectionEndIndex = result.PiecesNew.Length;
|
||||
|
||||
foreach (var block in result.DiffBlocks)
|
||||
{
|
||||
// Removed hitobjects
|
||||
// Removed indices
|
||||
for (int i = 0; i < block.DeleteCountA; i++)
|
||||
{
|
||||
int hoIndex = block.DeleteStartA + i - oldHitObjectsIndex - 1;
|
||||
int objectIndex = block.DeleteStartA + i;
|
||||
|
||||
if (hoIndex < 0)
|
||||
if (objectIndex <= oldSectionStartIndex || objectIndex >= oldSectionEndIndex)
|
||||
continue;
|
||||
|
||||
toRemove.Add(hoIndex);
|
||||
removedIndices.Add(objectIndex - oldSectionStartIndex - 1);
|
||||
}
|
||||
|
||||
// Added hitobjects
|
||||
// Added indices
|
||||
for (int i = 0; i < block.InsertCountB; i++)
|
||||
{
|
||||
int hoIndex = block.InsertStartB + i - newHitObjectsIndex - 1;
|
||||
int objectIndex = block.InsertStartB + i;
|
||||
|
||||
if (hoIndex < 0)
|
||||
if (objectIndex <= newSectionStartIndex || objectIndex >= newSectionEndIndex)
|
||||
continue;
|
||||
|
||||
toAdd.Add(hoIndex);
|
||||
addedIndices.Add(objectIndex - newSectionStartIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the indices to ensure that removal + insertion indices don't get jumbled up post-removal or post-insertion.
|
||||
// This isn't strictly required, but the differ makes no guarantees about order.
|
||||
toRemove.Sort();
|
||||
toAdd.Sort();
|
||||
|
||||
editorBeatmap.BeginChange();
|
||||
|
||||
// Apply the changes.
|
||||
for (int i = toRemove.Count - 1; i >= 0; i--)
|
||||
editorBeatmap.RemoveAt(toRemove[i]);
|
||||
|
||||
if (toAdd.Count > 0)
|
||||
{
|
||||
IBeatmap newBeatmap = readBeatmap(newState);
|
||||
foreach (int i in toAdd)
|
||||
editorBeatmap.Insert(i, newBeatmap.HitObjects[i]);
|
||||
}
|
||||
|
||||
editorBeatmap.EndChange();
|
||||
removedIndices.Sort();
|
||||
addedIndices.Sort();
|
||||
}
|
||||
|
||||
private string readString(byte[] state) => Encoding.UTF8.GetString(state);
|
||||
|
@ -54,6 +54,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
|
||||
Columns = createHeaders();
|
||||
Content = value.Select(createContent).ToArray().ToRectangular();
|
||||
|
||||
updateSelectedGroup();
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,10 +66,17 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
selectedGroup.BindValueChanged(group =>
|
||||
{
|
||||
// TODO: This should scroll the selected row into view.
|
||||
foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue;
|
||||
updateSelectedGroup();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updateSelectedGroup()
|
||||
{
|
||||
// TODO: This should scroll the selected row into view.
|
||||
foreach (var b in BackgroundFlow)
|
||||
b.Selected = ReferenceEquals(b.Item, selectedGroup?.Value);
|
||||
}
|
||||
|
||||
private TableColumn[] createHeaders()
|
||||
{
|
||||
var columns = new List<TableColumn>
|
||||
|
@ -222,7 +222,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
// Try and create matching types from the currently selected control point.
|
||||
var selected = selectedGroup.Value;
|
||||
|
||||
if (selected != null && selected != group)
|
||||
if (selected != null && !ReferenceEquals(selected, group))
|
||||
{
|
||||
foreach (var controlPoint in selected.ControlPoints)
|
||||
{
|
||||
|
@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
double? offsetChange = newStartTime - selectedGroupStartTime;
|
||||
|
||||
var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints
|
||||
.SkipWhile(g => g != tcp)
|
||||
.SkipWhile(g => !ReferenceEquals(g, tcp))
|
||||
.Skip(1)
|
||||
.FirstOrDefault();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user