1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 21:23:04 +08:00

Merge pull request #9957 from Craftplacer/combo-colors

Implement combo colour serializing
This commit is contained in:
Dan Balasescu 2020-09-07 14:25:35 +09:00 committed by GitHub
commit 3347f57e46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 22 deletions

View File

@ -10,6 +10,7 @@ using System.Text;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
@ -19,6 +20,7 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Skinning;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats namespace osu.Game.Tests.Beatmaps.Formats
@ -26,18 +28,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture] [TestFixture]
public class LegacyBeatmapEncoderTest public class LegacyBeatmapEncoderTest
{ {
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu")); private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore();
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStability(string name) public void TestEncodeDecodeStability(string name)
{ {
var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name)); var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded)); var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
sort(decoded); sort(decoded.beatmap);
sort(decodedAfterEncode); sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize())); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
{
// equal to null, no need to SequenceEqual
if (a.ComboColours == null && b.ComboColours == null)
return true;
if (a.ComboColours == null || b.ComboColours == null)
return false;
return a.ComboColours.SequenceEqual(b.ComboColours);
} }
private void sort(IBeatmap beatmap) private void sort(IBeatmap beatmap)
@ -50,18 +67,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private IBeatmap decodeFromLegacy(Stream stream) private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name)
{ {
using (var reader = new LineBufferedReader(stream)) using (var reader = new LineBufferedReader(stream))
return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader)); {
var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
return (convert(beatmap), beatmapSkin);
}
} }
private Stream encodeToLegacy(IBeatmap beatmap) private class TestLegacySkin : LegacySkin
{ {
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
{
}
}
private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
{
var (beatmap, beatmapSkin) = fullBeatmap;
var stream = new MemoryStream(); var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmap).Encode(writer); new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer);
stream.Position = 0; stream.Position = 0;

View File

@ -351,7 +351,7 @@ namespace osu.Game.Tests.Editing
using (var encoded = new MemoryStream()) using (var encoded = new MemoryStream())
{ {
using (var sw = new StreamWriter(encoded)) using (var sw = new StreamWriter(encoded))
new LegacyBeatmapEncoder(beatmap).Encode(sw); new LegacyBeatmapEncoder(beatmap, null).Encode(sw);
return encoded.ToArray(); return encoded.ToArray();
} }

View File

@ -27,6 +27,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
using Decoder = osu.Game.Beatmaps.Formats.Decoder; using Decoder = osu.Game.Beatmaps.Formats.Decoder;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
@ -198,14 +199,15 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param> /// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param> /// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
public void Save(BeatmapInfo info, IBeatmap beatmapContent) /// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
{ {
var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID));
using (var stream = new MemoryStream()) using (var stream = new MemoryStream())
{ {
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmapContent).Encode(sw); new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);

View File

@ -8,6 +8,6 @@ namespace osu.Game.Beatmaps.Formats
{ {
public interface IHasCustomColours public interface IHasCustomColours
{ {
Dictionary<string, Color4> CustomColours { get; set; } Dictionary<string, Color4> CustomColours { get; }
} }
} }

View File

@ -7,13 +7,16 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using JetBrains.Annotations;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
{ {
@ -23,9 +26,18 @@ namespace osu.Game.Beatmaps.Formats
private readonly IBeatmap beatmap; private readonly IBeatmap beatmap;
public LegacyBeatmapEncoder(IBeatmap beatmap) [CanBeNull]
private readonly ISkin skin;
/// <summary>
/// Creates a new <see cref="LegacyBeatmapEncoder"/>.
/// </summary>
/// <param name="beatmap">The beatmap to encode.</param>
/// <param name="skin">The beatmap's skin, used for encoding combo colours.</param>
public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] ISkin skin)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;
this.skin = skin;
if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3)
throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap));
@ -53,6 +65,9 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(); writer.WriteLine();
handleControlPoints(writer); handleControlPoints(writer);
writer.WriteLine();
handleColours(writer);
writer.WriteLine(); writer.WriteLine();
handleHitObjects(writer); handleHitObjects(writer);
} }
@ -196,6 +211,28 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private void handleColours(TextWriter writer)
{
var colours = skin?.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
if (colours == null || colours.Count == 0)
return;
writer.WriteLine("[Colours]");
for (var i = 0; i < colours.Count; i++)
{
var comboColour = colours[i];
writer.Write(FormattableString.Invariant($"Combo{i}: "));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},"));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},"));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},"));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.A * byte.MaxValue)}"));
writer.WriteLine();
}
}
private void handleHitObjects(TextWriter writer) private void handleHitObjects(TextWriter writer)
{ {
if (beatmap.HitObjects.Count == 0) if (beatmap.HitObjects.Count == 0)

View File

@ -101,9 +101,8 @@ namespace osu.Game.Screens.Edit
return; return;
} }
AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin));
dependencies.CacheAs(editorBeatmap); dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap); changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs<IEditorChangeHandler>(changeHandler); dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
@ -399,7 +398,7 @@ namespace osu.Game.Screens.Edit
clock.SeekForward(!clock.IsRunning, amount); clock.SeekForward(!clock.IsRunning, amount);
} }
private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin);
private void exportBeatmap() private void exportBeatmap()
{ {

View File

@ -15,6 +15,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -47,6 +48,8 @@ namespace osu.Game.Screens.Edit
public readonly IBeatmap PlayableBeatmap; public readonly IBeatmap PlayableBeatmap;
public readonly ISkin BeatmapSkin;
[Resolved] [Resolved]
private BindableBeatDivisor beatDivisor { get; set; } private BindableBeatDivisor beatDivisor { get; set; }
@ -54,9 +57,10 @@ namespace osu.Game.Screens.Edit
private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>(); private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>();
public EditorBeatmap(IBeatmap playableBeatmap) public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null)
{ {
PlayableBeatmap = playableBeatmap; PlayableBeatmap = playableBeatmap;
BeatmapSkin = beatmapSkin;
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap);

View File

@ -85,7 +85,7 @@ namespace osu.Game.Screens.Edit
using (var stream = new MemoryStream()) using (var stream = new MemoryStream())
{ {
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
savedStates.Add(stream.ToArray()); savedStates.Add(stream.ToArray());
} }

View File

@ -23,7 +23,7 @@ namespace osu.Game.Skinning
public readonly int Keys; public readonly int Keys;
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>(); public Dictionary<string, Color4> CustomColours { get; } = new Dictionary<string, Color4>();
public Dictionary<string, string> ImageLookups = new Dictionary<string, string>(); public Dictionary<string, string> ImageLookups = new Dictionary<string, string>();

View File

@ -45,7 +45,7 @@ namespace osu.Game.Skinning
public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours); public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours);
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>(); public Dictionary<string, Color4> CustomColours { get; } = new Dictionary<string, Color4>();
public readonly Dictionary<string, string> ConfigDictionary = new Dictionary<string, string>(); public readonly Dictionary<string, string> ConfigDictionary = new Dictionary<string, string>();
} }