Merge branch 'refactor-combo-colour-retrieval' into legacy-beatmap-combo-offset

This commit is contained in:
Salman Ahmed
2021-07-20 10:11:52 +03:00
1214 changed files with 36441 additions and 11055 deletions

View File

@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Pooling;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
@ -12,44 +13,42 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary>
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
/// </summary>
public class FollowPointConnection : PoolableDrawable
public class FollowPointConnection : PoolableDrawableWithLifetime<FollowPointLifetimeEntry>
{
// Todo: These shouldn't be constants
public const int SPACING = 32;
public const double PREEMPT = 800;
public FollowPointLifetimeEntry Entry;
public DrawablePool<FollowPoint> Pool;
protected override void PrepareForUse()
protected override void OnApply(FollowPointLifetimeEntry entry)
{
base.PrepareForUse();
Entry.Invalidated += onEntryInvalidated;
base.OnApply(entry);
entry.Invalidated += onEntryInvalidated;
refreshPoints();
}
protected override void FreeAfterUse()
protected override void OnFree(FollowPointLifetimeEntry entry)
{
base.FreeAfterUse();
Entry.Invalidated -= onEntryInvalidated;
base.OnFree(entry);
entry.Invalidated -= onEntryInvalidated;
// Return points to the pool.
ClearInternal(false);
Entry = null;
}
private void onEntryInvalidated() => refreshPoints();
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
private void refreshPoints()
{
ClearInternal(false);
OsuHitObject start = Entry.Start;
OsuHitObject end = Entry.End;
var entry = Entry;
if (entry?.End == null) return;
OsuHitObject start = entry.Start;
OsuHitObject end = entry.End;
double startTime = start.GetEndTime();
@ -87,14 +86,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
fp.FadeIn(end.TimeFadeIn);
fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn).Expire();
finalTransformEndTime = fadeOutTime + end.TimeFadeIn;
finalTransformEndTime = fp.LifetimeEnd;
}
}
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
Entry.LifetimeEnd = finalTransformEndTime;
entry.LifetimeEnd = finalTransformEndTime;
}
/// <summary>

View File

@ -1,6 +1,8 @@
// 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.
#nullable enable
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Performance;
@ -11,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
public class FollowPointLifetimeEntry : LifetimeEntry
{
public event Action Invalidated;
public event Action? Invalidated;
public readonly OsuHitObject Start;
public FollowPointLifetimeEntry(OsuHitObject start)
@ -22,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
bindEvents();
}
private OsuHitObject end;
private OsuHitObject? end;
public OsuHitObject End
public OsuHitObject? End
{
get => end;
set
@ -56,11 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
public void UnbindEvents()
{
if (Start != null)
{
Start.DefaultsApplied -= onDefaultsApplied;
Start.PositionBindable.ValueChanged -= onPositionChanged;
}
Start.DefaultsApplied -= onDefaultsApplied;
Start.PositionBindable.ValueChanged -= onPositionChanged;
if (End != null)
{

View File

@ -6,43 +6,32 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Pooling;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
/// <summary>
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
/// </summary>
public class FollowPointRenderer : CompositeDrawable
public class FollowPointRenderer : PooledDrawableWithLifetimeContainer<FollowPointLifetimeEntry, FollowPointConnection>
{
public override bool RemoveCompletedTransforms => false;
public IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
public new IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
private DrawablePool<FollowPointConnection> connectionPool;
private DrawablePool<FollowPoint> pointPool;
private readonly List<FollowPointLifetimeEntry> lifetimeEntries = new List<FollowPointLifetimeEntry>();
private readonly Dictionary<LifetimeEntry, FollowPointConnection> connectionsInUse = new Dictionary<LifetimeEntry, FollowPointConnection>();
private readonly Dictionary<HitObject, IBindable> startTimeMap = new Dictionary<HitObject, IBindable>();
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
public FollowPointRenderer()
{
lifetimeManager.EntryBecameAlive += onEntryBecameAlive;
lifetimeManager.EntryBecameDead += onEntryBecameDead;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
connectionPool = new DrawablePoolNoLifetime<FollowPointConnection>(1, 200),
pointPool = new DrawablePoolNoLifetime<FollowPoint>(50, 1000)
connectionPool = new DrawablePool<FollowPointConnection>(1, 200),
pointPool = new DrawablePool<FollowPoint>(50, 1000)
};
}
@ -107,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
previousEntry.End = newEntry.Start;
}
lifetimeManager.AddEntry(newEntry);
Add(newEntry);
}
private void removeEntry(OsuHitObject hitObject)
@ -118,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
entry.UnbindEvents();
lifetimeEntries.RemoveAt(index);
lifetimeManager.RemoveEntry(entry);
Remove(entry);
if (index > 0)
{
@ -131,30 +120,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
}
}
protected override bool CheckChildrenLife()
protected override FollowPointConnection GetDrawable(FollowPointLifetimeEntry entry)
{
bool anyAliveChanged = base.CheckChildrenLife();
anyAliveChanged |= lifetimeManager.Update(Time.Current);
return anyAliveChanged;
}
private void onEntryBecameAlive(LifetimeEntry entry)
{
var connection = connectionPool.Get(c =>
{
c.Entry = (FollowPointLifetimeEntry)entry;
c.Pool = pointPool;
});
connectionsInUse[entry] = connection;
AddInternal(connection);
}
private void onEntryBecameDead(LifetimeEntry entry)
{
RemoveInternal(connectionsInUse[entry]);
connectionsInUse.Remove(entry);
var connection = connectionPool.Get();
connection.Pool = pointPool;
connection.Apply(entry);
return connection;
}
private void onStartTimeChanged(OsuHitObject hitObject)
@ -171,16 +142,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
entry.UnbindEvents();
lifetimeEntries.Clear();
}
private class DrawablePoolNoLifetime<T> : DrawablePool<T>
where T : PoolableDrawable, new()
{
public override bool RemoveWhenNotAlive => false;
public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null)
: base(initialSize, maximumSize)
{
}
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
@ -19,7 +20,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece
public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece, IHasApproachCircle
{
public OsuAction? HitAction => HitArea.HitAction;
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public HitReceptor HitArea { get; private set; }
public SkinnableDrawable CirclePiece { get; private set; }
Drawable IHasApproachCircle.ApproachCircle => ApproachCircle;
private Container scaleContainer;
private InputManager inputManager;
@ -172,6 +175,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateStartTimeStateTransforms();
// always fade out at the circle's start time (to match user expectations).
ApproachCircle.FadeOut(50);
}
@ -182,7 +186,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// todo: temporary / arbitrary, used for lifetime optimisation.
this.Delay(800).FadeOut();
(CirclePiece.Drawable as IMainCirclePiece)?.Animate(state);
// in the case of an early state change, the fade should be expedited to the current point in time.
if (HitStateUpdateTime < HitObject.StartTime)
ApproachCircle.FadeOut(50);
switch (state)
{

View File

@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects;
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
[CanBeNull]
public PlaySliderBody SliderBody => Body.Drawable as PlaySliderBody;
public IBindable<int> PathVersion => pathVersion;
private readonly Bindable<int> pathVersion = new Bindable<int>();
@ -215,16 +216,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
sliderBody?.UpdateProgress(completionProgress);
SliderBody?.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects)
{
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(sliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0));
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
}
Size = sliderBody?.Size ?? Vector2.Zero;
OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
Size = SliderBody?.Size ?? Vector2.Zero;
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
if (DrawSize != Vector2.Zero)
{
@ -238,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void OnKilled()
{
base.OnKilled();
sliderBody?.RecyclePath();
SliderBody?.RecyclePath();
}
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
@ -324,7 +325,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
case ArmedState.Hit:
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
if (sliderBody?.SnakingOut.Value == true)
if (SliderBody?.SnakingOut.Value == true)
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
break;
}
@ -332,7 +333,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
private class DefaultSliderBody : PlaySliderBody
{

View File

@ -97,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateHitStateTransforms(state);
(CirclePiece.Drawable as IMainCirclePiece)?.Animate(state);
switch (state)
{
case ArmedState.Idle:
@ -154,7 +152,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
while (Math.Abs(aimRotation - Arrow.Rotation) > 180)
aimRotation += aimRotation < Arrow.Rotation ? 360 : -360;
if (!hasRotation)
// The clock may be paused in a scenario like the editor.
if (!hasRotation || !Clock.IsRunning)
{
Arrow.Rotation = aimRotation;
hasRotation = true;

View File

@ -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;
@ -86,8 +87,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Debug.Assert(HitObject.HitWindows != null);
(CirclePiece.Drawable as IMainCirclePiece)?.Animate(state);
switch (state)
{
case ArmedState.Idle:
@ -111,7 +110,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);
}
}
}

View File

@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result;
public SkinnableDrawable Body { get; private set; }
public SpinnerRotationTracker RotationTracker { get; private set; }
private SpinnerSpmCalculator spmCalculator;
@ -39,6 +41,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Bindable<bool> isSpinning;
private bool spinnerFrequencyModulate;
private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f;
/// <summary>
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
/// </summary>
@ -83,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()),
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()),
RotationTracker = new SpinnerRotationTracker(this)
}
},
@ -106,9 +111,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
isSpinning.BindValueChanged(updateSpinningSample);
}
private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f;
protected override void OnFree()
{
base.OnFree();

View File

@ -18,9 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin, allowFallback);
base.SkinChanged(skin);
updateColour();
}