1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 09:07:25 +08:00

Merge branch 'controlpoint-rework' into beat-sync-container-improvements

This commit is contained in:
smoogipooo 2017-05-23 16:07:59 +09:00
commit 7066376855
39 changed files with 385 additions and 225 deletions

@ -1 +1 @@
Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162
Subproject commit c6f030d6f1ab65a48de9ff1d0c424acb686e0149

View File

@ -18,7 +18,7 @@ using osu.Game.Rulesets.Taiko.UI;
using System.Collections.Generic;
using osu.Desktop.VisualTests.Beatmaps;
using osu.Framework.Allocation;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Desktop.VisualTests.Tests
{
@ -53,8 +53,8 @@ namespace osu.Desktop.VisualTests.Tests
time += RNG.Next(50, 500);
}
TimingInfo timing = new TimingInfo();
timing.ControlPoints.Add(new ControlPoint
var controlPointInfo = new ControlPointInfo();
controlPointInfo.TimingPoints.Add(new TimingControlPoint
{
BeatLength = 200
});
@ -73,7 +73,7 @@ namespace osu.Desktop.VisualTests.Tests
Author = @"peppy",
},
},
TimingInfo = timing
ControlPointInfo = controlPointInfo
});
Add(new Drawable[]

View File

@ -7,10 +7,10 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI;
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps.Timing;
using OpenTK;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Timing;
namespace osu.Desktop.VisualTests.Tests
{
@ -27,7 +27,7 @@ namespace osu.Desktop.VisualTests.Tests
Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
{
Clear();
Add(new ManiaPlayfield(cols, new List<ControlPoint>())
Add(new ManiaPlayfield(cols, new List<TimingChange>())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -41,7 +41,7 @@ namespace osu.Desktop.VisualTests.Tests
Clear();
ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(cols, new List<ControlPoint> { new ControlPoint { BeatLength = 200 } })
Add(playField = new ManiaPlayfield(cols, new List<TimingChange>())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -5,11 +5,11 @@ using System;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@ -32,11 +32,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern)
{
ControlPoint overridePoint;
ControlPoint controlPoint = Beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
convertType = PatternType.None;
if ((overridePoint ?? controlPoint)?.KiaiMode == false)
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
convertType = PatternType.LowProbability;
var distanceData = hitObject as IHasDistance;
@ -44,13 +41,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
repeatCount = repeatsData?.RepeatCount ?? 1;
double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime);
double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength;
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier);
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;

View File

@ -6,7 +6,7 @@ using System.Linq;
using OpenTK;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
@ -25,17 +25,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
StairType = lastStair;
ControlPoint overridePoint;
ControlPoint controlPoint = beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime);
var positionData = hitObject as IHasPosition;
float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length;
double timeSeparation = hitObject.StartTime - previousTime;
double beatLength = controlPoint.BeatLength;
bool kiai = (overridePoint ?? controlPoint).KiaiMode;
if (timeSeparation <= 125)
{
// More than 120 BPM
@ -72,12 +69,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// More than 100 BPM stream
convertType |= PatternType.ForceStack | PatternType.LowProbability;
}
else if (positionSeparation < 20 && density >= beatLength / 2.5)
else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5)
{
// Low density stream
convertType |= PatternType.Reverse | PatternType.LowProbability;
}
else if (density < beatLength / 2.5 || kiai)
else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode)
{
// High density
}

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Audio;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types;
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary>
public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows();
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
ReleaseHitWindows = HitWindows * release_window_lenience;
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements;
@ -17,9 +17,9 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary>
public HitWindows HitWindows { get; protected set; } = new HitWindows();
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
HitWindows = new HitWindows(difficulty.OverallDifficulty);
}

View File

@ -7,7 +7,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Mania.Timing
{
@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Mania.Timing
/// </summary>
public double TimeSpan { get; set; }
private readonly List<DrawableControlPoint> drawableControlPoints;
private readonly List<DrawableControlPoint> drawableControlPoints = new List<DrawableControlPoint>();
public ControlPointContainer(IEnumerable<ControlPoint> timingChanges)
public ControlPointContainer(IEnumerable<TimingChange> timingChanges)
{
drawableControlPoints = timingChanges.Select(t => new DrawableControlPoint(t)).ToList();
Children = drawableControlPoints;
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Timing
/// </summary>
private class DrawableControlPoint : Container
{
private readonly ControlPoint timingChange;
private readonly TimingChange timingChange;
protected override Container<Drawable> Content => content;
private readonly Container content;
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Timing
/// the content container will scroll at twice the normal rate.
/// </summary>
/// <param name="timingChange">The control point to create the drawable control point for.</param>
public DrawableControlPoint(ControlPoint timingChange)
public DrawableControlPoint(TimingChange timingChange)
{
this.timingChange = timingChange;

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Timing
{
public class TimingChange
{
/// <summary>
/// The time at which this timing change happened.
/// </summary>
public double Time;
/// <summary>
/// The beat length.
/// </summary>
public double BeatLength = 500;
/// <summary>
/// The speed multiplier.
/// </summary>
public double SpeedMultiplier = 1;
}
}

View File

@ -17,7 +17,6 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Beatmaps.Timing;
using System;
using osu.Framework.Configuration;
@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly ControlPointContainer ControlPointContainer;
public Column(IEnumerable<ControlPoint> timingChanges)
public Column(IEnumerable<TimingChange> timingChanges)
{
RelativeSizeAxes = Axes.Y;
Width = column_width;

View File

@ -2,19 +2,22 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@ -33,22 +36,32 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield()
{
ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
double lastSpeedMultiplier = 1;
double lastBeatLength = 500;
if (firstTimingChange == null)
throw new InvalidOperationException("The Beatmap contains no timing points!");
// Merge timing + difficulty points
var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints);
// Generate the timing points, making non-timing changes use the previous timing change
var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>
var timingChanges = allPoints.Select(c =>
{
ControlPoint t = c.Clone();
var timingPoint = c as TimingControlPoint;
var difficultyPoint = c as DifficultyControlPoint;
if (c.TimingChange)
firstTimingChange = c;
else
t.BeatLength = firstTimingChange.BeatLength;
if (timingPoint != null)
lastBeatLength = timingPoint.BeatLength;
return t;
if (difficultyPoint != null)
lastSpeedMultiplier = difficultyPoint.SpeedMultiplier;
return new TimingChange
{
Time = c.Time,
BeatLength = lastBeatLength,
SpeedMultiplier = lastSpeedMultiplier
};
});
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;

View File

@ -19,7 +19,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Input;
using osu.Game.Beatmaps.Timing;
using osu.Framework.Graphics.Transforms;
using osu.Framework.MathUtils;
@ -65,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int columnCount;
public ManiaPlayfield(int columnCount, IEnumerable<ControlPoint> timingChanges)
public ManiaPlayfield(int columnCount, IEnumerable<TimingChange> timingChanges)
{
this.columnCount = columnCount;

View File

@ -78,6 +78,7 @@
<Compile Include="Mods\ManiaMod.cs" />
<Compile Include="UI\SpecialColumnPosition.cs" />
<Compile Include="Timing\ControlPointContainer.cs" />
<Compile Include="Timing\TimingChange.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

View File

@ -6,8 +6,8 @@ using OpenTK;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects
return OsuScoreResult.Miss;
}
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
@ -10,6 +9,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Database;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -62,13 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Velocity;
public double TickDistance;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / timing.SpeedMultiplierAt(StartTime);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
Velocity = scoringDistance / timing.BeatLengthAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;
}

View File

@ -2,8 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public override bool NewCombo => true;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
}

View File

@ -12,6 +12,7 @@ using osu.Game.Database;
using osu.Game.IO.Serialization;
using osu.Game.Audio;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.Beatmaps
{
@ -77,8 +78,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
int repeats = repeatsData?.RepeatCount ?? 1;
double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime);
double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
double speedAdjustment = difficultyPoint.SpeedMultiplier;
double speedAdjustedBeatLength = timingPoint.BeatLength * speedAdjustment;
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;

View File

@ -5,9 +5,9 @@ using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.Objects
{
@ -55,11 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
private double tickSpacing = 100;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
tickSpacing = timing.BeatLengthAt(StartTime) / TickRate;
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / TickRate;
RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
namespace osu.Game.Rulesets.Taiko.Objects
@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public double HitWindowMiss = 95;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.UI;
@ -51,17 +51,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public bool Kiai { get; protected set; }
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000) / difficulty.SliderMultiplier;
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
ControlPoint overridePoint;
Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode;
ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier;
if (overridePoint != null)
Kiai |= overridePoint.KiaiMode;
Kiai |= effectPoint.KiaiMode;
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
using OpenTK;
using osu.Game.Rulesets.Beatmaps;
using System.Linq;
namespace osu.Game.Rulesets.Taiko.UI
{
@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.UI
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
var timingPoints = Beatmap.TimingInfo.ControlPoints.FindAll(cp => cp.TimingChange);
var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
if (timingPoints.Count == 0)
return;
@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
StartTime = time,
};
barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));

View File

@ -7,6 +7,7 @@ using osu.Game.Database;
using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps
{
@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps
where T : HitObject
{
public BeatmapInfo BeatmapInfo;
public TimingInfo TimingInfo = new TimingInfo();
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
public readonly List<Color4> ComboColors = new List<Color4>
{
@ -46,7 +47,7 @@ namespace osu.Game.Beatmaps
public Beatmap(Beatmap original = null)
{
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
TimingInfo = original?.TimingInfo ?? TimingInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors;
}

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Beatmaps.ControlPoints
{
public class ControlPoint : IComparable<ControlPoint>
{
/// <summary>
/// The time at which the control point takes effect.
/// </summary>
public double Time;
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
}
}

View File

@ -0,0 +1,100 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Lists;
namespace osu.Game.Beatmaps.ControlPoints
{
public class ControlPointInfo
{
/// <summary>
/// All timing points.
/// </summary>
public readonly SortedList<TimingControlPoint> TimingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
/// <summary>
/// All difficulty points.
/// </summary>
public readonly SortedList<DifficultyControlPoint> DifficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
/// <summary>
/// All sound points.
/// </summary>
public readonly SortedList<SoundControlPoint> SoundPoints = new SortedList<SoundControlPoint>(Comparer<SoundControlPoint>.Default);
/// <summary>
/// All effect points.
/// </summary>
public readonly SortedList<EffectControlPoint> EffectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
/// <summary>
/// Finds the difficulty control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the difficulty control point at.</param>
/// <returns>The difficulty control point.</returns>
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
/// <summary>
/// Finds the effect control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the effect control point at.</param>
/// <returns>The effect control point.</returns>
public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
/// <summary>
/// Finds the sound control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the sound control point at.</param>
/// <returns>The sound control point.</returns>
public SoundControlPoint SoundPointAt(double time) => binarySearch(SoundPoints, time);
/// <summary>
/// Finds the timing control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the timing control point at.</param>
/// <returns>The timing control point.</returns>
public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time);
/// <summary>
/// Finds the maximum BPM represented by any timing control point.
/// </summary>
public double BPMMaximum =>
60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
/// <summary>
/// Finds the minimum BPM represented by any timing control point.
/// </summary>
public double BPMMinimum =>
60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
/// <summary>
/// Finds the mode BPM (most common BPM) represented by the control points.
/// </summary>
public double BPMMode =>
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
private T binarySearch<T>(SortedList<T> list, double time)
where T : ControlPoint, new()
{
if (list.Count == 0)
return new T();
if (time < list[0].Time)
return new T();
int index = list.BinarySearch(new T() { Time = time });
// Check if we've found an exact match (t == time)
if (index >= 0)
return list[index];
index = ~index;
if (index == list.Count)
return list[list.Count - 1];
return list[index - 1];
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.ControlPoints
{
public class DifficultyControlPoint : ControlPoint
{
/// <summary>
/// The speed multiplier at this control point.
/// </summary>
public double SpeedMultiplier = 1;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.ControlPoints
{
public class EffectControlPoint : ControlPoint
{
/// <summary>
/// Whether this control point enables Kiai mode.
/// </summary>
public bool KiaiMode;
/// <summary>
/// Whether the first bar line of this control point is ignored.
/// </summary>
public bool OmitFirstBarLine;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.ControlPoints
{
public class SoundControlPoint : ControlPoint
{
/// <summary>
/// The default sample bank at this control point.
/// </summary>
public string SampleBank;
/// <summary>
/// The default sample volume at this control point.
/// </summary>
public int SampleVolume;
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Beatmaps.ControlPoints
{
public class TimingControlPoint : ControlPoint
{
/// <summary>
/// The time signature at this control point.
/// </summary>
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
/// <summary>
/// The beat length at this control point.
/// </summary>
public double BeatLength = 500;
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
foreach (var h in Objects)
h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty);
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
PreprocessHitObjects();
}

View File

@ -8,6 +8,7 @@ using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps.Formats
{
@ -241,6 +242,7 @@ namespace osu.Game.Beatmaps.Formats
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1;
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
if (split.Length >= 3)
@ -275,18 +277,48 @@ namespace osu.Game.Beatmaps.Formats
if (stringSampleSet == @"none")
stringSampleSet = @"normal";
beatmap.TimingInfo.ControlPoints.Add(new ControlPoint
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
if (timingChange)
{
Time = time,
BeatLength = beatLength,
SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1,
TimingChange = timingChange,
TimeSignature = timeSignature,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume,
KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature
});
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
{
Time = time,
BeatLength = beatLength,
TimeSignature = timeSignature
});
}
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
{
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
{
Time = time,
SpeedMultiplier = speedMultiplier
});
}
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
{
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
{
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume
});
}
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
{
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
{
Time = time,
KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature
});
}
}
private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours)

View File

@ -1,20 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.Timing
{
public class ControlPoint
{
public string SampleBank;
public int SampleVolume;
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
public double Time;
public double BeatLength = 500;
public double SpeedMultiplier = 1;
public bool TimingChange = true;
public bool KiaiMode;
public bool OmitFirstBarLine;
public ControlPoint Clone() => (ControlPoint)MemberwiseClone();
}
}

View File

@ -1,80 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Beatmaps.Timing
{
public class TimingInfo
{
public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time);
public double BPMAt(double time)
{
return 60000 / BeatLengthAt(time);
}
/// <summary>
/// Finds the speed multiplier at a time.
/// </summary>
/// <param name="time">The time to find the speed multiplier at.</param>
/// <returns>The speed multiplier.</returns>
public double SpeedMultiplierAt(double time)
{
ControlPoint overridePoint;
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1;
}
/// <summary>
/// Finds the beat length at a time. This is expressed in milliseconds.
/// </summary>
/// <param name="time">The time to find the beat length at.</param>
/// <returns>The beat length.</returns>
public double BeatLengthAt(double time)
{
ControlPoint overridePoint;
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return timingPoint.BeatLength;
}
/// <summary>
/// Finds the timing point at a time.
/// </summary>
/// <param name="time">The time to find the timing point at.</param>
/// <param name="overridePoint">The timing point containing the velocity change of the returned timing point.</param>
/// <returns>The timing point.</returns>
public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint)
{
overridePoint = null;
ControlPoint timingPoint = null;
foreach (var controlPoint in ControlPoints)
{
// Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s).
// This null check makes it so that the first ControlPoint that makes a timing change is used as
// the timingPoint for those HitObject(s).
if (controlPoint.Time <= time || timingPoint == null)
{
if (controlPoint.TimingChange)
{
timingPoint = controlPoint;
overridePoint = null;
}
else
overridePoint = controlPoint;
}
else break;
}
return timingPoint ?? new ControlPoint();
}
}
}

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Graphics.Containers
@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Containers
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private int lastBeat;
private ControlPoint lastControlPoint;
private TimingControlPoint lastTimingPoint;
protected override void Update()
{
@ -22,29 +23,29 @@ namespace osu.Game.Graphics.Containers
return;
double currentTrackTime = beatmap.Value.Track.CurrentTime;
ControlPoint overridePoint;
ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(currentTrackTime, out overridePoint);
if (controlPoint.BeatLength == 0)
TimingControlPoint timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
EffectControlPoint effectPoint = beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
if (timingPoint.BeatLength == 0)
return;
bool kiai = (overridePoint ?? controlPoint).KiaiMode;
int beat = (int)((currentTrackTime - controlPoint.Time) / controlPoint.BeatLength);
int beat = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
// The beats before the start of the first control point are off by 1, this should do the trick
if (currentTrackTime < controlPoint.Time)
if (currentTrackTime < timingPoint.Time)
beat--;
if (controlPoint == lastControlPoint && beat == lastBeat)
if (timingPoint == lastTimingPoint && beat == lastBeat)
return;
double offsetFromBeat = (controlPoint.Time - currentTrackTime) % controlPoint.BeatLength;
double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
using (BeginDelayedSequence(offsetFromBeat, true))
OnNewBeat(beat, controlPoint.BeatLength, controlPoint.TimeSignature, kiai);
OnNewBeat(beat, timingPoint.BeatLength, timingPoint.TimeSignature, effectPoint.KiaiMode);
lastBeat = beat;
lastControlPoint = controlPoint;
lastTimingPoint = timingPoint;
}
[BackgroundDependencyLoader]

View File

@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Beatmaps
return new Beatmap<T>
{
BeatmapInfo = original.BeatmapInfo,
TimingInfo = original.TimingInfo,
ControlPointInfo = original.ControlPointInfo,
HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList()
};
}

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Audio;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Objects.Types;
@ -33,31 +33,28 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// Applies default values to this HitObject.
/// </summary>
/// <param name="controlPointInfo">The control points.</param>
/// <param name="difficulty">The difficulty settings to use.</param>
/// <param name="timing">The timing settings to use.</param>
public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public virtual void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
ControlPoint overridePoint;
ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint);
ControlPoint samplePoint = overridePoint ?? timingPoint;
SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime);
// Initialize first sample
Samples.ForEach(s => initializeSampleInfo(s, samplePoint));
Samples.ForEach(s => initializeSampleInfo(s, soundPoint));
// Initialize any repeat samples
var repeatData = this as IHasRepeats;
repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, samplePoint)));
repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, soundPoint)));
}
private void initializeSampleInfo(SampleInfo sample, ControlPoint controlPoint)
private void initializeSampleInfo(SampleInfo sample, SoundControlPoint soundPoint)
{
if (sample.Volume == 0)
sample.Volume = controlPoint?.SampleVolume ?? 0;
sample.Volume = soundPoint?.SampleVolume ?? 0;
// If the bank is not assigned a name, assign it from the control point
if (string.IsNullOrEmpty(sample.Bank))
sample.Bank = controlPoint?.SampleBank ?? @"normal";
sample.Bank = soundPoint?.SampleBank ?? @"normal";
}
}
}

View File

@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.UI
// Apply defaults
foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
// Post-process the beatmap
processor.PostProcess(Beatmap);

View File

@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = HitRenderer.Objects.First().StartTime;
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.TimingInfo.BeatLengthAt(firstObjectTime) * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.ProcessFrame();
offsetClock = new FramedOffsetClock(decoupledClock);

View File

@ -238,12 +238,12 @@ namespace osu.Game.Screens.Select
private string getBPMRange(Beatmap beatmap)
{
double bpmMax = beatmap.TimingInfo.BPMMaximum;
double bpmMin = beatmap.TimingInfo.BPMMinimum;
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm";
return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)";
return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.ControlPointInfo.BPMMode) + "bpm)";
}
public class InfoLabel : Container

View File

@ -87,10 +87,15 @@
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
<Compile Include="Beatmaps\ControlPoints\ControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\ControlPointInfo.cs" />
<Compile Include="Beatmaps\ControlPoints\DifficultyControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\EffectControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\SoundControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\TimingControlPoint.cs" />
<Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
<Compile Include="Beatmaps\Timing\BreakPeriod.cs" />
<Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
<Compile Include="Beatmaps\Timing\TimingInfo.cs" />
<Compile Include="Database\BeatmapMetrics.cs" />
<Compile Include="Database\Database.cs" />
<Compile Include="Database\RulesetInfo.cs" />
@ -202,7 +207,6 @@
<Compile Include="Beatmaps\Drawables\Panel.cs" />
<Compile Include="Rulesets\Objects\Drawables\DrawableHitObject.cs" />
<Compile Include="Rulesets\Objects\HitObject.cs" />
<Compile Include="Beatmaps\Timing\ControlPoint.cs" />
<Compile Include="Configuration\OsuConfigManager.cs" />
<Compile Include="Overlays\Notifications\IHasCompletionTarget.cs" />
<Compile Include="Overlays\Notifications\Notification.cs" />