mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 19:42:55 +08:00
Merge pull request #26249 from frenzibyte/ruleset-specific-combo-counter
Move combo counter to ruleset-specific HUD components container
This commit is contained in:
commit
dcafee7cb7
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Skinning;
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
||||
public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
|
||||
{
|
||||
if (withModifiedSkin)
|
||||
{
|
||||
@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
CreateTest();
|
||||
}
|
||||
|
||||
AddAssert("legacy HUD combo counter hidden", () =>
|
||||
{
|
||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
||||
});
|
||||
AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType<LegacyComboCounter>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,25 +37,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
// Modifications for global components.
|
||||
if (containerLookup.Ruleset == null)
|
||||
{
|
||||
var components = base.GetDrawableComponent(lookup) as Container;
|
||||
|
||||
if (providesComboCounter && components != null)
|
||||
{
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
return base.GetDrawableComponent(lookup) as Container;
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is Drawable d)
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// Our own ruleset components default.
|
||||
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is Drawable d)
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// Our own ruleset components default.
|
||||
@ -74,6 +74,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyComboCounter(),
|
||||
new LegacyKeyCounterDisplay(),
|
||||
}
|
||||
};
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup component)
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
switch (component)
|
||||
switch (lookup)
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
break;
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(component);
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
Normal file
Binary file not shown.
@ -7,21 +7,25 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -39,6 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; } = null!;
|
||||
|
||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
|
||||
[SetUpSteps]
|
||||
@ -46,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
|
||||
|
||||
AddStep("reload skin editor", () =>
|
||||
@ -369,6 +377,93 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
() => Is.EqualTo(3));
|
||||
}
|
||||
|
||||
private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null);
|
||||
|
||||
private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null);
|
||||
|
||||
[Test]
|
||||
public void TestMigrationArgon()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old argon skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"argon-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<ArgonComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new ArgonComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationTriangles()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old triangles skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"triangles-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<DefaultComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new DefaultComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationLegacy()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<LegacyComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
private Skin importSkinFromArchives(string filename)
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||
}
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
|
||||
|
@ -4,7 +4,6 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLegacyComboCounterHiddenByRulesetImplementation()
|
||||
{
|
||||
AddToggleStep("toggle legacy hidden by ruleset", visible =>
|
||||
{
|
||||
foreach (var legacyCounter in this.ChildrenOfType<LegacyComboCounter>())
|
||||
legacyCounter.HiddenByRulesetImplementation = visible;
|
||||
});
|
||||
|
||||
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using JetBrains.Annotations;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@ -93,15 +94,12 @@ namespace osu.Game.Skinning
|
||||
// Temporary until default skin has a valid hit lighting.
|
||||
if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty();
|
||||
|
||||
if (base.GetDrawableComponent(lookup) is Drawable c)
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c)
|
||||
return c;
|
||||
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle global level defaults for now.
|
||||
if (containerLookup.Ruleset != null)
|
||||
return null;
|
||||
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
@ -114,7 +112,22 @@ namespace osu.Game.Skinning
|
||||
return songSelectComponents;
|
||||
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
|
||||
if (containerLookup.Ruleset != null)
|
||||
{
|
||||
return new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new ArgonComboCounter
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Position = new Vector2(36, -66),
|
||||
Scale = new Vector2(1.3f),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var mainHUDComponents = new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var health = container.OfType<ArgonHealthDisplay>().FirstOrDefault();
|
||||
var healthLine = container.OfType<BoxElement>().FirstOrDefault();
|
||||
@ -122,7 +135,6 @@ namespace osu.Game.Skinning
|
||||
var score = container.OfType<ArgonScoreCounter>().FirstOrDefault();
|
||||
var accuracy = container.OfType<ArgonAccuracyCounter>().FirstOrDefault();
|
||||
var performancePoints = container.OfType<ArgonPerformancePointsCounter>().FirstOrDefault();
|
||||
var combo = container.OfType<ArgonComboCounter>().FirstOrDefault();
|
||||
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
|
||||
var keyCounter = container.OfType<ArgonKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
@ -203,13 +215,6 @@ namespace osu.Game.Skinning
|
||||
keyCounter.Origin = Anchor.BottomRight;
|
||||
keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
|
||||
}
|
||||
|
||||
if (combo != null && hitError != null)
|
||||
{
|
||||
combo.Anchor = Anchor.BottomLeft;
|
||||
combo.Origin = Anchor.BottomLeft;
|
||||
combo.Position = new Vector2((hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -239,10 +244,6 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
new ArgonComboCounter
|
||||
{
|
||||
Scale = new Vector2(1.3f)
|
||||
},
|
||||
new BarHitErrorMeter(),
|
||||
new BarHitErrorMeter(),
|
||||
new ArgonSongProgress(),
|
||||
@ -250,7 +251,7 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
};
|
||||
|
||||
return skinnableTargetWrapper;
|
||||
return mainHUDComponents;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -43,18 +43,6 @@ namespace osu.Game.Skinning
|
||||
|
||||
private readonly Container counterContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Hides the combo counter internally without affecting its <see cref="SerialisedDrawableInfo"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible,
|
||||
/// without potentially affecting the user's selected skin.
|
||||
/// </remarks>
|
||||
public bool HiddenByRulesetImplementation
|
||||
{
|
||||
set => counterContainer.Alpha = value ? 1 : 0;
|
||||
}
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public LegacyComboCounter()
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Audio;
|
||||
@ -349,19 +350,24 @@ namespace osu.Game.Skinning
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (base.GetDrawableComponent(lookup) is Drawable c)
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c)
|
||||
return c;
|
||||
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle global level defaults for now.
|
||||
if (containerLookup.Ruleset != null)
|
||||
return null;
|
||||
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
if (containerLookup.Ruleset != null)
|
||||
{
|
||||
return new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new LegacyComboCounter(),
|
||||
};
|
||||
}
|
||||
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
||||
@ -394,7 +400,6 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyComboCounter(),
|
||||
new LegacyScoreCounter(),
|
||||
new LegacyAccuracyCounter(),
|
||||
new LegacySongProgress(),
|
||||
|
@ -14,18 +14,21 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
public abstract class Skin : IDisposable, ISkin
|
||||
{
|
||||
private readonly IStorageResourceProvider? resources;
|
||||
|
||||
/// <summary>
|
||||
/// A texture store which can be used to perform user file lookups for this skin.
|
||||
/// </summary>
|
||||
@ -68,6 +71,8 @@ namespace osu.Game.Skinning
|
||||
/// <param name="configurationFilename">An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini".</param>
|
||||
protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? fallbackStore = null, string configurationFilename = @"skin.ini")
|
||||
{
|
||||
this.resources = resources;
|
||||
|
||||
Name = skin.Name;
|
||||
|
||||
if (resources != null)
|
||||
@ -131,40 +136,9 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
string jsonContent = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
SkinLayoutInfo? layoutInfo = null;
|
||||
|
||||
// handle namespace changes...
|
||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
|
||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
|
||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter");
|
||||
|
||||
try
|
||||
{
|
||||
// First attempt to deserialise using the new SkinLayoutInfo format
|
||||
layoutInfo = JsonConvert.DeserializeObject<SkinLayoutInfo>(jsonContent);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// Of note, the migration code below runs on read of skins, but there's nothing to
|
||||
// force a rewrite after migration. Let's not remove these migration rules until we
|
||||
// have something in place to ensure we don't end up breaking skins of users that haven't
|
||||
// manually saved their skin since a change was implemented.
|
||||
|
||||
// If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
|
||||
var layoutInfo = parseLayoutInfo(jsonContent, skinnableTarget);
|
||||
if (layoutInfo == null)
|
||||
{
|
||||
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
|
||||
|
||||
if (deserializedContent == null)
|
||||
continue;
|
||||
|
||||
layoutInfo = new SkinLayoutInfo();
|
||||
layoutInfo.Update(null, deserializedContent.ToArray());
|
||||
|
||||
Logger.Log($"Ferrying {deserializedContent.Count()} components in {skinnableTarget} to global section of new {nameof(SkinLayoutInfo)} format");
|
||||
}
|
||||
continue;
|
||||
|
||||
LayoutInfos[skinnableTarget] = layoutInfo;
|
||||
}
|
||||
@ -220,7 +194,7 @@ namespace osu.Game.Skinning
|
||||
if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null;
|
||||
if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null;
|
||||
|
||||
return new Container
|
||||
return new UserConfiguredLayoutContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance())
|
||||
@ -230,6 +204,81 @@ namespace osu.Game.Skinning
|
||||
return null;
|
||||
}
|
||||
|
||||
#region Deserialisation & Migration
|
||||
|
||||
private SkinLayoutInfo? parseLayoutInfo(string jsonContent, SkinComponentsContainerLookup.TargetArea target)
|
||||
{
|
||||
SkinLayoutInfo? layout = null;
|
||||
|
||||
// handle namespace changes...
|
||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
|
||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
|
||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter");
|
||||
|
||||
try
|
||||
{
|
||||
// First attempt to deserialise using the new SkinLayoutInfo format
|
||||
layout = JsonConvert.DeserializeObject<SkinLayoutInfo>(jsonContent);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
|
||||
if (layout == null)
|
||||
{
|
||||
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
|
||||
if (deserializedContent == null)
|
||||
return null;
|
||||
|
||||
layout = new SkinLayoutInfo { Version = 0 };
|
||||
layout.Update(null, deserializedContent.ToArray());
|
||||
|
||||
Logger.Log($"Ferrying {deserializedContent.Count()} components in {target} to global section of new {nameof(SkinLayoutInfo)} format");
|
||||
}
|
||||
|
||||
for (int i = layout.Version + 1; i <= SkinLayoutInfo.LATEST_VERSION; i++)
|
||||
applyMigration(layout, target, i);
|
||||
|
||||
layout.Version = SkinLayoutInfo.LATEST_VERSION;
|
||||
return layout;
|
||||
}
|
||||
|
||||
private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup.TargetArea target, int version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents ||
|
||||
!layout.TryGetDrawableInfo(null, out var globalHUDComponents) ||
|
||||
resources == null)
|
||||
break;
|
||||
|
||||
var comboCounters = globalHUDComponents.Where(c =>
|
||||
c.Type.Name == nameof(LegacyComboCounter) ||
|
||||
c.Type.Name == nameof(DefaultComboCounter) ||
|
||||
c.Type.Name == nameof(ArgonComboCounter)).ToArray();
|
||||
|
||||
layout.Update(null, globalHUDComponents.Except(comboCounters).ToArray());
|
||||
|
||||
resources.RealmAccess.Run(r =>
|
||||
{
|
||||
foreach (var ruleset in r.All<RulesetInfo>())
|
||||
{
|
||||
layout.Update(ruleset, layout.TryGetDrawableInfo(ruleset, out var rulesetHUDComponents)
|
||||
? rulesetHUDComponents.Concat(comboCounters).ToArray()
|
||||
: comboCounters);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disposal
|
||||
|
||||
~Skin()
|
||||
|
@ -19,12 +19,26 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
private const string global_identifier = @"global";
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<SerialisedDrawableInfo> AllDrawables => DrawableInfo.Values.SelectMany(v => v);
|
||||
/// <summary>
|
||||
/// Latest version representing the schema of the skin layout.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>0: Initial version of all skin layouts.</description></item>
|
||||
/// <item><description>1: Moves existing combo counters from global to per-ruleset HUD targets.</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public const int LATEST_VERSION = 1;
|
||||
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public int Version = LATEST_VERSION;
|
||||
|
||||
[JsonProperty]
|
||||
public Dictionary<string, SerialisedDrawableInfo[]> DrawableInfo { get; set; } = new Dictionary<string, SerialisedDrawableInfo[]>();
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<SerialisedDrawableInfo> AllDrawables => DrawableInfo.Values.SelectMany(v => v);
|
||||
|
||||
public bool TryGetDrawableInfo(RulesetInfo? ruleset, [NotNullWhen(true)] out SerialisedDrawableInfo[]? components) =>
|
||||
DrawableInfo.TryGetValue(ruleset?.ShortName ?? global_identifier, out components);
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Skinning
|
||||
// Temporary until default skin has a valid hit lighting.
|
||||
if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty();
|
||||
|
||||
if (base.GetDrawableComponent(lookup) is Drawable c)
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c)
|
||||
return c;
|
||||
|
||||
switch (lookup)
|
||||
|
15
osu.Game/Skinning/UserConfiguredLayoutContainer.cs
Normal file
15
osu.Game/Skinning/UserConfiguredLayoutContainer.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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 osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
{
|
||||
/// <summary>
|
||||
/// This signifies that a <see cref="Skin.GetDrawableComponent"/> call resolved a configuration created
|
||||
/// by a user in their skin. Generally this should be given priority over any local defaults or overrides.
|
||||
/// </summary>
|
||||
public partial class UserConfiguredLayoutContainer : Container
|
||||
{
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user