diff --git a/.github/ISSUE_TEMPLATE/02-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
deleted file mode 100644
index c3357dd780..0000000000
--- a/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: Feature Request
-about: Propose a feature you would like to see in the game!
----
-**Describe the new feature:**
-
-**Proposal designs of the feature:**
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 69baeee60c..c62231e8e0 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,12 @@
blank_issues_enabled: false
contact_links:
+ - name: Suggestions or feature request
+ url: https://github.com/ppy/osu/discussions/categories/ideas
+ about: Got something you think should change or be added? Search for or start a new discussion!
+ - name: Help
+ url: https://github.com/ppy/osu/discussions/categories/q-a
+ about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
- name: osu!stable issues
url: https://github.com/ppy/osu-stable-issues
- about: For issues regarding osu!stable (not osu!lazer), open them here.
+ about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
+
diff --git a/osu.Android.props b/osu.Android.props
index b3842a528d..bcd5f9bd9a 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index e3c457693e..23ce444560 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Catch
switch (result)
{
case HitResult.LargeTickHit:
- return "large droplet";
+ return "Large droplet";
case HitResult.SmallTickHit:
- return "small droplet";
+ return "Small droplet";
case HitResult.LargeBonus:
- return "banana";
+ return "Banana";
}
return base.GetDisplayNameForHitResult(result);
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 1b48832ed6..e43d88ad40 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -28,53 +28,56 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
case HUDSkinComponents.ComboCounter:
// catch may provide its own combo counter; hide the default.
- return providesComboCounter ? Drawable.Empty() : null;
+ if (providesComboCounter)
+ return Drawable.Empty();
+
+ break;
}
}
- if (!(component is CatchSkinComponent catchSkinComponent))
- return null;
-
- switch (catchSkinComponent.Component)
+ if (component is CatchSkinComponent catchSkinComponent)
{
- case CatchSkinComponents.Fruit:
- if (GetTexture("fruit-pear") != null)
- return new LegacyFruitPiece();
+ switch (catchSkinComponent.Component)
+ {
+ case CatchSkinComponents.Fruit:
+ if (GetTexture("fruit-pear") != null)
+ return new LegacyFruitPiece();
- break;
+ return null;
- case CatchSkinComponents.Banana:
- if (GetTexture("fruit-bananas") != null)
- return new LegacyBananaPiece();
+ case CatchSkinComponents.Banana:
+ if (GetTexture("fruit-bananas") != null)
+ return new LegacyBananaPiece();
- break;
+ return null;
- case CatchSkinComponents.Droplet:
- if (GetTexture("fruit-drop") != null)
- return new LegacyDropletPiece();
+ case CatchSkinComponents.Droplet:
+ if (GetTexture("fruit-drop") != null)
+ return new LegacyDropletPiece();
- break;
+ return null;
- case CatchSkinComponents.CatcherIdle:
- return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
- this.GetAnimation("fruit-ryuuta", true, true, true);
+ case CatchSkinComponents.CatcherIdle:
+ return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
+ this.GetAnimation("fruit-ryuuta", true, true, true);
- case CatchSkinComponents.CatcherFail:
- return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
- this.GetAnimation("fruit-ryuuta", true, true, true);
+ case CatchSkinComponents.CatcherFail:
+ return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
+ this.GetAnimation("fruit-ryuuta", true, true, true);
- case CatchSkinComponents.CatcherKiai:
- return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
- this.GetAnimation("fruit-ryuuta", true, true, true);
+ case CatchSkinComponents.CatcherKiai:
+ return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
+ this.GetAnimation("fruit-ryuuta", true, true, true);
- case CatchSkinComponents.CatchComboCounter:
- if (providesComboCounter)
- return new LegacyCatchComboCounter(Source);
+ case CatchSkinComponents.CatchComboCounter:
+ if (providesComboCounter)
+ return new LegacyCatchComboCounter(Source);
- break;
+ return null;
+ }
}
- return null;
+ return Source.GetDrawableComponent(component);
}
public override IBindable GetConfig(TLookup lookup)
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 24ccae895d..261b8b1fad 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
break;
}
- return null;
+ return Source.GetDrawableComponent(component);
}
private Drawable getResult(HitResult result)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index d81af053d1..cd6bf1d8d2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -7,13 +7,14 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece
+ public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, IHasMainCirclePiece
{
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
@@ -111,7 +112,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
- public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
- Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
+ protected override void OnApply()
+ {
+ base.OnApply();
+
+ if (Slider != null)
+ Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 88302ebc57..ffd4f78400 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -34,90 +34,90 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
public override Drawable GetDrawableComponent(ISkinComponent component)
{
- if (!(component is OsuSkinComponent osuComponent))
- return null;
-
- switch (osuComponent.Component)
+ if (component is OsuSkinComponent osuComponent)
{
- case OsuSkinComponents.FollowPoint:
- return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
+ switch (osuComponent.Component)
+ {
+ case OsuSkinComponents.FollowPoint:
+ return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
- case OsuSkinComponents.SliderFollowCircle:
- var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
- if (followCircle != null)
- // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
- followCircle.Scale *= 0.5f;
- return followCircle;
+ case OsuSkinComponents.SliderFollowCircle:
+ var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
+ if (followCircle != null)
+ // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
+ followCircle.Scale *= 0.5f;
+ return followCircle;
- case OsuSkinComponents.SliderBall:
- var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
+ case OsuSkinComponents.SliderBall:
+ var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
- // todo: slider ball has a custom frame delay based on velocity
- // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
+ // todo: slider ball has a custom frame delay based on velocity
+ // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
- if (sliderBallContent != null)
- return new LegacySliderBall(sliderBallContent);
+ if (sliderBallContent != null)
+ return new LegacySliderBall(sliderBallContent);
- return null;
-
- case OsuSkinComponents.SliderBody:
- if (hasHitCircle.Value)
- return new LegacySliderBody();
-
- return null;
-
- case OsuSkinComponents.SliderTailHitCircle:
- if (hasHitCircle.Value)
- return new LegacyMainCirclePiece("sliderendcircle", false);
-
- return null;
-
- case OsuSkinComponents.SliderHeadHitCircle:
- if (hasHitCircle.Value)
- return new LegacyMainCirclePiece("sliderstartcircle");
-
- return null;
-
- case OsuSkinComponents.HitCircle:
- if (hasHitCircle.Value)
- return new LegacyMainCirclePiece();
-
- return null;
-
- case OsuSkinComponents.Cursor:
- if (Source.GetTexture("cursor") != null)
- return new LegacyCursor();
-
- return null;
-
- case OsuSkinComponents.CursorTrail:
- if (Source.GetTexture("cursortrail") != null)
- return new LegacyCursorTrail();
-
- return null;
-
- case OsuSkinComponents.HitCircleText:
- if (!this.HasFont(LegacyFont.HitCircle))
return null;
- return new LegacySpriteText(LegacyFont.HitCircle)
- {
- // stable applies a blanket 0.8x scale to hitcircle fonts
- Scale = new Vector2(0.8f),
- };
+ case OsuSkinComponents.SliderBody:
+ if (hasHitCircle.Value)
+ return new LegacySliderBody();
- case OsuSkinComponents.SpinnerBody:
- bool hasBackground = Source.GetTexture("spinner-background") != null;
+ return null;
- if (Source.GetTexture("spinner-top") != null && !hasBackground)
- return new LegacyNewStyleSpinner();
- else if (hasBackground)
- return new LegacyOldStyleSpinner();
+ case OsuSkinComponents.SliderTailHitCircle:
+ if (hasHitCircle.Value)
+ return new LegacyMainCirclePiece("sliderendcircle", false);
- return null;
+ return null;
+
+ case OsuSkinComponents.SliderHeadHitCircle:
+ if (hasHitCircle.Value)
+ return new LegacyMainCirclePiece("sliderstartcircle");
+
+ return null;
+
+ case OsuSkinComponents.HitCircle:
+ if (hasHitCircle.Value)
+ return new LegacyMainCirclePiece();
+
+ return null;
+
+ case OsuSkinComponents.Cursor:
+ if (Source.GetTexture("cursor") != null)
+ return new LegacyCursor();
+
+ return null;
+
+ case OsuSkinComponents.CursorTrail:
+ if (Source.GetTexture("cursortrail") != null)
+ return new LegacyCursorTrail();
+
+ return null;
+
+ case OsuSkinComponents.HitCircleText:
+ if (!this.HasFont(LegacyFont.HitCircle))
+ return null;
+
+ return new LegacySpriteText(LegacyFont.HitCircle)
+ {
+ // stable applies a blanket 0.8x scale to hitcircle fonts
+ Scale = new Vector2(0.8f),
+ };
+
+ case OsuSkinComponents.SpinnerBody:
+ bool hasBackground = Source.GetTexture("spinner-background") != null;
+
+ if (Source.GetTexture("spinner-top") != null && !hasBackground)
+ return new LegacyNewStyleSpinner();
+ else if (hasBackground)
+ return new LegacyOldStyleSpinner();
+
+ return null;
+ }
}
- return null;
+ return Source.GetDrawableComponent(component);
}
public override IBindable GetConfig(TLookup lookup)
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index b51f096d7d..90c99316b1 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// Old osu! used hit sounding to determine various hit type information
IList samples = obj.Samples;
- bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
-
switch (obj)
{
case IHasDistance distanceData:
@@ -94,15 +92,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{
IList currentSamples = allSamples[i];
- bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
- strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
yield return new Hit
{
StartTime = j,
- Type = isRim ? HitType.Rim : HitType.Centre,
Samples = currentSamples,
- IsStrong = strong
};
i = (i + 1) % allSamples.Count;
@@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
StartTime = obj.StartTime,
Samples = obj.Samples,
- IsStrong = strong,
Duration = taikoDuration,
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
};
@@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
default:
{
- bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
-
- bool isRim = samples.Any(isRimDefinition);
-
yield return new Hit
{
StartTime = obj.StartTime,
- Type = isRim ? HitType.Rim : HitType.Centre,
Samples = samples,
- IsStrong = strong
};
break;
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
index a24130d6ac..ab3b729307 100644
--- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Taiko.Edit
{
EditorBeatmap.PerformOnSelection(h =>
{
- if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
+ if (h is Hit taikoHit)
+ {
+ taikoHit.Type = state ? HitType.Rim : HitType.Centre;
+ EditorBeatmap.Update(h);
+ }
});
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index f4a66c39a8..b4ed242893 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -17,13 +17,25 @@ namespace osu.Game.Rulesets.Taiko.Objects
public HitType Type
{
get => TypeBindable.Value;
- set
- {
- TypeBindable.Value = value;
- updateSamplesFromType();
- }
+ set => TypeBindable.Value = value;
}
+ public Hit()
+ {
+ TypeBindable.BindValueChanged(_ => updateSamplesFromType());
+ SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
+ }
+
+ private void updateTypeFromSamples()
+ {
+ Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
+ }
+
+ ///
+ /// Returns an array of any samples which would cause this object to be a "rim" type hit.
+ ///
+ private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
+
private void updateSamplesFromType()
{
var rimSamples = getRimSamples();
@@ -42,11 +54,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
}
}
- ///
- /// Returns an array of any samples which would cause this object to be a "rim" type hit.
- ///
- private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
-
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
public class StrongNestedHit : StrongNestedHitObject
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
index cac56d1269..6c17573b50 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
@@ -33,14 +33,21 @@ namespace osu.Game.Rulesets.Taiko.Objects
public bool IsStrong
{
get => IsStrongBindable.Value;
- set
- {
- IsStrongBindable.Value = value;
- updateSamplesFromStrong();
- }
+ set => IsStrongBindable.Value = value;
}
- private void updateSamplesFromStrong()
+ protected TaikoStrongableHitObject()
+ {
+ IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
+ SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
+ }
+
+ private void updateTypeFromSamples()
+ {
+ IsStrong = getStrongSamples().Any();
+ }
+
+ private void updateSamplesFromType()
{
var strongSamples = getStrongSamples();
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
index 3e506f69ce..e0557c8617 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
@@ -38,98 +38,98 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
return Drawable.Empty().With(d => d.Expire());
}
- if (!(component is TaikoSkinComponent taikoComponent))
- return null;
-
- switch (taikoComponent.Component)
+ if (component is TaikoSkinComponent taikoComponent)
{
- case TaikoSkinComponents.DrumRollBody:
- if (GetTexture("taiko-roll-middle") != null)
- return new LegacyDrumRoll();
+ switch (taikoComponent.Component)
+ {
+ case TaikoSkinComponents.DrumRollBody:
+ if (GetTexture("taiko-roll-middle") != null)
+ return new LegacyDrumRoll();
- return null;
+ return null;
- case TaikoSkinComponents.InputDrum:
- if (GetTexture("taiko-bar-left") != null)
- return new LegacyInputDrum();
+ case TaikoSkinComponents.InputDrum:
+ if (GetTexture("taiko-bar-left") != null)
+ return new LegacyInputDrum();
- return null;
+ return null;
- case TaikoSkinComponents.CentreHit:
- case TaikoSkinComponents.RimHit:
+ case TaikoSkinComponents.CentreHit:
+ case TaikoSkinComponents.RimHit:
- if (GetTexture("taikohitcircle") != null)
- return new LegacyHit(taikoComponent.Component);
+ if (GetTexture("taikohitcircle") != null)
+ return new LegacyHit(taikoComponent.Component);
- return null;
+ return null;
- case TaikoSkinComponents.DrumRollTick:
- return this.GetAnimation("sliderscorepoint", false, false);
+ case TaikoSkinComponents.DrumRollTick:
+ return this.GetAnimation("sliderscorepoint", false, false);
- case TaikoSkinComponents.HitTarget:
- if (GetTexture("taikobigcircle") != null)
- return new TaikoLegacyHitTarget();
+ case TaikoSkinComponents.HitTarget:
+ if (GetTexture("taikobigcircle") != null)
+ return new TaikoLegacyHitTarget();
- return null;
+ return null;
- case TaikoSkinComponents.PlayfieldBackgroundRight:
- if (GetTexture("taiko-bar-right") != null)
- return new TaikoLegacyPlayfieldBackgroundRight();
+ case TaikoSkinComponents.PlayfieldBackgroundRight:
+ if (GetTexture("taiko-bar-right") != null)
+ return new TaikoLegacyPlayfieldBackgroundRight();
- return null;
+ return null;
- case TaikoSkinComponents.PlayfieldBackgroundLeft:
- // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
- if (GetTexture("taiko-bar-right") != null)
- return Drawable.Empty();
+ case TaikoSkinComponents.PlayfieldBackgroundLeft:
+ // This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
+ if (GetTexture("taiko-bar-right") != null)
+ return Drawable.Empty();
- return null;
+ return null;
- case TaikoSkinComponents.BarLine:
- if (GetTexture("taiko-barline") != null)
- return new LegacyBarLine();
+ case TaikoSkinComponents.BarLine:
+ if (GetTexture("taiko-barline") != null)
+ return new LegacyBarLine();
- return null;
+ return null;
- case TaikoSkinComponents.TaikoExplosionMiss:
+ case TaikoSkinComponents.TaikoExplosionMiss:
- var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
- if (missSprite != null)
- return new LegacyHitExplosion(missSprite);
+ var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
+ if (missSprite != null)
+ return new LegacyHitExplosion(missSprite);
- return null;
+ return null;
- case TaikoSkinComponents.TaikoExplosionOk:
- case TaikoSkinComponents.TaikoExplosionGreat:
+ case TaikoSkinComponents.TaikoExplosionOk:
+ case TaikoSkinComponents.TaikoExplosionGreat:
- var hitName = getHitName(taikoComponent.Component);
- var hitSprite = this.GetAnimation(hitName, true, false);
+ var hitName = getHitName(taikoComponent.Component);
+ var hitSprite = this.GetAnimation(hitName, true, false);
- if (hitSprite != null)
- {
- var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
+ if (hitSprite != null)
+ {
+ var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
- return new LegacyHitExplosion(hitSprite, strongHitSprite);
- }
+ return new LegacyHitExplosion(hitSprite, strongHitSprite);
+ }
- return null;
+ return null;
- case TaikoSkinComponents.TaikoExplosionKiai:
- // suppress the default kiai explosion if the skin brings its own sprites.
- // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
- if (hasExplosion.Value)
- return Drawable.Empty().With(d => d.Expire());
+ case TaikoSkinComponents.TaikoExplosionKiai:
+ // suppress the default kiai explosion if the skin brings its own sprites.
+ // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
+ if (hasExplosion.Value)
+ return Drawable.Empty().With(d => d.Expire());
- return null;
+ return null;
- case TaikoSkinComponents.Scroller:
- if (GetTexture("taiko-slider") != null)
- return new LegacyTaikoScroller();
+ case TaikoSkinComponents.Scroller:
+ if (GetTexture("taiko-slider") != null)
+ return new LegacyTaikoScroller();
- return null;
+ return null;
- case TaikoSkinComponents.Mascot:
- return new DrawableTaikoMascot();
+ case TaikoSkinComponents.Mascot:
+ return new DrawableTaikoMascot();
+ }
}
return Source.GetDrawableComponent(component);
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index a18f82fe4a..059432eeaf 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -169,6 +169,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
+ protected override ISkin GetSkin() => throw new NotImplementedException();
+
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index a8ee1bcc2e..a47631a83b 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tests.Collections.IO
{
var osu = LoadOsuIntoHost(host);
- await osu.CollectionManager.Import(new MemoryStream());
+ await importCollectionsFromStream(osu, new MemoryStream());
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
}
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Collections.IO
{
var osu = LoadOsuIntoHost(host);
- await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
+ await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Collections.IO
{
var osu = LoadOsuIntoHost(host, true);
- await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
+ await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Collections.IO
ms.Seek(0, SeekOrigin.Begin);
- await osu.CollectionManager.Import(ms);
+ await importCollectionsFromStream(osu, ms);
}
Assert.That(host.UpdateThread.Running, Is.True);
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Collections.IO
{
var osu = LoadOsuIntoHost(host, true);
- await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
+ await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
// Move first beatmap from second collection into the first.
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
@@ -169,5 +169,12 @@ namespace osu.Game.Tests.Collections.IO
}
}
}
+
+ private static async Task importCollectionsFromStream(TestOsuGameBase osu, Stream stream)
+ {
+ // intentionally spin this up on a separate task to avoid disposal deadlocks.
+ // see https://github.com/EventStore/EventStore/issues/1179
+ await Task.Run(() => osu.CollectionManager.Import(stream).Wait());
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
new file mode 100644
index 0000000000..cc53e50884
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -0,0 +1,130 @@
+// 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.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Lists;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Extensions;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Skinning.Legacy;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
+ {
+ private ISkin currentBeatmapSkin;
+
+ [Resolved]
+ private SkinManager skinManager { get; set; }
+
+ [Cached]
+ private ScoreProcessor scoreProcessor = new ScoreProcessor();
+
+ [Cached(typeof(HealthProcessor))]
+ private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
+
+ protected override bool HasCustomSteps => true;
+
+ [Test]
+ public void TestEmptyLegacyBeatmapSkinFallsBack()
+ {
+ CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
+ AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
+ }
+
+ protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin)
+ {
+ CreateTest(() =>
+ {
+ AddStep("setup skins", () =>
+ {
+ skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
+ currentBeatmapSkin = getBeatmapSkin();
+ });
+ });
+ }
+
+ protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource)
+ {
+ var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target)
+ .ChildrenOfType().SingleOrDefault();
+
+ if (actualComponentsContainer == null)
+ return false;
+
+ var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
+
+ var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new SkinnableTargetComponent(target));
+ if (expectedComponentsContainer == null)
+ return false;
+
+ var expectedComponentsAdjustmentContainer = new Container
+ {
+ Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content),
+ Size = actualComponentsContainer.DrawSize,
+ Child = expectedComponentsContainer,
+ };
+
+ Add(expectedComponentsAdjustmentContainer);
+ expectedComponentsAdjustmentContainer.UpdateSubTree();
+ var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo();
+ Remove(expectedComponentsAdjustmentContainer);
+
+ return almostEqual(actualInfo, expectedInfo);
+
+ static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
+ other != null
+ && info.Type == other.Type
+ && info.Anchor == other.Anchor
+ && info.Origin == other.Origin
+ && Precision.AlmostEquals(info.Position, other.Position)
+ && Precision.AlmostEquals(info.Scale, other.Scale)
+ && Precision.AlmostEquals(info.Rotation, other.Rotation)
+ && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
+ }
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
+
+ protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
+
+ private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly ISkin beatmapSkin;
+
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
+ : base(beatmap, storyboard, referenceClock, audio)
+ {
+ this.beatmapSkin = beatmapSkin;
+ }
+
+ protected override ISkin GetSkin() => beatmapSkin;
+ }
+
+ private class TestOsuRuleset : OsuRuleset
+ {
+ public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source);
+
+ private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer
+ {
+ public TestOsuLegacySkinTransformer(ISkinSource source)
+ : base(source)
+ {
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index cfdea31a75..8160a62991 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -88,13 +88,18 @@ namespace osu.Game.Tests.Visual.Gameplay
{
beforeLoadAction?.Invoke();
+ prepareBeatmap();
+
+ LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
+ }
+
+ private void prepareBeatmap()
+ {
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToTrack(Beatmap.Value.Track);
-
- LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
}
[Test]
@@ -178,10 +183,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("load slow dummy beatmap", () =>
{
- LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
- Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
+ prepareBeatmap();
+ slowPlayer = new SlowLoadPlayer(false, false);
+ LoadScreen(loader = new TestPlayerLoader(() => slowPlayer));
});
+ AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000));
+
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index 0ac8e01482..5ef3eff856 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(null);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
- AddAssert("score shown", () => Player.IsScoreShown);
+ AddUntilStep("wait for score shown", () => Player.IsScoreShown);
+ AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index 80b9aa8228..af2f6fa5fe 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -73,8 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
for (int i = 0; i < users; i++)
spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
- Client.CurrentMatchPlayingUserIds.Clear();
- Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
+ spectatorClient.Schedule(() =>
+ {
+ Client.CurrentMatchPlayingUserIds.Clear();
+ Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
+ });
Children = new Drawable[]
{
@@ -91,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
+ AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index 55c5b5b9c2..acf9deb3cb 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("click first row", () =>
{
firstRow = panel.ChildrenOfType().First();
+
InputManager.MoveMouseTo(firstRow);
InputManager.Click(MouseButton.Left);
});
@@ -138,6 +139,64 @@ namespace osu.Game.Tests.Visual.Settings
}
}
+ [Test]
+ public void TestSingleBindingResetButton()
+ {
+ KeyBindingRow settingsKeyBindingRow = null;
+
+ AddStep("click first row", () =>
+ {
+ settingsKeyBindingRow = panel.ChildrenOfType().First();
+
+ InputManager.MoveMouseTo(settingsKeyBindingRow);
+ InputManager.Click(MouseButton.Left);
+ InputManager.PressKey(Key.P);
+ InputManager.ReleaseKey(Key.P);
+ });
+
+ AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha > 0);
+
+ AddStep("click reset button for bindings", () =>
+ {
+ var resetButton = settingsKeyBindingRow.ChildrenOfType>().First();
+
+ resetButton.Click();
+ });
+
+ AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0);
+
+ AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
+ }
+
+ [Test]
+ public void TestResetAllBindingsButton()
+ {
+ KeyBindingRow settingsKeyBindingRow = null;
+
+ AddStep("click first row", () =>
+ {
+ settingsKeyBindingRow = panel.ChildrenOfType().First();
+
+ InputManager.MoveMouseTo(settingsKeyBindingRow);
+ InputManager.Click(MouseButton.Left);
+ InputManager.PressKey(Key.P);
+ InputManager.ReleaseKey(Key.P);
+ });
+
+ AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha > 0);
+
+ AddStep("click reset button for bindings", () =>
+ {
+ var resetButton = panel.ChildrenOfType().First();
+
+ resetButton.Click();
+ });
+
+ AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0);
+
+ AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
+ }
+
[Test]
public void TestClickRowSelectsFirstBinding()
{
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
index 8f1c17ed29..f63145f534 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Overlays.Settings;
+using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Settings
{
@@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Settings
private class TestSettingsTextBox : SettingsTextBox
{
- public new Drawable RestoreDefaultValueButton => this.ChildrenOfType().Single();
+ public Drawable RestoreDefaultValueButton => this.ChildrenOfType>().Single();
}
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 44c9361ff8..78ddfa9ed2 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -786,9 +786,12 @@ namespace osu.Game.Tests.Visual.SongSelect
}
}
- private void checkVisibleItemCount(bool diff, int count) =>
- AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
+ private void checkVisibleItemCount(bool diff, int count)
+ {
+ // until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet.
+ AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () =>
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
+ }
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index cbed28641c..5477e4a0f8 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.IO.Archives;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
@@ -52,6 +53,8 @@ namespace osu.Game.Tests
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
+ protected override ISkin GetSkin() => null;
+
public override Stream GetStream(string storagePath) => null;
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 5e975de77c..46e3a4f6d7 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -526,6 +526,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetBeatmapTrack() => null;
+ protected override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
}
}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 6922d1c286..ea7f45e53f 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
@@ -49,6 +50,8 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack() => GetVirtualTrack();
+ protected override ISkin GetSkin() => null;
+
public override Stream GetStream(string storagePath) => null;
private class DummyRulesetInfo : RulesetInfo
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 3576b149bf..0a55678fb7 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
public bool SkinLoaded => skin.IsResultAvailable;
public ISkin Skin => skin.Value;
- protected virtual ISkin GetSkin() => new DefaultSkin(null);
+ protected abstract ISkin GetSkin();
private readonly RecyclableLazy skin;
public abstract Stream GetStream(string storagePath);
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index 3a63587b30..086cc573d5 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -58,8 +58,13 @@ namespace osu.Game.Collections
if (storage.Exists(database_name))
{
+ List beatmapCollections;
+
using (var stream = storage.GetStream(database_name))
- importCollections(readCollections(stream));
+ beatmapCollections = readCollections(stream);
+
+ // intentionally fire-and-forget async.
+ importCollections(beatmapCollections);
}
}
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 9c09b6e7d0..0df3359c28 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -46,12 +47,19 @@ namespace osu.Game.Overlays.KeyBinding
}
}
+ private Container content;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
+ content.ReceivePositionalInputAt(screenSpacePos);
+
public bool FilteringActive { get; set; }
private OsuSpriteText text;
private FillFlowContainer cancelAndClearButtons;
private FillFlowContainer buttons;
+ private Bindable isDefault { get; } = new BindableBool(true);
+
public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
public KeyBindingRow(object action, IEnumerable bindings)
@@ -61,9 +69,6 @@ namespace osu.Game.Overlays.KeyBinding
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
-
- Masking = true;
- CornerRadius = padding;
}
[Resolved]
@@ -72,51 +77,72 @@ namespace osu.Game.Overlays.KeyBinding
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- EdgeEffect = new EdgeEffectParameters
- {
- Radius = 2,
- Colour = colours.YellowDark.Opacity(0),
- Type = EdgeEffectType.Shadow,
- Hollow = true,
- };
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS };
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
- new Box
+ new RestoreDefaultValueButton
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- Alpha = 0.6f,
- },
- text = new OsuSpriteText
- {
- Text = action.GetDescription(),
- Margin = new MarginPadding(padding),
- },
- buttons = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight
- },
- cancelAndClearButtons = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Padding = new MarginPadding(padding) { Top = height + padding * 2 },
- Anchor = Anchor.TopRight,
+ Current = isDefault,
+ Action = RestoreDefaults,
Origin = Anchor.TopRight,
- Alpha = 0,
- Spacing = new Vector2(5),
+ },
+ content = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Masking = true,
+ CornerRadius = padding,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Radius = 2,
+ Colour = colours.YellowDark.Opacity(0),
+ Type = EdgeEffectType.Shadow,
+ Hollow = true,
+ },
Children = new Drawable[]
{
- new CancelButton { Action = finalise },
- new ClearButton { Action = clear },
- },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.6f,
+ },
+ text = new OsuSpriteText
+ {
+ Text = action.GetDescription(),
+ Margin = new MarginPadding(padding),
+ },
+ buttons = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight
+ },
+ cancelAndClearButtons = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding(padding) { Top = height + padding * 2 },
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Alpha = 0,
+ Spacing = new Vector2(5),
+ Children = new Drawable[]
+ {
+ new CancelButton { Action = finalise },
+ new ClearButton { Action = clear },
+ },
+ }
+ }
}
};
foreach (var b in bindings)
buttons.Add(new KeyButton(b));
+
+ updateIsDefaultValue();
}
public void RestoreDefaults()
@@ -129,18 +155,20 @@ namespace osu.Game.Overlays.KeyBinding
button.UpdateKeyCombination(d);
store.Update(button.KeyBinding);
}
+
+ isDefault.Value = true;
}
protected override bool OnHover(HoverEvent e)
{
- FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
+ content.FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
- FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
+ content.FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
base.OnHoverLost(e);
}
@@ -288,6 +316,8 @@ namespace osu.Game.Overlays.KeyBinding
{
store.Update(bindTarget.KeyBinding);
+ updateIsDefaultValue();
+
bindTarget.IsBinding = false;
Schedule(() =>
{
@@ -305,8 +335,8 @@ namespace osu.Game.Overlays.KeyBinding
protected override void OnFocus(FocusEvent e)
{
- AutoSizeDuration = 500;
- AutoSizeEasing = Easing.OutQuint;
+ content.AutoSizeDuration = 500;
+ content.AutoSizeEasing = Easing.OutQuint;
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
@@ -331,6 +361,11 @@ namespace osu.Game.Overlays.KeyBinding
if (bindTarget != null) bindTarget.IsBinding = true;
}
+ private void updateIsDefaultValue()
+ {
+ isDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults);
+ }
+
private class CancelButton : TriangleButton
{
public CancelButton()
@@ -379,9 +414,6 @@ namespace osu.Game.Overlays.KeyBinding
Margin = new MarginPadding(padding);
- // todo: use this in a meaningful way
- // var isDefault = keyBinding.Action is Enum;
-
Masking = true;
CornerRadius = padding;
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
index 707176e63e..5e1f9d8f75 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
@@ -61,8 +61,11 @@ namespace osu.Game.Overlays.KeyBinding
{
Text = "Reset all bindings in section";
RelativeSizeAxes = Axes.X;
- Margin = new MarginPadding { Top = 5 };
- Height = 20;
+ Width = 0.5f;
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+ Margin = new MarginPadding { Top = 15 };
+ Height = 30;
Content.CornerRadius = 5;
}
diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs
new file mode 100644
index 0000000000..213ad2ba68
--- /dev/null
+++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs
@@ -0,0 +1,106 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osuTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays
+{
+ public class RestoreDefaultValueButton : OsuButton, IHasTooltip, IHasCurrentValue
+ {
+ public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
+
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button.
+ public override bool AcceptsFocus => true;
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
+
+ private Color4 buttonColour;
+
+ private bool hovering;
+
+ public RestoreDefaultValueButton()
+ {
+ Height = 1;
+
+ RelativeSizeAxes = Axes.Y;
+ Width = SettingsPanel.CONTENT_MARGINS;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colour)
+ {
+ BackgroundColour = colour.Yellow;
+ buttonColour = colour.Yellow;
+ Content.Width = 0.33f;
+ Content.CornerRadius = 3;
+ Content.EdgeEffect = new EdgeEffectParameters
+ {
+ Colour = buttonColour.Opacity(0.1f),
+ Type = EdgeEffectType.Glow,
+ Radius = 2,
+ };
+
+ Padding = new MarginPadding { Vertical = 1.5f };
+ Alpha = 0f;
+
+ Action += () =>
+ {
+ if (!current.Disabled) current.SetDefault();
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Current.ValueChanged += _ => UpdateState();
+ Current.DisabledChanged += _ => UpdateState();
+ Current.DefaultChanged += _ => UpdateState();
+
+ UpdateState();
+ }
+
+ public string TooltipText => "revert to default";
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ hovering = true;
+ UpdateState();
+ return false;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ hovering = false;
+ UpdateState();
+ }
+
+ public void UpdateState() => Scheduler.AddOnce(updateState);
+
+ private void updateState()
+ {
+ if (current == null)
+ return;
+
+ this.FadeTo(current.IsDefault ? 0f :
+ hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
+ this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index 86a836d29b..807916e7f6 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -5,16 +5,11 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -108,7 +103,7 @@ namespace osu.Game.Overlays.Settings
protected SettingsItem()
{
- RestoreDefaultValueButton restoreDefaultButton;
+ RestoreDefaultValueButton restoreDefaultButton;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@@ -116,7 +111,7 @@ namespace osu.Game.Overlays.Settings
InternalChildren = new Drawable[]
{
- restoreDefaultButton = new RestoreDefaultValueButton(),
+ restoreDefaultButton = new RestoreDefaultValueButton(),
FlowContent = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
@@ -137,7 +132,7 @@ namespace osu.Game.Overlays.Settings
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
if (ShowsDefaultIndicator)
- restoreDefaultButton.Bindable = controlWithCurrent.Current;
+ restoreDefaultButton.Current = controlWithCurrent.Current;
}
}
@@ -146,101 +141,5 @@ namespace osu.Game.Overlays.Settings
if (labelText != null)
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
}
-
- protected internal class RestoreDefaultValueButton : Container, IHasTooltip
- {
- public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
-
- private Bindable bindable;
-
- public Bindable Bindable
- {
- get => bindable;
- set
- {
- bindable = value;
- bindable.ValueChanged += _ => UpdateState();
- bindable.DisabledChanged += _ => UpdateState();
- bindable.DefaultChanged += _ => UpdateState();
- UpdateState();
- }
- }
-
- private Color4 buttonColour;
-
- private bool hovering;
-
- public RestoreDefaultValueButton()
- {
- RelativeSizeAxes = Axes.Y;
- Width = SettingsPanel.CONTENT_MARGINS;
- Padding = new MarginPadding { Vertical = 1.5f };
- Alpha = 0f;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colour)
- {
- buttonColour = colour.Yellow;
-
- Child = new Container
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- CornerRadius = 3,
- Masking = true,
- Colour = buttonColour,
- EdgeEffect = new EdgeEffectParameters
- {
- Colour = buttonColour.Opacity(0.1f),
- Type = EdgeEffectType.Glow,
- Radius = 2,
- },
- Width = 0.33f,
- Child = new Box { RelativeSizeAxes = Axes.Both },
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- UpdateState();
- }
-
- public string TooltipText => "revert to default";
-
- protected override bool OnClick(ClickEvent e)
- {
- if (bindable != null && !bindable.Disabled)
- bindable.SetDefault();
- return true;
- }
-
- protected override bool OnHover(HoverEvent e)
- {
- hovering = true;
- UpdateState();
- return false;
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- hovering = false;
- UpdateState();
- }
-
- public void UpdateState() => Scheduler.AddOnce(updateState);
-
- private void updateState()
- {
- if (bindable == null)
- return;
-
- this.FadeTo(bindable.IsDefault ? 0f :
- hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
- this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index f0a11d67b7..eae828c142 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
@@ -49,8 +50,6 @@ namespace osu.Game.Overlays
private readonly bool showSidebar;
- protected Box Background;
-
protected SettingsPanel(bool showSidebar)
{
this.showSidebar = showSidebar;
@@ -63,13 +62,13 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = ContentContainer = new Container
+ InternalChild = ContentContainer = new NonMaskedContent
{
Width = WIDTH,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
- Background = new Box
+ new Box
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
@@ -165,7 +164,7 @@ namespace osu.Game.Overlays
{
base.PopOut();
- ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
+ ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint);
Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
@@ -191,6 +190,12 @@ namespace osu.Game.Overlays
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
+ private class NonMaskedContent : Container
+ {
+ // masking breaks the pan-out transform with nested sub-settings panels.
+ protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
+ }
+
public class SettingsSectionsContainer : SectionsContainer
{
public SearchContainer SearchContainer;
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index cc663c37af..cca55819c5 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -311,6 +311,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Invoked for this to take on any values from a newly-applied .
+ /// This is also fired after any changes which occurred via an call.
///
protected virtual void OnApply()
{
@@ -318,6 +319,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Invoked for this to revert any values previously taken on from the currently-applied .
+ /// This is also fired after any changes which occurred via an call.
///
protected virtual void OnFree()
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs
index 5a6f98f504..22b211f257 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs
@@ -77,7 +77,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
double offset = result.Time.Value - blueprints.First().Item.StartTime;
if (offset != 0)
- Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
+ {
+ Beatmap.PerformOnSelection(obj =>
+ {
+ obj.StartTime += offset;
+ Beatmap.Update(obj);
+ });
+ }
}
return true;
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
index 2141c490df..246d4aa8d7 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
@@ -125,6 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return;
h.Samples.Add(new HitSampleInfo(sampleName));
+ EditorBeatmap.Update(h);
});
}
@@ -134,7 +135,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// The name of the hit sample.
public void RemoveHitSample(string sampleName)
{
- EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName));
+ EditorBeatmap.PerformOnSelection(h =>
+ {
+ h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
+ EditorBeatmap.Update(h);
+ });
}
///
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
index 7c1bbd65f9..6f04f36b83 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
@@ -276,7 +276,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime);
double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount;
- EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment);
+ EditorBeatmap.PerformOnSelection(h =>
+ {
+ h.StartTime += adjustment;
+ EditorBeatmap.Update(h);
+ });
}
}
diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
index 66784fda54..6f92db98ee 100644
--- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
+++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
@@ -11,6 +11,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.IO;
+using osu.Game.Skinning;
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
namespace osu.Game.Screens.Edit
@@ -117,6 +118,8 @@ namespace osu.Game.Screens.Edit
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
+ protected override ISkin GetSkin() => throw new NotImplementedException();
+
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 39f9e2d388..a9f3edf049 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -522,7 +522,10 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen())
{
ValidForResume = false;
- this.MakeCurrent();
+
+ // in the potential case that this instance has already been exited, this is required to avoid a crash.
+ if (this.GetChildScreen() != null)
+ this.MakeCurrent();
return;
}
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index a3fca3d4e1..9773bd5ce9 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved(CanBeNull = true)]
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
- public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty();
+ public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren;
[CanBeNull]
private Container beatmapContainer;
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index 3ec205e897..caf37e5bc9 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -3,6 +3,7 @@
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -23,6 +24,25 @@ namespace osu.Game.Skinning
Configuration.AllowDefaultComboColoursFallback = false;
}
+ public override Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ if (component is SkinnableTargetComponent targetComponent)
+ {
+ switch (targetComponent.Target)
+ {
+ case SkinnableTarget.MainHUDComponents:
+ // this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet.
+ // therefore keep the check here until fallback default legacy skin is supported.
+ if (!this.HasFont(LegacyFont.Score))
+ return null;
+
+ break;
+ }
+ }
+
+ return base.GetDrawableComponent(component);
+ }
+
public override IBindable GetConfig(TLookup lookup)
{
switch (lookup)
@@ -51,6 +71,6 @@ namespace osu.Game.Skinning
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
- new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
+ new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString };
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
index 4c42823779..ca041da801 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
@@ -57,7 +57,13 @@ namespace osu.Game.Storyboards.Drawables
public DrawableStoryboard(Storyboard storyboard)
{
Storyboard = storyboard;
+
Size = new Vector2(640, 480);
+
+ bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).Any(e => !(e is StoryboardVideo));
+
+ Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f);
+
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
index 2ada83c3b4..1085b52d65 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
@@ -5,6 +5,7 @@ using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osuTK;
namespace osu.Game.Storyboards.Drawables
{
@@ -15,6 +16,8 @@ namespace osu.Game.Storyboards.Drawables
public override bool IsPresent => Enabled && base.IsPresent;
+ protected LayerElementContainer ElementContainer { get; }
+
public DrawableStoryboardLayer(StoryboardLayer layer)
{
Layer = layer;
@@ -24,10 +27,10 @@ namespace osu.Game.Storyboards.Drawables
Enabled = layer.VisibleWhenPassing;
Masking = layer.Masking;
- InternalChild = new LayerElementContainer(layer);
+ InternalChild = ElementContainer = new LayerElementContainer(layer);
}
- private class LayerElementContainer : LifetimeManagementContainer
+ protected class LayerElementContainer : LifetimeManagementContainer
{
private readonly StoryboardLayer storyboardLayer;
@@ -35,8 +38,8 @@ namespace osu.Game.Storyboards.Drawables
{
storyboardLayer = layer;
- Width = 640;
- Height = 480;
+ Size = new Vector2(640, 480);
+
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs
index bc61f704dd..3486c1d66a 100644
--- a/osu.Game/Storyboards/Storyboard.cs
+++ b/osu.Game/Storyboards/Storyboard.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Storyboards
public Storyboard()
{
- layers.Add("Video", new StoryboardLayer("Video", 4, false));
+ layers.Add("Video", new StoryboardVideoLayer("Video", 4, false));
layers.Add("Background", new StoryboardLayer("Background", 3));
layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, });
layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, });
@@ -85,12 +85,8 @@ namespace osu.Game.Storyboards
}
}
- public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null)
- {
- var drawable = new DrawableStoryboard(this);
- drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f);
- return drawable;
- }
+ public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) =>
+ new DrawableStoryboard(this);
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
{
diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs
index 1cde7cf67a..fa9d4ebfea 100644
--- a/osu.Game/Storyboards/StoryboardLayer.cs
+++ b/osu.Game/Storyboards/StoryboardLayer.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Storyboards
Elements.Add(element);
}
- public DrawableStoryboardLayer CreateDrawable()
+ public virtual DrawableStoryboardLayer CreateDrawable()
=> new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name };
}
}
diff --git a/osu.Game/Storyboards/StoryboardVideoLayer.cs b/osu.Game/Storyboards/StoryboardVideoLayer.cs
new file mode 100644
index 0000000000..2a01c2274a
--- /dev/null
+++ b/osu.Game/Storyboards/StoryboardVideoLayer.cs
@@ -0,0 +1,32 @@
+// 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.Graphics;
+using osu.Game.Storyboards.Drawables;
+using osuTK;
+
+namespace osu.Game.Storyboards
+{
+ public class StoryboardVideoLayer : StoryboardLayer
+ {
+ public StoryboardVideoLayer(string name, int depth, bool masking)
+ : base(name, depth, masking)
+ {
+ }
+
+ public override DrawableStoryboardLayer CreateDrawable()
+ => new DrawableStoryboardVideoLayer(this) { Depth = Depth, Name = Name };
+
+ public class DrawableStoryboardVideoLayer : DrawableStoryboardLayer
+ {
+ public DrawableStoryboardVideoLayer(StoryboardVideoLayer layer)
+ : base(layer)
+ {
+ // for videos we want to take on the full size of the storyboard container hierarchy
+ // to allow the video to fill the full available region.
+ ElementContainer.RelativeSizeAxes = Axes.Both;
+ ElementContainer.Size = Vector2.One;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index a97f6defe9..4935f7fc13 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -17,6 +17,7 @@ using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
+using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps
{
@@ -216,6 +217,8 @@ namespace osu.Game.Tests.Beatmaps
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
+ protected override ISkin GetSkin() => throw new NotImplementedException();
+
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index 852006bc9b..bfce59c7de 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -6,6 +6,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
+using osu.Game.Skinning;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Beatmaps
@@ -36,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps
protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard();
+ protected override ISkin GetSkin() => null;
+
public override Stream GetStream(string storagePath) => null;
protected override Texture GetBackground() => null;
diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
index 3a5ffa8770..c7aa43b377 100644
--- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
+++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
@@ -3,6 +3,7 @@
#nullable enable
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -53,6 +54,8 @@ namespace osu.Game.Tests.Visual.Spectator
});
}
+ public new void Schedule(Action action) => base.Schedule(action);
+
///
/// Sends frames for an arbitrary user.
///
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e3331cd365..d252d6f53f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -34,7 +34,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index e35b1b5c42..59b026e0ad 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+