mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 12:23:22 +08:00
Merge branch 'master' into fix-drawable-judgement-animation-loss
This commit is contained in:
commit
94dc61150b
@ -38,17 +38,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
new Fruit
|
||||
{
|
||||
X = 0,
|
||||
StartTime = 250
|
||||
StartTime = 1000
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.WIDTH,
|
||||
StartTime = 500
|
||||
StartTime = 2000
|
||||
},
|
||||
new JuiceStream
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 750,
|
||||
StartTime = 3000,
|
||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
hitObject.Scale = 1.5f;
|
||||
|
||||
if (hyperdash)
|
||||
hitObject.HyperDashTarget = new Banana();
|
||||
((PalpableCatchHitObject)hitObject).HyperDashTarget = new Banana();
|
||||
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.RelativePositionAxes = Axes.None;
|
||||
|
@ -5,11 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||
{
|
||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
||||
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||
|
||||
foreach (var currentObject in beatmap.HitObjects)
|
||||
{
|
||||
if (currentObject is Fruit fruitObject)
|
||||
objectWithDroplets.Add(fruitObject);
|
||||
palpableObjects.Add(fruitObject);
|
||||
|
||||
if (currentObject is JuiceStream)
|
||||
{
|
||||
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
|
||||
foreach (var juice in currentObject.NestedHitObjects)
|
||||
{
|
||||
if (!(currentJuiceElement is TinyDroplet))
|
||||
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
|
||||
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||
palpableObjects.Add(palpableObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
|
||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||
|
||||
@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int lastDirection = 0;
|
||||
double lastExcess = halfCatcherWidth;
|
||||
|
||||
for (int i = 0; i < objectWithDroplets.Count - 1; i++)
|
||||
for (int i = 0; i < palpableObjects.Count - 1; i++)
|
||||
{
|
||||
CatchHitObject currentObject = objectWithDroplets[i];
|
||||
CatchHitObject nextObject = objectWithDroplets[i + 1];
|
||||
var currentObject = palpableObjects[i];
|
||||
var nextObject = palpableObjects[i + 1];
|
||||
|
||||
// Reset variables in-case values have changed (e.g. after applying HR)
|
||||
currentObject.HyperDashTarget = null;
|
||||
|
@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
{
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
|
||||
public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
|
||||
public new PalpableCatchHitObject BaseObject => (PalpableCatchHitObject)base.BaseObject;
|
||||
|
||||
public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
|
||||
public new PalpableCatchHitObject LastObject => (PalpableCatchHitObject)base.LastObject;
|
||||
|
||||
public readonly float NormalizedPosition;
|
||||
public readonly float LastNormalizedPosition;
|
||||
|
@ -2,13 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class Banana : Fruit
|
||||
public class Banana : Fruit, IHasComboInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of banana in current shower.
|
||||
@ -26,6 +29,29 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
Samples = samples;
|
||||
}
|
||||
|
||||
private Color4? colour;
|
||||
|
||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
// override any external colour changes with banananana
|
||||
return colour ??= getBananaColour();
|
||||
}
|
||||
|
||||
private Color4 getBananaColour()
|
||||
{
|
||||
switch (RNG.Next(0, 3))
|
||||
{
|
||||
default:
|
||||
return new Color4(255, 240, 0, 255);
|
||||
|
||||
case 1:
|
||||
return new Color4(255, 192, 0, 255);
|
||||
|
||||
case 2:
|
||||
return new Color4(214, 221, 28, 255);
|
||||
}
|
||||
}
|
||||
|
||||
private class BananaHitSampleInfo : HitSampleInfo
|
||||
{
|
||||
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
||||
|
@ -27,11 +27,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
set => x = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this object can be placed on the catcher's plate.
|
||||
/// </summary>
|
||||
public virtual bool CanBePlated => false;
|
||||
|
||||
/// <summary>
|
||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||
/// </summary>
|
||||
@ -63,13 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
set => ComboIndexBindable.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Difference between the distance to the next object
|
||||
/// and the distance that would have triggered a hyper dash.
|
||||
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
|
||||
/// </summary>
|
||||
public float DistanceToHyperDash { get; set; }
|
||||
|
||||
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||
|
||||
/// <summary>
|
||||
@ -83,16 +71,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public float Scale { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this fruit can initiate a hyperdash.
|
||||
/// </summary>
|
||||
public bool HyperDash => HyperDashTarget != null;
|
||||
|
||||
/// <summary>
|
||||
/// The target fruit if we are to initiate a hyperdash.
|
||||
/// </summary>
|
||||
public CatchHitObject HyperDashTarget;
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
@ -105,14 +83,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single object that can be caught by the catcher.
|
||||
/// </summary>
|
||||
public abstract class PalpableCatchHitObject : CatchHitObject
|
||||
{
|
||||
public override bool CanBePlated => true;
|
||||
}
|
||||
|
||||
public enum FruitVisualRepresentation
|
||||
{
|
||||
Pear,
|
||||
|
@ -1,10 +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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
@ -15,14 +13,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
private Color4? colour;
|
||||
|
||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
// override any external colour changes with banananana
|
||||
return colour ??= getBananaColour();
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
base.UpdateInitialTransforms();
|
||||
@ -46,20 +36,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
if (Samples != null)
|
||||
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
|
||||
}
|
||||
|
||||
private Color4 getBananaColour()
|
||||
{
|
||||
switch (RNG.Next(0, 3))
|
||||
{
|
||||
default:
|
||||
return new Color4(255, 240, 0, 255);
|
||||
|
||||
case 1:
|
||||
return new Color4(255, 192, 0, 255);
|
||||
|
||||
case 2:
|
||||
return new Color4(214, 221, 28, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,55 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject
|
||||
{
|
||||
protected Container ScaleContainer { get; private set; }
|
||||
|
||||
protected PalpableDrawableCatchHitObject(CatchHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
Masking = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
ScaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
|
||||
ScaleContainer.Scale = new Vector2(HitObject.Scale);
|
||||
}
|
||||
|
||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours) =>
|
||||
comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count];
|
||||
}
|
||||
|
||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||
{
|
||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||
|
||||
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
||||
|
||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||
|
||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||
|
@ -4,11 +4,12 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableDroplet : PalpableDrawableCatchHitObject
|
||||
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||
{
|
||||
public override bool StaysOnPlate => false;
|
||||
|
||||
|
@ -4,11 +4,12 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableFruit : PalpableDrawableCatchHitObject
|
||||
public class DrawableFruit : DrawablePalpableCatchHitObject
|
||||
{
|
||||
public DrawableFruit(CatchHitObject h)
|
||||
: base(h)
|
||||
|
@ -0,0 +1,42 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
|
||||
{
|
||||
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
||||
/// </summary>
|
||||
public virtual bool StaysOnPlate => true;
|
||||
|
||||
protected readonly Container ScaleContainer;
|
||||
|
||||
protected DrawablePalpableCatchHitObject(CatchHitObject h)
|
||||
: base(h)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
|
||||
AddInternal(ScaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ScaleContainer.Scale = new Vector2(HitObject.Scale);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DropletPiece : CompositeDrawable
|
||||
{
|
||||
public DropletPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var hitObject = drawableCatchObject.HitObject;
|
||||
|
||||
InternalChild = new Pulp
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||
};
|
||||
|
||||
if (hitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(2f),
|
||||
Depth = 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
BorderThickness = 6,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0.3f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class BananaPiece : PulpFormation
|
||||
{
|
@ -0,0 +1,31 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class BorderPiece : Circle
|
||||
{
|
||||
public BorderPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
BorderColour = Color4.White;
|
||||
BorderThickness = 6f * FruitPiece.RADIUS_ADJUST;
|
||||
|
||||
// Border is drawn only when there is a child drawable.
|
||||
Child = new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class DropletPiece : CompositeDrawable
|
||||
{
|
||||
public DropletPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
|
||||
InternalChild = new Pulp
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||
};
|
||||
|
||||
if (drawableCatchObject.HitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new HyperDropletBorderPiece());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,9 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
internal class FruitPiece : CompositeDrawable
|
||||
{
|
||||
@ -19,8 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
/// </summary>
|
||||
public const float RADIUS_ADJUST = 1.1f;
|
||||
|
||||
private Circle border;
|
||||
private CatchHitObject hitObject;
|
||||
private BorderPiece border;
|
||||
private PalpableCatchHitObject hitObject;
|
||||
|
||||
public FruitPiece()
|
||||
{
|
||||
@ -30,52 +27,18 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
hitObject = drawableCatchObject.HitObject;
|
||||
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
||||
border = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = 6f * RADIUS_ADJUST,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
},
|
||||
getFruitFor(hitObject.VisualRepresentation),
|
||||
border = new BorderPiece(),
|
||||
});
|
||||
|
||||
if (hitObject.HyperDash)
|
||||
{
|
||||
AddInternal(new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
BorderThickness = 12f * RADIUS_ADJUST,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0.3f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
}
|
||||
}
|
||||
});
|
||||
AddInternal(new HyperBorderPiece());
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class GrapePiece : PulpFormation
|
||||
{
|
@ -0,0 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class HyperBorderPiece : BorderPiece
|
||||
{
|
||||
public HyperBorderPiece()
|
||||
{
|
||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||
BorderThickness = 12f * FruitPiece.RADIUS_ADJUST;
|
||||
|
||||
Child.Alpha = 0.3f;
|
||||
Child.Blending = BlendingParameters.Additive;
|
||||
Child.Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class HyperDropletBorderPiece : HyperBorderPiece
|
||||
{
|
||||
public HyperDropletBorderPiece()
|
||||
{
|
||||
Size /= 2;
|
||||
BorderThickness = 6f;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class PearPiece : PulpFormation
|
||||
{
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class PineapplePiece : PulpFormation
|
||||
{
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class PulpFormation : CompositeDrawable
|
||||
{
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class RaspberryPiece : PulpFormation
|
||||
{
|
35
osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
Normal file
35
osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single object that can be caught by the catcher.
|
||||
/// This includes normal fruits, droplets, and bananas but excludes objects that act only as a container of nested hit objects.
|
||||
/// </summary>
|
||||
public abstract class PalpableCatchHitObject : CatchHitObject, IHasComboInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Difference between the distance to the next object
|
||||
/// and the distance that would have triggered a hyper dash.
|
||||
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
|
||||
/// </summary>
|
||||
public float DistanceToHyperDash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this fruit can initiate a hyperdash.
|
||||
/// </summary>
|
||||
public bool HyperDash => HyperDashTarget != null;
|
||||
|
||||
/// <summary>
|
||||
/// The target fruit if we are to initiate a hyperdash.
|
||||
/// </summary>
|
||||
public CatchHitObject HyperDashTarget;
|
||||
|
||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count];
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
float lastPosition = CatchPlayfield.CENTER_X;
|
||||
double lastTime = 0;
|
||||
|
||||
void moveToNext(CatchHitObject h)
|
||||
void moveToNext(PalpableCatchHitObject h)
|
||||
{
|
||||
float positionChange = Math.Abs(lastPosition - h.X);
|
||||
double timeAvailable = h.StartTime - lastTime;
|
||||
@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects)
|
||||
{
|
||||
switch (obj)
|
||||
if (obj is PalpableCatchHitObject palpableObject)
|
||||
{
|
||||
case Fruit _:
|
||||
moveToNext(obj);
|
||||
break;
|
||||
moveToNext(palpableObject);
|
||||
}
|
||||
|
||||
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
||||
{
|
||||
switch (nestedObj)
|
||||
if (nestedObj is PalpableCatchHitObject palpableNestedObject)
|
||||
{
|
||||
case Banana _:
|
||||
case TinyDroplet _:
|
||||
case Droplet _:
|
||||
case Fruit _:
|
||||
moveToNext(nestedObj);
|
||||
break;
|
||||
moveToNext(palpableNestedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
|
||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
||||
|
||||
|
@ -220,11 +220,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <summary>
|
||||
/// Let the catcher attempt to catch a fruit.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit to catch.</param>
|
||||
/// <param name="hitObject">The fruit to catch.</param>
|
||||
/// <returns>Whether the catch is possible.</returns>
|
||||
public bool AttemptCatch(CatchHitObject fruit)
|
||||
public bool AttemptCatch(CatchHitObject hitObject)
|
||||
{
|
||||
if (!fruit.CanBePlated)
|
||||
if (!(hitObject is PalpableCatchHitObject fruit))
|
||||
return false;
|
||||
|
||||
var halfCatchWidth = catchWidth * 0.5f;
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
};
|
||||
}
|
||||
|
||||
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
||||
{
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
||||
}
|
||||
|
||||
if (result.IsHit && fruit.HitObject.CanBePlated)
|
||||
if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit)
|
||||
{
|
||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
if (caughtFruit == null) return;
|
||||
|
||||
caughtFruit.RelativePositionAxes = Axes.None;
|
||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||
caughtFruit.IsOnPlate = true;
|
||||
|
||||
caughtFruit.Anchor = Anchor.TopCentre;
|
||||
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
||||
}
|
||||
|
||||
if (fruit.HitObject.LastInCombo)
|
||||
if (hitObject.HitObject.LastInCombo)
|
||||
{
|
||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||
runAfterLoaded(() => MovableCatcher.Explode());
|
||||
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
MovableCatcher.Drop();
|
||||
}
|
||||
|
||||
comboDisplay.OnNewResult(fruit, result);
|
||||
comboDisplay.OnNewResult(hitObject, result);
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||
|
@ -96,6 +96,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
var first = (OsuHitObject)objects.First();
|
||||
var second = (OsuHitObject)objects.Last();
|
||||
|
||||
return first.Position == second.Position;
|
||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,5 +86,64 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSecondCircleInSelectionAlsoSnaps()
|
||||
{
|
||||
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
|
||||
|
||||
AddStep("disable distance snap", () => InputManager.Key(Key.Q));
|
||||
|
||||
AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
|
||||
|
||||
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("increment time", () => EditorClock.SeekForward(true));
|
||||
|
||||
AddStep("move mouse right", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.2f, 0)));
|
||||
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("increment time", () => EditorClock.SeekForward(true));
|
||||
|
||||
AddStep("move mouse down", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Width * 0.2f)));
|
||||
AddStep("place third object", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("enter selection mode", () => InputManager.Key(Key.Number1));
|
||||
|
||||
AddStep("select objects 2 and 3", () =>
|
||||
{
|
||||
// add selection backwards to test non-sequential time ordering
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[2]);
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]);
|
||||
});
|
||||
|
||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
|
||||
|
||||
AddAssert("object 3 snapped to 1", () =>
|
||||
{
|
||||
var objects = EditorBeatmap.HitObjects;
|
||||
|
||||
var first = (OsuHitObject)objects.First();
|
||||
var third = (OsuHitObject)objects.Last();
|
||||
|
||||
return Precision.AlmostEquals(first.EndPosition, third.Position);
|
||||
});
|
||||
|
||||
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f)));
|
||||
|
||||
AddAssert("object 2 snapped to 1", () =>
|
||||
{
|
||||
var objects = EditorBeatmap.HitObjects;
|
||||
|
||||
var first = (OsuHitObject)objects.First();
|
||||
var second = (OsuHitObject)objects.ElementAt(1);
|
||||
|
||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||
});
|
||||
|
||||
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
private class SnapProvider : IPositionSnapProvider
|
||||
{
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||
|
@ -105,11 +105,20 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
return snapResult;
|
||||
|
||||
return new SnapResult(screenSpacePosition, null);
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
var positionSnap = SnapScreenSpacePositionToValidPosition(screenSpacePosition);
|
||||
if (positionSnap.ScreenSpacePosition != screenSpacePosition)
|
||||
return positionSnap;
|
||||
|
||||
// will be null if distance snap is disabled or not feasible for the current time value.
|
||||
if (distanceSnapGrid == null)
|
||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
};
|
||||
}
|
||||
|
||||
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
|
||||
@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
||||
|
||||
state.BindTo(drawableObject.State);
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
}
|
||||
@ -59,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
state.BindValueChanged(updateState, true);
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
explode.Colour = colour.NewValue;
|
||||
@ -68,15 +65,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}, true);
|
||||
|
||||
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||
|
||||
drawableObject.ApplyCustomUpdateState += updateState;
|
||||
updateState(drawableObject, drawableObject.State.Value);
|
||||
}
|
||||
|
||||
private void updateState(ValueChangedEvent<ArmedState> state)
|
||||
private void updateState(DrawableHitObject drawableObject, ArmedState state)
|
||||
{
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
|
||||
{
|
||||
glow.FadeOut(400);
|
||||
|
||||
switch (state.NewValue)
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
const double flash_in = 40;
|
||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
private SkinnableSpriteText hitCircleText;
|
||||
|
||||
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
|
||||
@ -113,7 +112,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
if (overlayAboveNumber)
|
||||
AddInternal(hitCircleOverlay.CreateProxy());
|
||||
|
||||
state.BindTo(drawableObject.State);
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
|
||||
@ -137,19 +135,21 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
state.BindValueChanged(updateState, true);
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
if (hasNumber)
|
||||
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
|
||||
|
||||
drawableObject.ApplyCustomUpdateState += updateState;
|
||||
updateState(drawableObject, drawableObject.State.Value);
|
||||
}
|
||||
|
||||
private void updateState(ValueChangedEvent<ArmedState> state)
|
||||
private void updateState(DrawableHitObject drawableObject, ArmedState state)
|
||||
{
|
||||
const double legacy_fade_duration = 240;
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
|
||||
{
|
||||
switch (state.NewValue)
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
circleSprites.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
|
@ -153,6 +153,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private class SnapProvider : IPositionSnapProvider
|
||||
{
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
||||
|
@ -163,10 +163,10 @@ namespace osu.Game.Input.Bindings
|
||||
[Description("Toggle now playing overlay")]
|
||||
ToggleNowPlaying,
|
||||
|
||||
[Description("Previous Selection")]
|
||||
[Description("Previous selection")]
|
||||
SelectPrevious,
|
||||
|
||||
[Description("Next Selection")]
|
||||
[Description("Next selection")]
|
||||
SelectNext,
|
||||
|
||||
[Description("Home")]
|
||||
@ -175,26 +175,26 @@ namespace osu.Game.Input.Bindings
|
||||
[Description("Toggle notifications")]
|
||||
ToggleNotifications,
|
||||
|
||||
[Description("Pause")]
|
||||
[Description("Pause gameplay")]
|
||||
PauseGameplay,
|
||||
|
||||
// Editor
|
||||
[Description("Setup Mode")]
|
||||
[Description("Setup mode")]
|
||||
EditorSetupMode,
|
||||
|
||||
[Description("Compose Mode")]
|
||||
[Description("Compose mode")]
|
||||
EditorComposeMode,
|
||||
|
||||
[Description("Design Mode")]
|
||||
[Description("Design mode")]
|
||||
EditorDesignMode,
|
||||
|
||||
[Description("Timing Mode")]
|
||||
[Description("Timing mode")]
|
||||
EditorTimingMode,
|
||||
|
||||
[Description("Hold for HUD")]
|
||||
HoldForHUD,
|
||||
|
||||
[Description("Random Skin")]
|
||||
[Description("Random skin")]
|
||||
RandomSkin,
|
||||
}
|
||||
}
|
||||
|
@ -442,6 +442,9 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||
|
||||
public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
||||
|
||||
public abstract float DurationToDistance(double referenceTime, double duration);
|
||||
|
@ -8,12 +8,22 @@ namespace osu.Game.Rulesets.Edit
|
||||
public interface IPositionSnapProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Given a position, find a valid time snap.
|
||||
/// Given a position, find a valid time and position snap.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This call should be equivalent to running <see cref="SnapScreenSpacePositionToValidPosition"/> with any additional logic that can be performed without the time immutability restriction.
|
||||
/// </remarks>
|
||||
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
|
||||
/// <returns>The time and position post-snapping.</returns>
|
||||
SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||
|
||||
/// <summary>
|
||||
/// Given a position, find a value position snap, restricting time to its input value.
|
||||
/// </summary>
|
||||
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
|
||||
/// <returns>The position post-snapping. Time will always be null.</returns>
|
||||
SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
||||
/// </summary>
|
||||
|
@ -128,6 +128,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
private readonly Bindable<ArmedState> state = new Bindable<ArmedState>();
|
||||
|
||||
/// <summary>
|
||||
/// The state of this <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For pooled hitobjects, <see cref="ApplyCustomUpdateState"/> is recommended to be used instead for better editor/rewinding support.
|
||||
/// </remarks>
|
||||
public IBindable<ArmedState> State => state;
|
||||
|
||||
/// <summary>
|
||||
@ -259,7 +265,17 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
// If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates.
|
||||
if (IsLoaded)
|
||||
Schedule(() => updateState(ArmedState.Idle, true));
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Result.IsHit)
|
||||
updateState(ArmedState.Hit, true);
|
||||
else if (Result.HasResult)
|
||||
updateState(ArmedState.Miss, true);
|
||||
else
|
||||
updateState(ArmedState.Idle, true);
|
||||
});
|
||||
}
|
||||
|
||||
hasHitObjectApplied = true;
|
||||
}
|
||||
@ -529,11 +545,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
private void updateComboColour()
|
||||
{
|
||||
if (!(HitObject is IHasComboInformation)) return;
|
||||
if (!(HitObject is IHasComboInformation combo)) return;
|
||||
|
||||
var comboColours = CurrentSkin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
|
||||
|
||||
AccentColour.Value = GetComboColour(comboColours);
|
||||
var comboColours = CurrentSkin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||
AccentColour.Value = combo.GetComboColour(comboColours);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -544,6 +559,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// This will only be called if the <see cref="HitObject"/> implements <see cref="IHasComboInformation"/>.
|
||||
/// </remarks>
|
||||
/// <param name="comboColours">A list of combo colours provided by the beatmap or skin. Can be null if not available.</param>
|
||||
[Obsolete("Unused. Implement IHasComboInformation and IHasComboInformation.GetComboColour() on the HitObject model instead.")] // Can be removed 20210527
|
||||
protected virtual Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
if (!(HitObject is IHasComboInformation combo))
|
||||
|
@ -1,7 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
@ -35,5 +38,13 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// Whether this is the last object in the current combo.
|
||||
/// </summary>
|
||||
bool LastInCombo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour of the combo described by this <see cref="IHasComboInformation"/> object from a set of possible combo colours.
|
||||
/// Defaults to using <see cref="ComboIndex"/> to decide the colour.
|
||||
/// </summary>
|
||||
/// <param name="comboColours">A list of possible combo colours provided by the beatmap or skin.</param>
|
||||
/// <returns>The colour of the combo described by this <see cref="IHasComboInformation"/> object.</returns>
|
||||
Color4 GetComboColour([NotNull] IReadOnlyList<Color4> comboColours) => comboColours.Count > 0 ? comboColours[ComboIndex % comboColours.Count] : Color4.White;
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
if (e.Button == MouseButton.Right)
|
||||
return false;
|
||||
|
||||
if (movementBlueprint != null)
|
||||
if (movementBlueprints != null)
|
||||
{
|
||||
isDraggingBlueprint = true;
|
||||
changeHandler?.BeginChange();
|
||||
@ -299,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
SelectionBlueprints.Remove(blueprint);
|
||||
|
||||
if (movementBlueprint == blueprint)
|
||||
if (movementBlueprints?.Contains(blueprint) == true)
|
||||
finishSelectionMovement();
|
||||
|
||||
OnBlueprintRemoved(hitObject);
|
||||
@ -424,8 +424,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
#region Selection Movement
|
||||
|
||||
private Vector2? movementBlueprintOriginalPosition;
|
||||
private SelectionBlueprint movementBlueprint;
|
||||
private Vector2[] movementBlueprintOriginalPositions;
|
||||
private SelectionBlueprint[] movementBlueprints;
|
||||
private bool isDraggingBlueprint;
|
||||
|
||||
/// <summary>
|
||||
@ -442,8 +442,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
||||
movementBlueprint = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
|
||||
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
|
||||
movementBlueprints = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).ToArray();
|
||||
movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -453,30 +453,47 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <returns>Whether a movement was active.</returns>
|
||||
private bool moveCurrentSelection(DragEvent e)
|
||||
{
|
||||
if (movementBlueprint == null)
|
||||
if (movementBlueprints == null)
|
||||
return false;
|
||||
|
||||
if (snapProvider == null)
|
||||
return true;
|
||||
|
||||
Debug.Assert(movementBlueprintOriginalPosition != null);
|
||||
Debug.Assert(movementBlueprintOriginalPositions != null);
|
||||
|
||||
HitObject draggedObject = movementBlueprint.HitObject;
|
||||
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||
|
||||
// check for positional snap for every object in selection (for things like object-object snapping)
|
||||
for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++)
|
||||
{
|
||||
var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled;
|
||||
|
||||
var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition);
|
||||
|
||||
if (positionalResult.ScreenSpacePosition == testPosition) continue;
|
||||
|
||||
// attempt to move the objects, and abort any time based snapping if we can.
|
||||
if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition)))
|
||||
return true;
|
||||
}
|
||||
|
||||
// if no positional snapping could be performed, try unrestricted snapping from the earliest
|
||||
// hitobject in the selection.
|
||||
|
||||
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||
Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled;
|
||||
|
||||
// Retrieve a snapped position.
|
||||
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
|
||||
|
||||
// Move the hitobjects.
|
||||
if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition)))
|
||||
if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), result.ScreenSpacePosition)))
|
||||
return true;
|
||||
|
||||
if (result.Time.HasValue)
|
||||
{
|
||||
// Apply the start time at the newly snapped-to position
|
||||
double offset = result.Time.Value - draggedObject.StartTime;
|
||||
double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime;
|
||||
|
||||
foreach (HitObject obj in Beatmap.SelectedHitObjects)
|
||||
{
|
||||
@ -494,11 +511,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <returns>Whether a movement was active.</returns>
|
||||
private bool finishSelectionMovement()
|
||||
{
|
||||
if (movementBlueprint == null)
|
||||
if (movementBlueprints == null)
|
||||
return false;
|
||||
|
||||
movementBlueprintOriginalPosition = null;
|
||||
movementBlueprint = null;
|
||||
movementBlueprintOriginalPositions = null;
|
||||
movementBlueprints = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -224,6 +224,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
/// </summary>
|
||||
public double VisibleRange => track.Length / Zoom;
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -19,8 +18,8 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -28,32 +27,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public class TimelineHitObjectBlueprint : SelectionBlueprint
|
||||
{
|
||||
private readonly Circle circle;
|
||||
private const float thickness = 5;
|
||||
private const float shadow_radius = 5;
|
||||
private const float circle_size = 24;
|
||||
|
||||
public Action<DragEvent> OnDragHandled;
|
||||
|
||||
[UsedImplicitly]
|
||||
private readonly Bindable<double> startTime;
|
||||
|
||||
public Action<DragEvent> OnDragHandled;
|
||||
private Bindable<int> indexInCurrentComboBindable;
|
||||
private Bindable<int> comboIndexBindable;
|
||||
|
||||
private readonly Circle circle;
|
||||
private readonly DragBar dragBar;
|
||||
|
||||
private readonly List<Container> shadowComponents = new List<Container>();
|
||||
|
||||
private DrawableHitObject drawableHitObject;
|
||||
|
||||
private Bindable<Color4> comboColour;
|
||||
|
||||
private readonly Container mainComponents;
|
||||
|
||||
private readonly OsuSpriteText comboIndexText;
|
||||
|
||||
private Bindable<int> comboIndex;
|
||||
|
||||
private const float thickness = 5;
|
||||
|
||||
private const float shadow_radius = 5;
|
||||
|
||||
private const float circle_size = 24;
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; }
|
||||
|
||||
public TimelineHitObjectBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
@ -152,46 +145,42 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
updateShadows();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(HitObjectComposer composer)
|
||||
{
|
||||
if (composer != null)
|
||||
{
|
||||
// best effort to get the drawable representation for grabbing colour and what not.
|
||||
drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (HitObject is IHasComboInformation comboInfo)
|
||||
{
|
||||
comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
comboIndex.BindValueChanged(combo =>
|
||||
{
|
||||
comboIndexText.Text = (combo.NewValue + 1).ToString();
|
||||
}, true);
|
||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||
|
||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||
|
||||
skin.SourceChanged += updateComboColour;
|
||||
}
|
||||
}
|
||||
|
||||
if (drawableHitObject != null)
|
||||
{
|
||||
comboColour = drawableHitObject.AccentColour.GetBoundCopy();
|
||||
comboColour.BindValueChanged(colour =>
|
||||
{
|
||||
if (HitObject is IHasDuration)
|
||||
mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White);
|
||||
else
|
||||
mainComponents.Colour = drawableHitObject.AccentColour.Value;
|
||||
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||
|
||||
var col = mainComponents.Colour.TopLeft.Linear;
|
||||
float brightness = col.R + col.G + col.B;
|
||||
private void updateComboColour()
|
||||
{
|
||||
if (!(HitObject is IHasComboInformation combo))
|
||||
return;
|
||||
|
||||
// decide the combo index colour based on brightness?
|
||||
comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White;
|
||||
}, true);
|
||||
}
|
||||
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||
var comboColour = combo.GetComboColour(comboColours);
|
||||
|
||||
if (HitObject is IHasDuration)
|
||||
mainComponents.Colour = ColourInfo.GradientHorizontal(comboColour, Color4.White);
|
||||
else
|
||||
mainComponents.Colour = comboColour;
|
||||
|
||||
var col = mainComponents.Colour.TopLeft.Linear;
|
||||
float brightness = col.R + col.G + col.B;
|
||||
|
||||
// decide the combo index colour based on brightness?
|
||||
comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -43,6 +44,21 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
if (ruleset == null || composer == null)
|
||||
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
|
||||
|
||||
return wrapSkinnableContent(composer);
|
||||
}
|
||||
|
||||
protected override Drawable CreateTimelineContent()
|
||||
{
|
||||
if (ruleset == null || composer == null)
|
||||
return base.CreateTimelineContent();
|
||||
|
||||
return wrapSkinnableContent(new TimelineBlueprintContainer(composer));
|
||||
}
|
||||
|
||||
private Drawable wrapSkinnableContent(Drawable content)
|
||||
{
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
||||
|
||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||
@ -51,9 +67,7 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
|
||||
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content));
|
||||
}
|
||||
|
||||
protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(composer);
|
||||
}
|
||||
}
|
||||
|
@ -375,6 +375,9 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
||||
return false;
|
||||
|
||||
const double precision = 1;
|
||||
|
||||
double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y;
|
||||
|
Loading…
Reference in New Issue
Block a user