mirror of
https://github.com/ppy/osu.git
synced 2026-06-08 20:04:08 +08:00
4ee4aac708
Reported [on reddit of all places](https://www.reddit.com/r/osugame/comments/1qhnfdn/every_time_i_make_an_adjustment_to_the_hitcircle/). The reason that this is happening is as follows: - Changing a combo colour raises `BeatmapSkinChanged` - `BeatmapSkinChanged` getting raised triggers the sample chooser dropdown to repopulate its items (as intended and correctly) - Setting items of a dropdown can also change its `Current.Value`, because the relevant logic attempts to ensure that `Current.Value` is valid in line with the new items - Therefore the `Current.Value` of the dropdown changes from `null` to sample set `-1` which corresponds to the "Add new..." item as it's the only item in the dropdown... - which is indistinguishable from the sequence of events that happens if the user manually opens the dropdown and clicks the "Add new..." item. This change sidesteps this entire car crash by just ensuring that even beatmaps without custom samples have at least set number 1 initialised (even if it's empty and has no samples). This means that the initial value of the dropdown is never `null`, and that every time that the value changes to the set `-1` it actually is due to user action. Should have known better than to play these dumb games.
164 lines
6.1 KiB
C#
164 lines
6.1 KiB
C#
// 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 System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using osu.Framework.Audio.Sample;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Textures;
|
|
using osu.Game.Audio;
|
|
using osu.Game.Skinning;
|
|
using osuTK.Graphics;
|
|
|
|
namespace osu.Game.Screens.Edit
|
|
{
|
|
/// <summary>
|
|
/// A beatmap skin which is being edited.
|
|
/// </summary>
|
|
public class EditorBeatmapSkin : ISkin, IDisposable
|
|
{
|
|
public event Action? BeatmapSkinChanged;
|
|
|
|
/// <summary>
|
|
/// The underlying beatmap skin.
|
|
/// </summary>
|
|
protected internal readonly LegacyBeatmapSkin Skin;
|
|
|
|
/// <summary>
|
|
/// The combo colours of this skin.
|
|
/// If empty, the default combo colours will be used.
|
|
/// </summary>
|
|
public BindableList<Colour4> ComboColours { get; }
|
|
|
|
private readonly EditorBeatmap editorBeatmap;
|
|
|
|
public EditorBeatmapSkin(EditorBeatmap editorBeatmap, LegacyBeatmapSkin skin)
|
|
{
|
|
this.editorBeatmap = editorBeatmap;
|
|
|
|
Skin = skin;
|
|
ComboColours = new BindableList<Colour4>();
|
|
|
|
if (Skin.Configuration.ComboColours is IReadOnlyList<Color4> comboColours)
|
|
{
|
|
// due to the foibles of how `IHasComboInformation` / `ComboIndexWithOffsets` work,
|
|
// the actual effective first combo colour that will be used on the beatmap is the one with index 1, not 0.
|
|
// see also: `IHasComboInformation.UpdateComboInformation`,
|
|
// https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameModes/Edit/Forms/SongSetup.cs#L233-L234.
|
|
for (int i = 0; i < comboColours.Count; ++i)
|
|
ComboColours.Add(comboColours[(i + 1) % comboColours.Count]);
|
|
}
|
|
|
|
ComboColours.BindCollectionChanged((_, _) => updateColours());
|
|
|
|
if (skin.BeatmapSetResources != null)
|
|
skin.BeatmapSetResources.CacheInvalidated += InvokeSkinChanged;
|
|
}
|
|
|
|
public void InvokeSkinChanged() => BeatmapSkinChanged?.Invoke();
|
|
|
|
#region Combo colours
|
|
|
|
private void updateColours()
|
|
{
|
|
// performs the inverse of the index rotation operation described in the ctor.
|
|
Skin.Configuration.CustomComboColours.Clear();
|
|
for (int i = 0; i < ComboColours.Count; ++i)
|
|
Skin.Configuration.CustomComboColours.Add(ComboColours[(ComboColours.Count + i - 1) % ComboColours.Count]);
|
|
InvokeSkinChanged();
|
|
editorBeatmap.SaveState();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Sample sets
|
|
|
|
public record SampleSet(int SampleSetIndex, string Name)
|
|
{
|
|
public SampleSet(int sampleSetIndex)
|
|
: this(sampleSetIndex, $@"Custom #{sampleSetIndex}")
|
|
{
|
|
}
|
|
|
|
public override string ToString() => Name;
|
|
|
|
public HashSet<string> Filenames = [];
|
|
|
|
public string? FindSampleIfExists(string sampleName, string bankName)
|
|
=> Filenames.SingleOrDefault(f => f.StartsWith($@"{bankName}-{sampleName}{(SampleSetIndex > 1 ? SampleSetIndex : null)}", StringComparison.Ordinal));
|
|
|
|
public virtual bool Equals(SampleSet? other) => SampleSetIndex == other?.SampleSetIndex;
|
|
public override int GetHashCode() => SampleSetIndex;
|
|
}
|
|
|
|
public IEnumerable<SampleSet> GetAvailableSampleSets()
|
|
{
|
|
string[] possibleSounds = HitSampleInfo.ALL_ADDITIONS.Prepend(HitSampleInfo.HIT_NORMAL).ToArray();
|
|
string[] possibleBanks = HitSampleInfo.ALL_BANKS;
|
|
|
|
string[] possiblePrefixes = possibleSounds.SelectMany(sound => possibleBanks.Select(bank => $@"{bank}-{sound}")).ToArray();
|
|
|
|
Dictionary<int, SampleSet> sampleSets = new Dictionary<int, SampleSet>
|
|
{
|
|
[1] = new SampleSet(1),
|
|
};
|
|
|
|
if (Skin.Samples != null)
|
|
{
|
|
foreach (string sample in Skin.Samples.GetAvailableResources())
|
|
{
|
|
foreach (string possiblePrefix in possiblePrefixes)
|
|
{
|
|
if (!sample.StartsWith(possiblePrefix, StringComparison.Ordinal))
|
|
continue;
|
|
|
|
string indexString = Path.GetFileNameWithoutExtension(sample)[possiblePrefix.Length..];
|
|
int? index = null;
|
|
|
|
if (string.IsNullOrEmpty(indexString))
|
|
index = 1;
|
|
if (int.TryParse(indexString, out int parsed))
|
|
index = parsed;
|
|
|
|
if (!index.HasValue)
|
|
continue;
|
|
|
|
SampleSet? sampleSet;
|
|
if (!sampleSets.TryGetValue(index.Value, out sampleSet))
|
|
sampleSet = sampleSets[index.Value] = new SampleSet(index.Value);
|
|
|
|
sampleSet.Filenames.Add(sample);
|
|
}
|
|
}
|
|
}
|
|
|
|
return sampleSets.OrderBy(i => i.Key).Select(i => i.Value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Skin.BeatmapSetResources != null)
|
|
Skin.BeatmapSetResources.CacheInvalidated -= InvokeSkinChanged;
|
|
Skin.Dispose();
|
|
}
|
|
|
|
#region Delegated ISkin implementation
|
|
|
|
public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup);
|
|
public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
|
public ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo);
|
|
|
|
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
|
where TLookup : notnull
|
|
where TValue : notnull
|
|
=> Skin.GetConfig<TLookup, TValue>(lookup);
|
|
|
|
#endregion
|
|
}
|
|
}
|