1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 18:07:24 +08:00

Merge branch 'master' into fix-exporting-a-skin-with-too-long-file-name

This commit is contained in:
Cootz 2023-02-16 13:22:11 +03:00 committed by GitHub
commit 3057ccb635
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 64 additions and 71 deletions

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Graphics; using osuTK.Graphics;
@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (targetComponent.Lookup) switch (targetComponent.Lookup)
{ {
case GlobalSkinComponentLookup.LookupType.MainHUDComponents: case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer; var components = base.GetDrawableComponent(lookup) as Container;
if (providesComboCounter && components != null) if (providesComboCounter && components != null)
{ {

View File

@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
private double? releaseTime; private double? releaseTime;
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
public DrawableHoldNote() public DrawableHoldNote()
: this(null) : this(null)
{ {

View File

@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
public partial class DrawableHoldNoteTail : DrawableNote public partial class DrawableHoldNoteTail : DrawableNote
{ {
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail; protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject; protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true); public void UpdateResult() => base.UpdateResult(true);
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
Debug.Assert(HitObject.HitWindows != null); Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience // Factor in the release lenience
timeOffset /= release_window_lenience; timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE;
if (!userTriggered) if (!userTriggered)
{ {

View File

@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary> /// </summary>
public TailNote Tail { get; private set; } public TailNote Tail { get; private set; }
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
/// <summary> /// <summary>
/// The time between ticks of this hold. /// The time between ticks of this hold.
/// </summary> /// </summary>

View File

@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects
{ {
public class TailNote : Note public class TailNote : Note
{ {
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
public const double RELEASE_WINDOW_LENIENCE = 1.5;
public override Judgement CreateJudgement() => new ManiaJudgement(); public override Judgement CreateJudgement() => new ManiaJudgement();
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE;
} }
} }

View File

@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre; Origin = Anchor.Centre;
} }
public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
/// <summary> /// <summary>
/// Apply a judgement result. /// Apply a judgement result.
/// </summary> /// </summary>

View File

@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
AddNested(i < SpinsRequired AddNested(i < SpinsRequired
? new SpinnerTick { StartTime = startTime } ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
: new SpinnerBonusTick { StartTime = startTime }); : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration });
} }
} }

View File

@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public class SpinnerTick : OsuHitObject public class SpinnerTick : OsuHitObject
{ {
/// <summary>
/// Duration of the <see cref="Spinner"/> containing this spinner tick.
/// </summary>
public double SpinnerDuration { get; set; }
public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override double MaximumJudgementOffset => SpinnerDuration;
public class OsuSpinnerTickJudgement : OsuJudgement public class OsuSpinnerTickJudgement : OsuJudgement
{ {
public override HitResult MaxResult => HitResult.SmallBonus; public override HitResult MaxResult => HitResult.SmallBonus;

View File

@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece()); protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece());
public override double MaximumJudgementOffset => HitObject.HitWindow;
protected override void OnApply() protected override void OnApply()
{ {
base.OnApply(); base.OnApply();

View File

@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override double MaximumJudgementOffset => HitWindow;
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime }; protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
public class StrongNestedHit : StrongNestedHitObject public class StrongNestedHit : StrongNestedHitObject

View File

@ -1,13 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -28,10 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
{ {
private ISkin currentBeatmapSkin; private ISkin currentBeatmapSkin = null!;
[Resolved] [Resolved]
private SkinManager skinManager { get; set; } private SkinManager skinManager { get; set; } = null!;
protected override bool HasCustomSteps => true; protected override bool HasCustomSteps => true;
@ -57,15 +56,15 @@ namespace osu.Game.Tests.Visual.Gameplay
protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource) protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource)
{ {
var actualComponentsContainer = Player.ChildrenOfType<SkinnableTargetContainer>().First(s => s.Target == target) var targetContainer = Player.ChildrenOfType<SkinnableTargetContainer>().First(s => s.Target == target);
.ChildrenOfType<SkinnableTargetComponentsContainer>().SingleOrDefault(); var actualComponentsContainer = targetContainer.ChildrenOfType<Container>().SingleOrDefault(c => c.Parent == targetContainer);
if (actualComponentsContainer == null) if (actualComponentsContainer == null)
return false; return false;
var actualInfo = actualComponentsContainer.CreateSkinnableInfo(); var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)); var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)) as Container;
if (expectedComponentsContainer == null) if (expectedComponentsContainer == null)
return false; return false;
@ -92,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
return almostEqual(actualInfo, expectedInfo); return almostEqual(actualInfo, expectedInfo);
} }
private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) => private static bool almostEqual(SkinnableInfo info, SkinnableInfo? other) =>
other != null other != null
&& info.Type == other.Type && info.Type == other.Type
&& info.Anchor == other.Anchor && info.Anchor == other.Anchor
@ -102,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay
&& Precision.AlmostEquals(info.Rotation, other.Rotation) && Precision.AlmostEquals(info.Rotation, other.Rotation)
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual)); && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual));
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin); => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset(); protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
@ -111,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
private readonly ISkin beatmapSkin; private readonly ISkin beatmapSkin;
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin) public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
: base(beatmap, storyboard, referenceClock, audio) : base(beatmap, storyboard, referenceClock, audio)
{ {
this.beatmapSkin = beatmapSkin; this.beatmapSkin = beatmapSkin;

View File

@ -650,18 +650,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
UpdateResult(false); UpdateResult(false);
} }
/// <summary>
/// The maximum offset from the end time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> can be judged.
/// The time offset of <see cref="Result"/> will be clamped to this value during <see cref="ApplyResult"/>.
/// <para>
/// Defaults to the miss window of <see cref="HitObject"/>.
/// </para>
/// </summary>
/// <remarks>
/// This does not affect the time offset provided to invocations of <see cref="CheckForResult"/>.
/// </remarks>
public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0;
/// <summary> /// <summary>
/// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as /// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as
/// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>. /// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>.
@ -683,7 +671,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
$"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}]).");
} }
Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime());
if (Result.HasResult) if (Result.HasResult)
updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss);

View File

@ -200,6 +200,14 @@ namespace osu.Game.Rulesets.Objects
[NotNull] [NotNull]
protected virtual HitWindows CreateHitWindows() => new HitWindows(); protected virtual HitWindows CreateHitWindows() => new HitWindows();
/// <summary>
/// The maximum offset from the end time of <see cref="HitObject"/> at which this <see cref="HitObject"/> can be judged.
/// <para>
/// Defaults to the miss window.
/// </para>
/// </summary>
public virtual double MaximumJudgementOffset => HitWindows?.WindowFor(HitResult.Miss) ?? 0;
public IList<HitSampleInfo> CreateSlidingSamples() public IList<HitSampleInfo> CreateSlidingSamples()
{ {
var slidingSamples = new List<HitSampleInfo>(); var slidingSamples = new List<HitSampleInfo>();

View File

@ -232,8 +232,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
double computedStartTime = computeDisplayStartTime(entry); double computedStartTime = computeDisplayStartTime(entry);
// always load the hitobject before its first judgement offset // always load the hitobject before its first judgement offset
double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0; entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime);
entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
} }
private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null) private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null)

View File

@ -94,7 +94,7 @@ namespace osu.Game.Skinning
switch (globalLookup.Lookup) switch (globalLookup.Lookup)
{ {
case GlobalSkinComponentLookup.LookupType.SongSelect: case GlobalSkinComponentLookup.LookupType.SongSelect:
var songSelectComponents = new SkinnableTargetComponentsContainer(_ => var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
{ {
// do stuff when we need to. // do stuff when we need to.
}); });
@ -102,7 +102,7 @@ namespace osu.Game.Skinning
return songSelectComponents; return songSelectComponents;
case GlobalSkinComponentLookup.LookupType.MainHUDComponents: case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{ {
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault(); var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault(); var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();

View File

@ -2,39 +2,28 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using Newtonsoft.Json;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
/// <summary> /// <summary>
/// A container which groups the components of a <see cref="SkinnableTargetContainer"/> into a single object. /// A container which can be used to specify default skin components layouts.
/// Optionally also applies a default layout to the components. /// Handles applying a default layout to the components.
/// </summary> /// </summary>
[Serializable] public partial class DefaultSkinComponentsContainer : Container
public partial class SkinnableTargetComponentsContainer : Container, ISkinnableDrawable
{ {
public bool IsEditable => false;
public bool UsesFixedAnchor { get; set; }
private readonly Action<Container>? applyDefaults; private readonly Action<Container>? applyDefaults;
/// <summary> /// <summary>
/// Construct a wrapper with defaults that should be applied once. /// Construct a wrapper with defaults that should be applied once.
/// </summary> /// </summary>
/// <param name="applyDefaults">A function to apply the default layout.</param> /// <param name="applyDefaults">A function to apply the default layout.</param>
public SkinnableTargetComponentsContainer(Action<Container> applyDefaults) public DefaultSkinComponentsContainer(Action<Container> applyDefaults)
: this()
{
this.applyDefaults = applyDefaults;
}
[JsonConstructor]
public SkinnableTargetComponentsContainer()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
this.applyDefaults = applyDefaults;
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -347,7 +347,7 @@ namespace osu.Game.Skinning
switch (target.Lookup) switch (target.Lookup)
{ {
case GlobalSkinComponentLookup.LookupType.MainHUDComponents: case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{ {
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault(); var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
var accuracy = container.OfType<GameplayAccuracyCounter>().FirstOrDefault(); var accuracy = container.OfType<GameplayAccuracyCounter>().FirstOrDefault();

View File

@ -11,6 +11,7 @@ using Newtonsoft.Json;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
@ -174,8 +175,9 @@ namespace osu.Game.Skinning
foreach (var i in skinnableInfo) foreach (var i in skinnableInfo)
components.Add(i.CreateInstance()); components.Add(i.CreateInstance());
return new SkinnableTargetComponentsContainer return new Container
{ {
RelativeSizeAxes = Axes.Both,
Children = components, Children = components,
}; };
} }

View File

@ -7,13 +7,14 @@ using System.Linq;
using System.Threading; using System.Threading;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget
{ {
private SkinnableTargetComponentsContainer? content; private Container? content;
public GlobalSkinComponentLookup.LookupType Target { get; } public GlobalSkinComponentLookup.LookupType Target { get; }
@ -39,15 +40,16 @@ namespace osu.Game.Skinning
foreach (var i in skinnableInfo) foreach (var i in skinnableInfo)
drawables.Add(i.CreateInstance()); drawables.Add(i.CreateInstance());
Reload(new SkinnableTargetComponentsContainer Reload(new Container
{ {
RelativeSizeAxes = Axes.Both,
Children = drawables, Children = drawables,
}); });
} }
public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as SkinnableTargetComponentsContainer); public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as Container);
public void Reload(SkinnableTargetComponentsContainer? componentsContainer) public void Reload(Container? componentsContainer)
{ {
ClearInternal(); ClearInternal();
components.Clear(); components.Clear();

View File

@ -72,7 +72,7 @@ namespace osu.Game.Skinning
switch (target.Lookup) switch (target.Lookup)
{ {
case GlobalSkinComponentLookup.LookupType.SongSelect: case GlobalSkinComponentLookup.LookupType.SongSelect:
var songSelectComponents = new SkinnableTargetComponentsContainer(_ => var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
{ {
// do stuff when we need to. // do stuff when we need to.
}); });
@ -80,7 +80,7 @@ namespace osu.Game.Skinning
return songSelectComponents; return songSelectComponents;
case GlobalSkinComponentLookup.LookupType.MainHUDComponents: case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{ {
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault(); var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault(); var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();