1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 05:17:29 +08:00
osu-lazer/osu.Game/Beatmaps/Formats/LegacyDecoder.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

268 lines
9.7 KiB
C#
Raw Normal View History

// 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.
2018-04-13 17:19:50 +08:00
using System;
using System.Collections.Generic;
using osu.Framework.Extensions;
using osu.Framework.Logging;
2018-06-28 17:20:43 +08:00
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO;
2020-04-14 20:05:07 +08:00
using osu.Game.Rulesets.Objects.Legacy;
2018-11-20 15:51:59 +08:00
using osuTK.Graphics;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Beatmaps.Formats
{
2018-03-09 20:23:03 +08:00
public abstract class LegacyDecoder<T> : Decoder<T>
where T : new()
{
public const int LATEST_VERSION = 14;
2018-03-09 20:23:03 +08:00
protected readonly int FormatVersion;
2018-04-13 17:19:50 +08:00
2018-03-09 20:23:03 +08:00
protected LegacyDecoder(int version)
{
2018-03-09 20:23:03 +08:00
FormatVersion = version;
}
2018-04-13 17:19:50 +08:00
protected override void ParseStreamInto(LineBufferedReader stream, T output)
{
Section section = Section.General;
2018-04-13 17:19:50 +08:00
string? line;
2019-04-01 11:16:05 +08:00
while ((line = stream.ReadLine()) != null)
{
if (ShouldSkipLine(line))
continue;
2018-04-13 17:19:50 +08:00
if (section != Section.Metadata)
{
// comments should not be stripped from metadata lines, as the song metadata may contain "//" as valid data.
line = StripComments(line);
}
line = line.TrimEnd();
if (line.StartsWith('[') && line.EndsWith(']'))
{
2019-12-14 20:54:22 +08:00
if (!Enum.TryParse(line[1..^1], out section))
2019-08-12 00:42:05 +08:00
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
2018-04-13 17:19:50 +08:00
2020-03-30 16:18:09 +08:00
OnBeginNewSection(section);
continue;
}
2018-04-13 17:19:50 +08:00
2019-08-07 18:33:54 +08:00
try
{
ParseLine(output, section, line);
}
catch (Exception e)
{
Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}");
2019-08-07 18:33:54 +08:00
}
}
}
2018-04-13 17:19:50 +08:00
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal);
2018-04-13 17:19:50 +08:00
2020-03-30 16:18:09 +08:00
/// <summary>
/// Invoked when a new <see cref="Section"/> has been entered.
/// </summary>
/// <param name="section">The entered <see cref="Section"/>.</param>
protected virtual void OnBeginNewSection(Section section)
{
}
2018-03-13 18:13:50 +08:00
protected virtual void ParseLine(T output, Section section, string line)
{
switch (section)
{
case Section.Colours:
HandleColours(output, line, false);
2018-03-13 18:13:50 +08:00
return;
}
}
protected string StripComments(string line)
{
int index = line.AsSpan().IndexOf("//".AsSpan());
if (index > 0)
return line.Substring(0, index);
2019-02-28 12:31:40 +08:00
return line;
}
2018-04-13 17:19:50 +08:00
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
2018-03-13 18:13:50 +08:00
{
2018-03-14 17:41:48 +08:00
var pair = SplitKeyVal(line);
2018-04-13 17:19:50 +08:00
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
2018-04-13 17:19:50 +08:00
string[] split = pair.Value.Split(',');
2018-04-13 17:19:50 +08:00
if (split.Length != 3 && split.Length != 4)
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
2018-04-13 17:19:50 +08:00
2018-10-05 10:55:59 +08:00
Color4 colour;
2018-10-05 10:55:59 +08:00
try
{
byte alpha = allowAlpha && split.Length == 4 ? byte.Parse(split[3]) : (byte)255;
2020-06-25 13:15:26 +08:00
colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha);
2018-10-05 10:55:59 +08:00
}
2019-04-25 16:36:17 +08:00
catch
{
2018-03-13 18:13:50 +08:00
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
}
2018-04-13 17:19:50 +08:00
2018-03-13 18:13:50 +08:00
if (isCombo)
{
if (!(output is IHasComboColours tHasComboColours)) return;
2018-04-13 17:19:50 +08:00
tHasComboColours.CustomComboColours.Add(colour);
2018-03-13 18:13:50 +08:00
}
else
{
if (!(output is IHasCustomColours tHasCustomColours)) return;
2019-02-28 12:31:40 +08:00
2018-03-13 18:13:50 +08:00
tHasCustomColours.CustomColours[pair.Key] = colour;
}
}
2018-04-13 17:19:50 +08:00
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator = ':', bool shouldTrim = true)
{
string[] split = line.Split(separator, 2);
2018-04-13 17:19:50 +08:00
if (shouldTrim)
{
for (int i = 0; i < split.Length; i++)
split[i] = split[i].Trim();
}
2018-01-04 19:04:52 +08:00
return new KeyValuePair<string, string>
(
split[0],
split.Length > 1 ? split[1] : string.Empty
2018-01-04 19:04:52 +08:00
);
}
2018-04-13 17:19:50 +08:00
protected string CleanFilename(string path) => path
// User error which is supported by stable (https://github.com/ppy/osu/issues/21204)
.Replace(@"\\", @"\")
.Trim('"')
.ToStandardisedPath();
2022-06-13 14:40:11 +08:00
public enum Section
{
General,
Editor,
Metadata,
Difficulty,
Events,
TimingPoints,
Colours,
HitObjects,
Variables,
2020-03-30 16:18:09 +08:00
Fonts,
CatchTheBeat,
Mania,
}
2018-04-13 17:19:50 +08:00
[Obsolete("Do not use unless you're a legacy ruleset and 100% sure.")]
2022-06-20 13:56:04 +08:00
public class LegacyDifficultyControlPoint : DifficultyControlPoint, IEquatable<LegacyDifficultyControlPoint>
{
/// <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; private set; }
/// <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; private set; } = true;
public LegacyDifficultyControlPoint(int rulesetId, double beatLength)
: this()
{
// Note: In stable, the division occurs on floats, but with compiler optimisations turned on actually seems to occur on doubles via some .NET black magic (possibly inlining?).
if (rulesetId == 1 || rulesetId == 3)
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100.0 : 1;
else
BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 1000) / 100.0 : 1;
GenerateTicks = !double.IsNaN(beatLength);
}
public LegacyDifficultyControlPoint()
{
SliderVelocityBindable.Precision = double.Epsilon;
}
public override bool IsRedundant(ControlPoint? existing)
2022-08-24 15:37:33 +08:00
=> base.IsRedundant(existing)
&& GenerateTicks == ((existing as LegacyDifficultyControlPoint)?.GenerateTicks ?? true);
public override void CopyFrom(ControlPoint other)
{
base.CopyFrom(other);
BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier;
GenerateTicks = ((LegacyDifficultyControlPoint)other).GenerateTicks;
}
2022-06-20 13:56:04 +08:00
public override bool Equals(ControlPoint? other)
=> other is LegacyDifficultyControlPoint otherLegacyDifficultyControlPoint
&& Equals(otherLegacyDifficultyControlPoint);
public bool Equals(LegacyDifficultyControlPoint? other)
=> base.Equals(other)
&& BpmMultiplier == other.BpmMultiplier
&& GenerateTicks == other.GenerateTicks;
2022-06-20 13:56:04 +08:00
// ReSharper disable twice NonReadonlyMemberInGetHashCode
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier, GenerateTicks);
}
2022-06-20 13:56:04 +08:00
internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint>
2018-06-28 17:20:43 +08:00
{
public int CustomSampleBank;
2019-06-30 20:58:30 +08:00
public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
2018-06-28 17:20:43 +08:00
{
2019-06-30 20:58:30 +08:00
var baseInfo = base.ApplyTo(hitSampleInfo);
2018-06-28 17:20:43 +08:00
2020-12-01 14:37:51 +08:00
if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
2020-12-02 09:55:48 +08:00
return legacy.With(newCustomSampleBank: CustomSampleBank);
2018-06-28 17:20:43 +08:00
return baseInfo;
}
2022-06-20 15:52:01 +08:00
public override bool IsRedundant(ControlPoint? existing)
2020-04-17 16:06:12 +08:00
=> base.IsRedundant(existing)
2020-04-17 16:04:09 +08:00
&& existing is LegacySampleControlPoint existingSample
&& CustomSampleBank == existingSample.CustomSampleBank;
public override void CopyFrom(ControlPoint other)
{
base.CopyFrom(other);
CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
}
2022-06-20 13:56:04 +08:00
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);
2018-06-28 17:20:43 +08:00
}
}
}