diff --git a/osu.Android.props b/osu.Android.props
index 839f7882bf..1b5461959a 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 3a7efb6263..80b9436b2c 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Catch
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin);
- public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this);
+ public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator();
public int LegacyID => 2;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index 6fefb2e5bb..b30b85be2d 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
private int tinyTicksMissed;
private int misses;
- public CatchPerformanceCalculator(Ruleset ruleset)
- : base(ruleset)
+ public CatchPerformanceCalculator()
+ : base(new CatchRuleset())
{
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 1eaf45e54a..b347cc9ae2 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private int countMeh;
private int countMiss;
- public ManiaPerformanceCalculator(Ruleset ruleset)
- : base(ruleset)
+ public ManiaPerformanceCalculator()
+ : base(new ManiaRuleset())
{
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index d04ef69dd2..bd6a67bf67 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
- public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this);
+ public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator();
public const string SHORT_NAME = "mania";
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 5644b2009d..a93a1641a1 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double effectiveMissCount;
- public OsuPerformanceCalculator(Ruleset ruleset)
- : base(ruleset)
+ public OsuPerformanceCalculator()
+ : base(new OsuRuleset())
{
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 3acec4498d..1447f131c6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -2,23 +2,22 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
-using osuTK;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
-using osuTK.Graphics;
using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -126,18 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray();
-
- var slidingSamples = new List();
-
- var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
- if (normalSample != null)
- slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide"));
-
- var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
- if (whistleSample != null)
- slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle"));
-
- slidingSample.Samples = slidingSamples.ToArray();
+ slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray();
}
public override void StopAllSamples()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index ec1387eb54..64964ed396 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -74,6 +74,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
}
+ protected override void LoadSamples()
+ {
+ // Tail models don't actually get samples, as the playback is handled by DrawableSlider.
+ // This override is only here for visibility in explaining this weird flow.
+ }
+
+ public override void PlaySamples()
+ {
+ // Tail models don't actually get samples, as the playback is handled by DrawableSlider.
+ // This override is only here for visibility in explaining this weird flow.
+ }
+
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index c6db02ee02..a904658a4c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -121,15 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.LoadSamples();
- var firstSample = HitObject.Samples.FirstOrDefault();
-
- if (firstSample != null)
- {
- var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin");
-
- spinningSample.Samples = new ISampleInfo[] { clone };
- spinningSample.Frequency.Value = spinning_sample_initial_frequency;
- }
+ spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray();
+ spinningSample.Frequency.Value = spinning_sample_initial_frequency;
}
private void updateSpinningSample(ValueChangedEvent tracking)
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 5c1c3fd253..776165cfb4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -29,6 +29,23 @@ namespace osu.Game.Rulesets.Osu.Objects
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
+ public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray();
+
+ public IList CreateSlidingSamples()
+ {
+ var slidingSamples = new List();
+
+ var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
+ if (normalSample != null)
+ slidingSamples.Add(normalSample.With("sliderslide"));
+
+ var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
+ if (whistleSample != null)
+ slidingSamples.Add(whistleSample.With("sliderwhistle"));
+
+ return slidingSamples;
+ }
+
private readonly Cached endPositionCache = new Cached();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 1eddfb7fef..ddee4d3ebd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . 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.Linq;
using System.Threading;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
@@ -73,5 +77,20 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override IList AuxiliarySamples => CreateSpinningSamples();
+
+ public HitSampleInfo[] CreateSpinningSamples()
+ {
+ var referenceSample = Samples.FirstOrDefault();
+
+ if (referenceSample == null)
+ return Array.Empty();
+
+ return new[]
+ {
+ SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin")
+ };
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 92b90339f9..2fdf42fca1 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap);
- public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this);
+ public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator();
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 9e73390fad..a8122551ff 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countMeh;
private int countMiss;
- public TaikoPerformanceCalculator(Ruleset ruleset)
- : base(ruleset)
+ public TaikoPerformanceCalculator()
+ : base(new TaikoRuleset())
{
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 8934b64ca5..615fbf093f 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
- public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this);
+ public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator();
public int LegacyID => 1;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
index 4011a6bc82..34e6d1996d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
@@ -52,6 +53,49 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("skinnable sprite has correct size", () => sprites.Any(s => Precision.AlmostEquals(s.ChildrenOfType().Single().Size, new Vector2(128, 128))));
}
+ [Test]
+ public void TestFlippedSprite()
+ {
+ const string lookup_name = "hitcircleoverlay";
+
+ AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
+ AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
+ AddStep("flip sprites", () => sprites.ForEach(s =>
+ {
+ s.FlipH = true;
+ s.FlipV = true;
+ }));
+ AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
+ }
+
+ [Test]
+ public void TestNegativeScale()
+ {
+ const string lookup_name = "hitcircleoverlay";
+
+ AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
+ AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
+ AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
+ AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
+ }
+
+ [Test]
+ public void TestNegativeScaleWithFlippedSprite()
+ {
+ const string lookup_name = "hitcircleoverlay";
+
+ AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
+ AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
+ AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(-1)));
+ AddAssert("origin flipped", () => sprites.All(s => s.Origin == Anchor.BottomRight));
+ AddStep("flip sprites", () => sprites.ForEach(s =>
+ {
+ s.FlipH = true;
+ s.FlipV = true;
+ }));
+ AddAssert("origin back", () => sprites.All(s => s.Origin == Anchor.TopLeft));
+ }
+
private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
=> new DrawableStoryboardSprite(
new StoryboardSprite(lookupName, origin, initialPosition)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
index ab9758a2ee..7febb54010 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
@@ -48,7 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("create display", () => recreateDisplay(new OsuHitWindows(), 5));
- AddRepeatStep("New random judgement", () => newJudgement(), 40);
+ AddRepeatStep("New random judgement", () =>
+ {
+ double offset = RNG.Next(-150, 150);
+ newJudgement(offset, drawableRuleset.HitWindows.ResultFor(offset));
+ }, 400);
AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index d331b818a1..58d18e1b21 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -6,6 +6,8 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
+using osu.Framework.Layout;
+using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Screens;
@@ -66,7 +68,7 @@ namespace osu.Game.Graphics.Containers
this.targetMode = targetMode;
RelativeSizeAxes = Axes.Both;
- InternalChild = sizableContainer = new AlwaysInputContainer
+ InternalChild = sizableContainer = new SizeableAlwaysInputContainer(targetMode == ScalingMode.Everything)
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
@@ -209,13 +211,50 @@ namespace osu.Game.Graphics.Containers
}
}
- private class AlwaysInputContainer : Container
+ private class SizeableAlwaysInputContainer : Container
{
+ [Resolved]
+ private GameHost host { get; set; }
+
+ [Resolved]
+ private ISafeArea safeArea { get; set; }
+
+ private readonly bool confineHostCursor;
+ private readonly LayoutValue cursorRectCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
+
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
- public AlwaysInputContainer()
+ ///
+ /// Container used for sizing/positioning purposes in . Always receives mouse input.
+ ///
+ /// Whether to confine the host cursor to the draw area of this container.
+ /// Cursor confinement will abide by the setting.
+ public SizeableAlwaysInputContainer(bool confineHostCursor)
{
RelativeSizeAxes = Axes.Both;
+ this.confineHostCursor = confineHostCursor;
+
+ if (confineHostCursor)
+ AddLayout(cursorRectCache);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (confineHostCursor && !cursorRectCache.IsValid)
+ {
+ updateHostCursorConfineRect();
+ cursorRectCache.Validate();
+ }
+ }
+
+ private void updateHostCursorConfineRect()
+ {
+ if (host.Window == null) return;
+
+ bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero;
+ host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat;
}
}
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index fb81e4fd14..ae117d03d2 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -1140,10 +1140,8 @@ namespace osu.Game
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
}
- protected virtual void ScreenChanged(IScreen current, IScreen newScreen)
+ private void screenChanged(IScreen current, IScreen newScreen)
{
- skinEditor.Reset();
-
switch (newScreen)
{
case IntroScreen intro:
@@ -1185,13 +1183,15 @@ namespace osu.Game
else
BackButton.Hide();
}
+
+ skinEditor.SetTarget((Screen)newScreen);
}
- private void screenPushed(IScreen lastScreen, IScreen newScreen) => ScreenChanged(lastScreen, newScreen);
+ private void screenPushed(IScreen lastScreen, IScreen newScreen) => screenChanged(lastScreen, newScreen);
private void screenExited(IScreen lastScreen, IScreen newScreen)
{
- ScreenChanged(lastScreen, newScreen);
+ screenChanged(lastScreen, newScreen);
if (newScreen == null)
Exit();
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 8b75de9718..5468db348e 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -69,7 +69,7 @@ namespace osu.Game
///
private const double global_track_volume_adjust = 0.8;
- public bool UseDevelopmentServer { get; }
+ public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
@@ -177,7 +177,6 @@ namespace osu.Game
public OsuGameBase()
{
- UseDevelopmentServer = DebugUtils.IsDebugBuild;
Name = @"osu!";
}
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index c590cc302f..57b897e5b5 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Threading;
using JetBrains.Annotations;
using Newtonsoft.Json;
@@ -67,6 +68,12 @@ namespace osu.Game.Rulesets.Objects
}
}
+ ///
+ /// Any samples which may be used by this hit object that are non-standard.
+ /// This is used only to preload these samples ahead of time.
+ ///
+ public virtual IList AuxiliarySamples => ImmutableList.Empty;
+
public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT;
public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT;
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index b091803406..8dc037c7c8 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -500,12 +500,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
public bool Equals(LegacyHitSampleInfo? other)
- => base.Equals(other) && CustomSampleBank == other.CustomSampleBank && IsLayered == other.IsLayered;
+ => base.Equals(other) && CustomSampleBank == other.CustomSampleBank;
public override bool Equals(object? obj)
=> obj is LegacyHitSampleInfo other && Equals(other);
- public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank, IsLayered);
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank);
}
private class FileHitSampleInfo : LegacyHitSampleInfo, IEquatable
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index 30e71dde1c..cd8f99db8b 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -3,22 +3,22 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Audio;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
-using System.Diagnostics;
namespace osu.Game.Rulesets.UI
{
@@ -264,10 +264,25 @@ namespace osu.Game.Rulesets.UI
var entry = CreateLifetimeEntry(hitObject);
lifetimeEntryMap[entry.HitObject] = entry;
+ preloadSamples(hitObject);
+
HitObjectContainer.Add(entry);
OnHitObjectAdded(entry.HitObject);
}
+ private void preloadSamples(HitObject hitObject)
+ {
+ // prepare sample pools ahead of time so we're not initialising at runtime.
+ foreach (var sample in hitObject.Samples)
+ prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample));
+
+ foreach (var sample in hitObject.AuxiliarySamples)
+ prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample));
+
+ foreach (var nestedObject in hitObject.NestedHitObjects)
+ preloadSamples(nestedObject);
+ }
+
///
/// Removes a for a pooled from this .
///
@@ -330,22 +345,7 @@ namespace osu.Game.Rulesets.UI
DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent)
{
- var lookupType = hitObject.GetType();
-
- IDrawablePool pool;
-
- // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists.
- if (!pools.TryGetValue(lookupType, out pool))
- {
- foreach (var (t, p) in pools)
- {
- if (!t.IsInstanceOfType(hitObject))
- continue;
-
- pools[lookupType] = pool = p;
- break;
- }
- }
+ var pool = prepareDrawableHitObjectPool(hitObject);
return (DrawableHitObject)pool?.Get(d =>
{
@@ -372,14 +372,39 @@ namespace osu.Game.Rulesets.UI
});
}
+ private IDrawablePool prepareDrawableHitObjectPool(HitObject hitObject)
+ {
+ var lookupType = hitObject.GetType();
+
+ IDrawablePool pool;
+
+ // Tests may add derived hitobject instances for which pools don't exist. Try to find any applicable pool and dynamically assign the type if the pool exists.
+ if (!pools.TryGetValue(lookupType, out pool))
+ {
+ foreach (var (t, p) in pools)
+ {
+ if (!t.IsInstanceOfType(hitObject))
+ continue;
+
+ pools[lookupType] = pool = p;
+ break;
+ }
+ }
+
+ return pool;
+ }
+
private readonly Dictionary> samplePools = new Dictionary>();
- public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo)
- {
- if (!samplePools.TryGetValue(sampleInfo, out var existingPool))
- AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1));
+ public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo) => prepareSamplePool(sampleInfo).Get();
- return existingPool.Get();
+ private DrawablePool prepareSamplePool(ISampleInfo sampleInfo)
+ {
+ if (samplePools.TryGetValue(sampleInfo, out var pool)) return pool;
+
+ AddInternal(samplePools[sampleInfo] = pool = new DrawableSamplePool(sampleInfo, 1));
+
+ return pool;
}
private class DrawableSamplePool : DrawablePool
diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs
index afe75c5ef7..a6b54dd1f2 100644
--- a/osu.Game/Screens/Menu/IntroScreen.cs
+++ b/osu.Game/Screens/Menu/IntroScreen.cs
@@ -13,14 +13,17 @@ using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Utils;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.IO.Archives;
+using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds;
+using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
using Realms;
@@ -55,7 +58,8 @@ namespace osu.Game.Screens.Menu
private const int exit_delay = 3000;
- private Sample seeya;
+ private SkinnableSound skinnableSeeya;
+ private ISample seeya;
protected virtual string SeeyaSampleName => "Intro/seeya";
@@ -90,14 +94,18 @@ namespace osu.Game.Screens.Menu
private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm)
+ private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm, IAPIProvider api)
{
// prevent user from changing beatmap while the intro is still running.
beatmap = Beatmap.BeginLease(false);
MenuVoice = config.GetBindable(OsuSetting.MenuVoice);
MenuMusic = config.GetBindable(OsuSetting.MenuMusic);
- seeya = audio.Samples.Get(SeeyaSampleName);
+
+ if (api.LocalUser.Value.IsSupporter)
+ AddInternal(skinnableSeeya = new SkinnableSound(new SampleInfo(SeeyaSampleName)));
+ else
+ seeya = audio.Samples.Get(SeeyaSampleName);
// if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection.
if (!MenuMusic.Value)
@@ -201,7 +209,15 @@ namespace osu.Game.Screens.Menu
// we also handle the exit transition.
if (MenuVoice.Value)
{
- seeya.Play();
+ if (skinnableSeeya != null)
+ {
+ // resuming a screen (i.e. calling OnResume) happens before the screen itself becomes alive,
+ // therefore skinnable samples may not be updated yet with the recently selected skin.
+ // schedule after children to ensure skinnable samples have processed skin changes before playing.
+ ScheduleAfterChildren(() => skinnableSeeya.Play());
+ }
+ else
+ seeya.Play();
// if playing the outro voice, we have more time to have fun with the background track.
// initially fade to almost silent then ramp out over the remaining time.
diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs
index 639591cfef..27eaa7eb3a 100644
--- a/osu.Game/Screens/Menu/IntroWelcome.cs
+++ b/osu.Game/Screens/Menu/IntroWelcome.cs
@@ -13,7 +13,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Online.API;
using osu.Game.Screens.Backgrounds;
+using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Screens.Menu
@@ -23,8 +26,11 @@ namespace osu.Game.Screens.Menu
protected override string BeatmapHash => "64e00d7022195959bfa3109d09c2e2276c8f12f486b91fcf6175583e973b48f2";
protected override string BeatmapFile => "welcome.osz";
private const double delay_step_two = 2142;
- private Sample welcome;
- private Sample pianoReverb;
+
+ private SkinnableSound skinnableWelcome;
+ private ISample welcome;
+
+ private ISample pianoReverb;
protected override string SeeyaSampleName => "Intro/Welcome/seeya";
protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false)
@@ -40,10 +46,15 @@ namespace osu.Game.Screens.Menu
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load(AudioManager audio, IAPIProvider api)
{
if (MenuVoice.Value)
- welcome = audio.Samples.Get(@"Intro/Welcome/welcome");
+ {
+ if (api.LocalUser.Value.IsSupporter)
+ AddInternal(skinnableWelcome = new SkinnableSound(new SampleInfo(@"Intro/Welcome/welcome")));
+ else
+ welcome = audio.Samples.Get(@"Intro/Welcome/welcome");
+ }
pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano");
}
@@ -65,7 +76,10 @@ namespace osu.Game.Screens.Menu
AddInternal(intro);
- welcome?.Play();
+ if (skinnableWelcome != null)
+ skinnableWelcome.Play();
+ else
+ welcome?.Play();
var reverbChannel = pianoReverb?.Play();
if (reverbChannel != null)
@@ -100,13 +114,13 @@ namespace osu.Game.Screens.Menu
private class WelcomeIntroSequence : Container
{
- private Sprite welcomeText;
+ private Drawable welcomeText;
private Container scaleContainer;
public LogoVisualisation LogoVisualisation { get; private set; }
[BackgroundDependencyLoader]
- private void load(TextureStore textures)
+ private void load(TextureStore textures, IAPIProvider api)
{
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
@@ -135,15 +149,17 @@ namespace osu.Game.Screens.Menu
Size = new Vector2(480),
Colour = Color4.Black
},
- welcomeText = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Texture = textures.Get(@"Intro/Welcome/welcome_text")
- },
}
},
};
+
+ if (api.LocalUser.Value.IsSupporter)
+ scaleContainer.Add(welcomeText = new SkinnableSprite(@"Intro/Welcome/welcome_text"));
+ else
+ scaleContainer.Add(welcomeText = new Sprite { Texture = textures.Get(@"Intro/Welcome/welcome_text") });
+
+ welcomeText.Anchor = Anchor.Centre;
+ welcomeText.Origin = Anchor.Centre;
}
protected override void LoadComplete()
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index fb8647284f..a2d3b7f4fc 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -334,6 +334,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
// ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not.
r.RoomID.Value = null;
+ // Null out dates because end date is not supported client-side and the settings overlay will populate a duration.
+ r.EndDate.Value = null;
+ r.Duration.Value = null;
+
Open(r);
joiningRoomOperation?.Dispose();
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index 7903e54960..542731cf93 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -13,23 +13,13 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{
public class BarHitErrorMeter : HitErrorMeter
{
- private const int arrow_move_duration = 400;
-
- private const int judgement_line_width = 6;
-
- private const int bar_height = 200;
-
- private const int bar_width = 2;
-
- private const int spacing = 2;
-
- private const float chevron_size = 8;
+ private const int judgement_line_width = 14;
+ private const int judgement_line_height = 4;
private SpriteIcon arrow;
private SpriteIcon iconEarly;
@@ -50,17 +40,94 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new FillFlowContainer
+ const int centre_marker_size = 8;
+ const int bar_height = 200;
+ const int bar_width = 2;
+ const float chevron_size = 8;
+ const float icon_size = 14;
+
+ var hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
+
+ InternalChild = new Container
{
AutoSizeAxes = Axes.X,
Height = bar_height,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(spacing, 0),
Margin = new MarginPadding(2),
Children = new Drawable[]
{
+ colourBars = new Container
+ {
+ Name = "colour axis",
+ X = chevron_size,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = judgement_line_width,
+ RelativeSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ iconEarly = new SpriteIcon
+ {
+ Y = -10,
+ Size = new Vector2(icon_size),
+ Icon = FontAwesome.Solid.ShippingFast,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ },
+ iconLate = new SpriteIcon
+ {
+ Y = 10,
+ Size = new Vector2(icon_size),
+ Icon = FontAwesome.Solid.Bicycle,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.Centre,
+ },
+ colourBarsEarly = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ Width = bar_width,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.5f,
+ Scale = new Vector2(1, -1),
+ },
+ colourBarsLate = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ Width = bar_width,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.5f,
+ },
+ new Circle
+ {
+ Name = "middle marker behind",
+ Colour = GetColourForHitResult(hitWindows.Last().result),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(centre_marker_size),
+ },
+ judgementsContainer = new Container
+ {
+ Name = "judgements",
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Y,
+ Width = judgement_line_width,
+ },
+ new Circle
+ {
+ Name = "middle marker in front",
+ Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(centre_marker_size),
+ Scale = new Vector2(0.5f),
+ },
+ }
+ },
new Container
{
+ Name = "average chevron",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = chevron_size,
@@ -75,58 +142,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Size = new Vector2(chevron_size),
}
},
- colourBars = new Container
- {
- Width = bar_width,
- RelativeSizeAxes = Axes.Y,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Children = new Drawable[]
- {
- colourBarsEarly = new Container
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Scale = new Vector2(1, -1),
- },
- colourBarsLate = new Container
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- },
- iconEarly = new SpriteIcon
- {
- Y = -10,
- Size = new Vector2(10),
- Icon = FontAwesome.Solid.ShippingFast,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.Centre,
- },
- iconLate = new SpriteIcon
- {
- Y = 10,
- Size = new Vector2(10),
- Icon = FontAwesome.Solid.Bicycle,
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.Centre,
- }
- }
- },
- judgementsContainer = new Container
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Width = judgement_line_width,
- RelativeSizeAxes = Axes.Y,
- },
}
};
- createColourBars();
+ createColourBars(hitWindows);
}
protected override void LoadComplete()
@@ -149,10 +168,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
iconLate.Rotation = -Rotation;
}
- private void createColourBars()
+ private void createColourBars((HitResult result, double length)[] windows)
{
- var windows = HitWindows.GetAllAvailableWindows().ToArray();
-
// max to avoid div-by-zero.
maxHitWindow = Math.Max(1, windows.First().length);
@@ -166,17 +183,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
colourBarsLate.Add(createColourBar(result, hitWindow, i == 0));
}
- // a little nub to mark the centre point.
- var centre = createColourBar(windows.Last().result, 0.01f);
- centre.Anchor = centre.Origin = Anchor.CentreLeft;
- centre.Width = 2.5f;
- colourBars.Add(centre);
-
- Drawable createColourBar(HitResult result, float height, bool first = false)
+ Drawable createColourBar(HitResult result, float height, bool requireGradient = false)
{
var colour = GetColourForHitResult(result);
- if (first)
+ if (requireGradient)
{
// the first bar needs gradient rendering.
const float gradient_start = 0.8f;
@@ -220,6 +231,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
protected override void OnNewJudgement(JudgementResult judgement)
{
+ const int arrow_move_duration = 800;
+
if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0)
return;
@@ -243,46 +256,52 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
judgementsContainer.Add(new JudgementLine
{
Y = getRelativeJudgementPosition(judgement.TimeOffset),
- Origin = Anchor.CentreLeft,
+ Colour = GetColourForHitResult(judgement.Type),
});
arrow.MoveToY(
getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1)
- , arrow_move_duration, Easing.Out);
+ , arrow_move_duration, Easing.OutQuint);
}
private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1);
internal class JudgementLine : CompositeDrawable
{
- private const int judgement_fade_duration = 5000;
-
public JudgementLine()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
- Height = 3;
+ Height = judgement_line_height;
- InternalChild = new CircularContainer
+ Blending = BlendingParameters.Additive;
+
+ Origin = Anchor.Centre;
+ Anchor = Anchor.TopCentre;
+
+ InternalChild = new Circle
{
- Masking = true,
RelativeSizeAxes = Axes.Both,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.White,
- }
};
}
protected override void LoadComplete()
{
+ const int judgement_fade_in_duration = 100;
+ const int judgement_fade_out_duration = 5000;
+
base.LoadComplete();
+ Alpha = 0;
Width = 0;
- this.ResizeWidthTo(1, 200, Easing.OutElasticHalf);
- this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration).Expire();
+ this
+ .FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
+ .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint)
+ .Then()
+ .FadeOut(judgement_fade_out_duration)
+ .ResizeWidthTo(0, judgement_fade_out_duration, Easing.InQuint)
+ .Expire();
}
}
diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs
index ae5cbc95f0..459fbf3be5 100644
--- a/osu.Game/Skinning/Editor/SkinEditor.cs
+++ b/osu.Game/Skinning/Editor/SkinEditor.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Skinning.Editor
protected override bool StartHidden => true;
- private readonly Drawable targetScreen;
+ private Drawable targetScreen;
private OsuTextFlowContainer headerText;
@@ -42,11 +42,13 @@ namespace osu.Game.Skinning.Editor
private bool hasBegunMutating;
+ private Container content;
+
public SkinEditor(Drawable targetScreen)
{
- this.targetScreen = targetScreen;
-
RelativeSizeAxes = Axes.Both;
+
+ UpdateTargetScreen(targetScreen);
}
[BackgroundDependencyLoader]
@@ -113,13 +115,9 @@ namespace osu.Game.Skinning.Editor
Origin = Anchor.CentreLeft,
RequestPlacement = placeComponent
},
- new Container
+ content = new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new SkinBlueprintContainer(targetScreen),
- }
},
}
}
@@ -147,6 +145,21 @@ namespace osu.Game.Skinning.Editor
}, true);
}
+ public void UpdateTargetScreen(Drawable targetScreen)
+ {
+ this.targetScreen = targetScreen;
+
+ Scheduler.AddOnce(loadBlueprintContainer);
+
+ void loadBlueprintContainer()
+ {
+ content.Children = new Drawable[]
+ {
+ new SkinBlueprintContainer(targetScreen),
+ };
+ }
+ }
+
private void skinChanged()
{
headerText.Clear();
@@ -171,7 +184,12 @@ namespace osu.Game.Skinning.Editor
private void placeComponent(Type type)
{
- var targetContainer = getTarget(SkinnableTarget.MainHUDComponents);
+ var target = availableTargets.FirstOrDefault()?.Target;
+
+ if (target == null)
+ return;
+
+ var targetContainer = getTarget(target.Value);
if (targetContainer == null)
return;
diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
index 61c363b019..73af2591c8 100644
--- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
+++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -8,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
@@ -114,15 +116,39 @@ namespace osu.Game.Skinning.Editor
}
///
- /// Exit any existing skin editor due to the game state changing.
+ /// Set a new target screen which will be used to find skinnable components.
///
- public void Reset()
+ public void SetTarget(Screen screen)
{
- skinEditor?.Save();
- skinEditor?.Hide();
- skinEditor?.Expire();
+ if (skinEditor == null) return;
- skinEditor = null;
+ skinEditor.Save();
+
+ // AddOnce with parameter will ensure the newest target is loaded if there is any overlap.
+ Scheduler.AddOnce(setTarget, screen);
+ }
+
+ private void setTarget(Screen target)
+ {
+ Debug.Assert(skinEditor != null);
+
+ if (!target.IsCurrentScreen())
+ return;
+
+ if (!target.IsLoaded)
+ {
+ Scheduler.AddOnce(setTarget, target);
+ return;
+ }
+
+ if (skinEditor.State.Value == Visibility.Visible)
+ skinEditor.UpdateTargetScreen(target);
+ else
+ {
+ skinEditor.Hide();
+ skinEditor.Expire();
+ skinEditor = null;
+ }
}
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
index 81623a9307..88cb5f40a1 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Textures;
@@ -73,31 +72,7 @@ namespace osu.Game.Storyboards.Drawables
protected override Vector2 DrawScale
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale;
- public override Anchor Origin
- {
- get
- {
- var origin = base.Origin;
-
- if (FlipH)
- {
- if (origin.HasFlagFast(Anchor.x0))
- origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
- else if (origin.HasFlagFast(Anchor.x2))
- origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
- }
-
- if (FlipV)
- {
- if (origin.HasFlagFast(Anchor.y0))
- origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
- else if (origin.HasFlagFast(Anchor.y2))
- origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
- }
-
- return origin;
- }
- }
+ public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV);
public override bool IsPresent
=> !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent;
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs
index eb877f3dff..db10f13896 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
@@ -73,31 +72,7 @@ namespace osu.Game.Storyboards.Drawables
protected override Vector2 DrawScale
=> new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y) * VectorScale;
- public override Anchor Origin
- {
- get
- {
- var origin = base.Origin;
-
- if (FlipH)
- {
- if (origin.HasFlagFast(Anchor.x0))
- origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
- else if (origin.HasFlagFast(Anchor.x2))
- origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
- }
-
- if (FlipV)
- {
- if (origin.HasFlagFast(Anchor.y0))
- origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
- else if (origin.HasFlagFast(Anchor.y2))
- origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
- }
-
- return origin;
- }
- }
+ public override Anchor Origin => StoryboardExtensions.AdjustOrigin(base.Origin, VectorScale, FlipH, FlipV);
public override bool IsPresent
=> !float.IsNaN(DrawPosition.X) && !float.IsNaN(DrawPosition.Y) && base.IsPresent;
diff --git a/osu.Game/Storyboards/StoryboardExtensions.cs b/osu.Game/Storyboards/StoryboardExtensions.cs
new file mode 100644
index 0000000000..4e8251c9e7
--- /dev/null
+++ b/osu.Game/Storyboards/StoryboardExtensions.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Extensions.EnumExtensions;
+using osu.Framework.Graphics;
+using osuTK;
+
+namespace osu.Game.Storyboards
+{
+ public static class StoryboardExtensions
+ {
+ ///
+ /// Given an origin and a set of properties, adjust the origin to display the sprite/animation correctly.
+ ///
+ /// The current origin.
+ /// The vector scale.
+ /// Whether the element is flipped horizontally.
+ /// Whether the element is flipped vertically.
+ /// The adjusted origin.
+ public static Anchor AdjustOrigin(Anchor origin, Vector2 vectorScale, bool flipH, bool flipV)
+ {
+ // Either flip horizontally or negative X scale, but not both.
+ if (flipH ^ (vectorScale.X < 0))
+ {
+ if (origin.HasFlagFast(Anchor.x0))
+ origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
+ else if (origin.HasFlagFast(Anchor.x2))
+ origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
+ }
+
+ // Either flip vertically or negative Y scale, but not both.
+ if (flipV ^ (vectorScale.Y < 0))
+ {
+ if (origin.HasFlagFast(Anchor.y0))
+ origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
+ else if (origin.HasFlagFast(Anchor.y2))
+ origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
+ }
+
+ return origin;
+ }
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index c106c373c4..5e194e2aca 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 86cf1b229c..23101c5af6 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,7 +61,7 @@
-
+
@@ -84,7 +84,7 @@
-
+