mirror of
https://github.com/osukey/osukey.git
synced 2025-07-01 00:09:55 +09:00
Merge branch 'master' into replay-pausing-support
This commit is contained in:
@ -38,17 +38,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
|||||||
new Fruit
|
new Fruit
|
||||||
{
|
{
|
||||||
X = 0,
|
X = 0,
|
||||||
StartTime = 250
|
StartTime = 1000
|
||||||
},
|
},
|
||||||
new Fruit
|
new Fruit
|
||||||
{
|
{
|
||||||
X = CatchPlayfield.WIDTH,
|
X = CatchPlayfield.WIDTH,
|
||||||
StartTime = 500
|
StartTime = 2000
|
||||||
},
|
},
|
||||||
new JuiceStream
|
new JuiceStream
|
||||||
{
|
{
|
||||||
X = CatchPlayfield.CENTER_X,
|
X = CatchPlayfield.CENTER_X,
|
||||||
StartTime = 750,
|
StartTime = 3000,
|
||||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
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;
|
hitObject.Scale = 1.5f;
|
||||||
|
|
||||||
if (hyperdash)
|
if (hyperdash)
|
||||||
hitObject.HyperDashTarget = new Banana();
|
((PalpableCatchHitObject)hitObject).HyperDashTarget = new Banana();
|
||||||
|
|
||||||
d.Anchor = Anchor.Centre;
|
d.Anchor = Anchor.Centre;
|
||||||
d.RelativePositionAxes = Axes.None;
|
d.RelativePositionAxes = Axes.None;
|
||||||
|
@ -5,11 +5,11 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.MathUtils;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
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.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||||
{
|
{
|
||||||
@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||||
|
|
||||||
foreach (var currentObject in beatmap.HitObjects)
|
foreach (var currentObject in beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
if (currentObject is Fruit fruitObject)
|
if (currentObject is Fruit fruitObject)
|
||||||
objectWithDroplets.Add(fruitObject);
|
palpableObjects.Add(fruitObject);
|
||||||
|
|
||||||
if (currentObject is JuiceStream)
|
if (currentObject is JuiceStream)
|
||||||
{
|
{
|
||||||
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
|
foreach (var juice in currentObject.NestedHitObjects)
|
||||||
{
|
{
|
||||||
if (!(currentJuiceElement is TinyDroplet))
|
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||||
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
|
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;
|
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||||
|
|
||||||
@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
int lastDirection = 0;
|
int lastDirection = 0;
|
||||||
double lastExcess = halfCatcherWidth;
|
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];
|
var currentObject = palpableObjects[i];
|
||||||
CatchHitObject nextObject = objectWithDroplets[i + 1];
|
var nextObject = palpableObjects[i + 1];
|
||||||
|
|
||||||
// Reset variables in-case values have changed (e.g. after applying HR)
|
// Reset variables in-case values have changed (e.g. after applying HR)
|
||||||
currentObject.HyperDashTarget = null;
|
currentObject.HyperDashTarget = null;
|
||||||
|
@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
private const float normalized_hitobject_radius = 41.0f;
|
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 NormalizedPosition;
|
||||||
public readonly float LastNormalizedPosition;
|
public readonly float LastNormalizedPosition;
|
||||||
|
@ -2,13 +2,16 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class Banana : Fruit
|
public class Banana : Fruit, IHasComboInformation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index of banana in current shower.
|
/// Index of banana in current shower.
|
||||||
@ -26,6 +29,29 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
Samples = samples;
|
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 class BananaHitSampleInfo : HitSampleInfo
|
||||||
{
|
{
|
||||||
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
||||||
|
@ -27,11 +27,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
set => x = value;
|
set => x = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this object can be placed on the catcher's plate.
|
|
||||||
/// </summary>
|
|
||||||
public virtual bool CanBePlated => false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -63,13 +58,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
set => ComboIndexBindable.Value = value;
|
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>();
|
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -83,16 +71,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public float Scale { get; set; } = 1;
|
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)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
@ -105,14 +83,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
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
|
public enum FruitVisualRepresentation
|
||||||
{
|
{
|
||||||
Pear,
|
Pear,
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
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()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
@ -46,20 +36,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
if (Samples != null)
|
if (Samples != null)
|
||||||
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
|
public class DrawableBananaShower : DrawableCatchHitObject
|
||||||
{
|
{
|
||||||
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||||
private readonly Container bananaContainer;
|
private readonly Container bananaContainer;
|
||||||
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case Banana banana:
|
case Banana banana:
|
||||||
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
return createDrawableRepresentation?.Invoke(banana);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
|
@ -2,69 +2,16 @@
|
|||||||
// 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 System.Collections.Generic;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
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
|
||||||
{
|
{
|
||||||
public abstract class PalpableDrawableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
|
||||||
where TObject : PalpableCatchHitObject
|
|
||||||
{
|
|
||||||
protected Container ScaleContainer { get; private set; }
|
|
||||||
|
|
||||||
protected PalpableDrawableCatchHitObject(TObject 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<TObject> : DrawableCatchHitObject
|
|
||||||
where TObject : CatchHitObject
|
|
||||||
{
|
|
||||||
public new TObject HitObject;
|
|
||||||
|
|
||||||
protected DrawableCatchHitObject(TObject hitObject)
|
|
||||||
: base(hitObject)
|
|
||||||
{
|
|
||||||
HitObject = hitObject;
|
|
||||||
Anchor = Anchor.BottomLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||||
{
|
{
|
||||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
|
||||||
|
|
||||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||||
|
|
||||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||||
@ -73,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
X = hitObject.X;
|
X = hitObject.X;
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<CatchHitObject, bool> CheckPosition;
|
public Func<CatchHitObject, bool> CheckPosition;
|
||||||
|
@ -4,15 +4,16 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableDroplet : PalpableDrawableCatchHitObject<Droplet>
|
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override bool StaysOnPlate => false;
|
public override bool StaysOnPlate => false;
|
||||||
|
|
||||||
public DrawableDroplet(Droplet h)
|
public DrawableDroplet(CatchHitObject h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : PalpableDrawableCatchHitObject<Fruit>
|
public class DrawableFruit : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public DrawableFruit(Fruit h)
|
public DrawableFruit(CatchHitObject h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
|
public class DrawableJuiceStream : DrawableCatchHitObject
|
||||||
{
|
{
|
||||||
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||||
private readonly Container dropletContainer;
|
private readonly Container dropletContainer;
|
||||||
@ -47,8 +47,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case CatchHitObject catchObject:
|
case CatchHitObject catchObject:
|
||||||
return createDrawableRepresentation?.Invoke(catchObject)?.With(o =>
|
return createDrawableRepresentation?.Invoke(catchObject);
|
||||||
((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
|
throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
|
||||||
|
@ -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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class BananaPiece : PulpFormation
|
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.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
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
|
internal class FruitPiece : CompositeDrawable
|
||||||
{
|
{
|
||||||
@ -19,8 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float RADIUS_ADJUST = 1.1f;
|
public const float RADIUS_ADJUST = 1.1f;
|
||||||
|
|
||||||
private Circle border;
|
private BorderPiece border;
|
||||||
private CatchHitObject hitObject;
|
private PalpableCatchHitObject hitObject;
|
||||||
|
|
||||||
public FruitPiece()
|
public FruitPiece()
|
||||||
{
|
{
|
||||||
@ -30,52 +27,18 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject)
|
private void load(DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||||
hitObject = drawableCatchObject.HitObject;
|
hitObject = drawableCatchObject.HitObject;
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
{
|
{
|
||||||
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
getFruitFor(hitObject.VisualRepresentation),
|
||||||
border = new Circle
|
border = new BorderPiece(),
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hitObject.HyperDash)
|
if (hitObject.HyperDash)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new HyperBorderPiece());
|
||||||
{
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,9 @@
|
|||||||
// 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 osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class GrapePiece : PulpFormation
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class PearPiece : PulpFormation
|
public class PearPiece : PulpFormation
|
||||||
{
|
{
|
@ -2,10 +2,9 @@
|
|||||||
// 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 osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class PineapplePiece : PulpFormation
|
public class PineapplePiece : PulpFormation
|
||||||
{
|
{
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public abstract class PulpFormation : CompositeDrawable
|
public abstract class PulpFormation : CompositeDrawable
|
||||||
{
|
{
|
@ -2,10 +2,9 @@
|
|||||||
// 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 osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class RaspberryPiece : PulpFormation
|
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;
|
float lastPosition = CatchPlayfield.CENTER_X;
|
||||||
double lastTime = 0;
|
double lastTime = 0;
|
||||||
|
|
||||||
void moveToNext(CatchHitObject h)
|
void moveToNext(PalpableCatchHitObject h)
|
||||||
{
|
{
|
||||||
float positionChange = Math.Abs(lastPosition - h.X);
|
float positionChange = Math.Abs(lastPosition - h.X);
|
||||||
double timeAvailable = h.StartTime - lastTime;
|
double timeAvailable = h.StartTime - lastTime;
|
||||||
@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
foreach (var obj in Beatmap.HitObjects)
|
foreach (var obj in Beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
switch (obj)
|
if (obj is PalpableCatchHitObject palpableObject)
|
||||||
{
|
{
|
||||||
case Fruit _:
|
moveToNext(palpableObject);
|
||||||
moveToNext(obj);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
||||||
{
|
{
|
||||||
switch (nestedObj)
|
if (nestedObj is PalpableCatchHitObject palpableNestedObject)
|
||||||
{
|
{
|
||||||
case Banana _:
|
moveToNext(palpableNestedObject);
|
||||||
case TinyDroplet _:
|
|
||||||
case Droplet _:
|
|
||||||
case Fruit _:
|
|
||||||
moveToNext(nestedObj);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||||
{
|
{
|
||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||||
|
|
||||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
accentColour.BindTo(drawableCatchObject.AccentColour);
|
||||||
|
|
||||||
|
@ -55,21 +55,18 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
CatcherArea,
|
CatcherArea,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
NewResult += onNewResult;
|
||||||
|
RevertResult += onRevertResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
|
||||||
{
|
{
|
||||||
h.OnNewResult += onNewResult;
|
((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
|
||||||
h.OnRevertResult += onRevertResult;
|
|
||||||
|
|
||||||
base.Add(h);
|
|
||||||
|
|
||||||
var fruit = (DrawableCatchHitObject)h;
|
|
||||||
fruit.CheckPosition = CheckIfWeCanCatch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
|
|
||||||
|
@ -220,11 +220,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Let the catcher attempt to catch a fruit.
|
/// Let the catcher attempt to catch a fruit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fruit">The fruit to catch.</param>
|
/// <param name="hitObject">The fruit to catch.</param>
|
||||||
/// <returns>Whether the catch is possible.</returns>
|
/// <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;
|
return false;
|
||||||
|
|
||||||
var halfCatchWidth = catchWidth * 0.5f;
|
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())
|
if (!result.Type.IsScorable())
|
||||||
return;
|
return;
|
||||||
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
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.
|
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
||||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
||||||
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (caughtFruit == null) return;
|
if (caughtFruit == null) return;
|
||||||
|
|
||||||
caughtFruit.RelativePositionAxes = Axes.None;
|
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.IsOnPlate = true;
|
||||||
|
|
||||||
caughtFruit.Anchor = Anchor.TopCentre;
|
caughtFruit.Anchor = Anchor.TopCentre;
|
||||||
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fruit.HitObject.LastInCombo)
|
if (hitObject.HitObject.LastInCombo)
|
||||||
{
|
{
|
||||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||||
runAfterLoaded(() => MovableCatcher.Explode());
|
runAfterLoaded(() => MovableCatcher.Explode());
|
||||||
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
MovableCatcher.Drop();
|
MovableCatcher.Drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
comboDisplay.OnNewResult(fruit, result);
|
comboDisplay.OnNewResult(hitObject, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
|
@ -96,6 +96,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
var first = (OsuHitObject)objects.First();
|
var first = (OsuHitObject)objects.First();
|
||||||
var second = (OsuHitObject)objects.Last();
|
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);
|
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
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
|
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||||
|
new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||||
|
@ -1,63 +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 System;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
|
||||||
{
|
|
||||||
public class DrawableOsuEditPool<T> : DrawableOsuPool<T>
|
|
||||||
where T : DrawableHitObject, new()
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
|
||||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
|
||||||
/// </summary>
|
|
||||||
private const double editor_hit_object_fade_out_extension = 700;
|
|
||||||
|
|
||||||
public DrawableOsuEditPool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
|
|
||||||
: base(checkHittable, onLoaded, initialSize, maximumSize)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState);
|
|
||||||
|
|
||||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
|
||||||
{
|
|
||||||
if (state == ArmedState.Idle)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
|
||||||
switch (hitObject)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
// there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
|
|
||||||
return;
|
|
||||||
|
|
||||||
case DrawableSlider _:
|
|
||||||
// no specifics to sliders but let them fade slower below.
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DrawableHitCircle circle: // also handles slider heads
|
|
||||||
circle.ApproachCircle
|
|
||||||
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
|
||||||
.Expire();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the existing fade out transform
|
|
||||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
|
||||||
|
|
||||||
if (existing == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
hitObject.RemoveTransform(existing);
|
|
||||||
|
|
||||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
|
||||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,12 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -26,8 +29,51 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
protected override GameplayCursorContainer CreateCursor() => null;
|
protected override GameplayCursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
protected override DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||||
=> new DrawableOsuEditPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
{
|
||||||
|
d.ApplyCustomUpdateState += updateState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||||
|
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||||
|
/// </summary>
|
||||||
|
private const double editor_hit_object_fade_out_extension = 700;
|
||||||
|
|
||||||
|
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (state == ArmedState.Idle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
// there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
|
||||||
|
return;
|
||||||
|
|
||||||
|
case DrawableSlider _:
|
||||||
|
// no specifics to sliders but let them fade slower below.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableHitCircle circle: // also handles slider heads
|
||||||
|
circle.ApproachCircle
|
||||||
|
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||||
|
.Expire();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the existing fade out transform
|
||||||
|
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||||
|
|
||||||
|
if (existing == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hitObject.RemoveTransform(existing);
|
||||||
|
|
||||||
|
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||||
|
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||||
return 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.
|
// will be null if distance snap is disabled or not feasible for the current time value.
|
||||||
if (distanceSnapGrid == null)
|
if (distanceSnapGrid == null)
|
||||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||||
|
@ -19,7 +19,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
public class DrawableHitCircle : DrawableOsuHitObject
|
||||||
{
|
{
|
||||||
public OsuAction? HitAction => HitArea.HitAction;
|
public OsuAction? HitAction => HitArea.HitAction;
|
||||||
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
||||||
|
@ -1,32 +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 System;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Pooling;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|
||||||
{
|
|
||||||
public class DrawableOsuPool<T> : DrawablePool<T>
|
|
||||||
where T : DrawableHitObject, new()
|
|
||||||
{
|
|
||||||
private readonly Func<DrawableHitObject, double, bool> checkHittable;
|
|
||||||
private readonly Action<Drawable> onLoaded;
|
|
||||||
|
|
||||||
public DrawableOsuPool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
|
|
||||||
: base(initialSize, maximumSize)
|
|
||||||
{
|
|
||||||
this.checkHittable = checkHittable;
|
|
||||||
this.onLoaded = onLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(o =>
|
|
||||||
{
|
|
||||||
var osuObject = (DrawableOsuHitObject)(object)o;
|
|
||||||
|
|
||||||
osuObject.CheckHittable = checkHittable;
|
|
||||||
osuObject.OnLoadComplete += onLoaded;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,7 +19,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
public class DrawableSlider : DrawableOsuHitObject
|
||||||
{
|
{
|
||||||
public new Slider HitObject => (Slider)base.HitObject;
|
public new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -25,8 +26,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
public class OsuPlayfield : Playfield
|
public class OsuPlayfield : Playfield
|
||||||
{
|
{
|
||||||
public readonly Func<DrawableHitObject, double, bool> CheckHittable;
|
|
||||||
|
|
||||||
private readonly PlayfieldBorder playfieldBorder;
|
private readonly PlayfieldBorder playfieldBorder;
|
||||||
private readonly ProxyContainer approachCircles;
|
private readonly ProxyContainer approachCircles;
|
||||||
private readonly ProxyContainer spinnerProxies;
|
private readonly ProxyContainer spinnerProxies;
|
||||||
@ -56,7 +55,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
};
|
};
|
||||||
|
|
||||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
CheckHittable = hitPolicy.IsHittable;
|
|
||||||
|
|
||||||
var hitWindows = new OsuHitWindows();
|
var hitWindows = new OsuHitWindows();
|
||||||
|
|
||||||
@ -68,6 +66,29 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
NewResult += onNewResult;
|
NewResult += onNewResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
|
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
|
||||||
|
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDrawableHitObjectLoaded(Drawable drawable)
|
||||||
|
{
|
||||||
|
// note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded.
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableSpinner _:
|
||||||
|
spinnerProxies.Add(drawable.CreateProxy());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableHitCircle hitCircle:
|
||||||
|
approachCircles.Add(hitCircle.ProxiedLayer.CreateProxy());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onJudgmentLoaded(DrawableOsuJudgement judgement)
|
private void onJudgmentLoaded(DrawableOsuJudgement judgement)
|
||||||
{
|
{
|
||||||
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
|
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
|
||||||
@ -78,28 +99,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
||||||
|
|
||||||
registerPool<HitCircle, DrawableHitCircle>(10, 100);
|
RegisterPool<HitCircle, DrawableHitCircle>(10, 100);
|
||||||
|
|
||||||
registerPool<Slider, DrawableSlider>(10, 100);
|
RegisterPool<Slider, DrawableSlider>(10, 100);
|
||||||
registerPool<SliderHeadCircle, DrawableSliderHead>(10, 100);
|
RegisterPool<SliderHeadCircle, DrawableSliderHead>(10, 100);
|
||||||
registerPool<SliderTailCircle, DrawableSliderTail>(10, 100);
|
RegisterPool<SliderTailCircle, DrawableSliderTail>(10, 100);
|
||||||
registerPool<SliderTick, DrawableSliderTick>(10, 100);
|
RegisterPool<SliderTick, DrawableSliderTick>(10, 100);
|
||||||
registerPool<SliderRepeat, DrawableSliderRepeat>(5, 50);
|
RegisterPool<SliderRepeat, DrawableSliderRepeat>(5, 50);
|
||||||
|
|
||||||
registerPool<Spinner, DrawableSpinner>(2, 20);
|
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
||||||
registerPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
||||||
registerPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
|
||||||
where TObject : HitObject
|
|
||||||
where TDrawable : DrawableHitObject, new()
|
|
||||||
=> RegisterPool<TObject, TDrawable>(CreatePool<TDrawable>(initialSize, maximumSize));
|
|
||||||
|
|
||||||
protected virtual DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
|
||||||
where TDrawable : DrawableHitObject, new()
|
|
||||||
=> new DrawableOsuPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
|
||||||
|
|
||||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
||||||
|
|
||||||
protected override void OnHitObjectAdded(HitObject hitObject)
|
protected override void OnHitObjectAdded(HitObject hitObject)
|
||||||
@ -114,27 +126,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnHitObjectLoaded(Drawable drawable)
|
|
||||||
{
|
|
||||||
switch (drawable)
|
|
||||||
{
|
|
||||||
case DrawableSliderHead _:
|
|
||||||
case DrawableSliderTail _:
|
|
||||||
case DrawableSliderTick _:
|
|
||||||
case DrawableSliderRepeat _:
|
|
||||||
case DrawableSpinnerTick _:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DrawableSpinner _:
|
|
||||||
spinnerProxies.Add(drawable.CreateProxy());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IDrawableHitObjectWithProxiedApproach approach:
|
|
||||||
approachCircles.Add(approach.ProxiedLayer.CreateProxy());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
|
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
|
||||||
|
@ -153,6 +153,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private class SnapProvider : IPositionSnapProvider
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
|
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||||
|
new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
||||||
|
181
osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs
Normal file
181
osu.Game.Tests/Visual/Online/TestScenePlayHistorySubsection.cs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// 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.Game.Overlays.Profile.Sections.Historical;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using static osu.Game.Users.User;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestScenePlayHistorySubsection : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red);
|
||||||
|
|
||||||
|
private readonly Bindable<User> user = new Bindable<User>();
|
||||||
|
private readonly PlayHistorySubsection section;
|
||||||
|
|
||||||
|
public TestScenePlayHistorySubsection()
|
||||||
|
{
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4
|
||||||
|
},
|
||||||
|
section = new PlayHistorySubsection(user)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNullValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_null_values);
|
||||||
|
AddAssert("Section is hidden", () => section.Alpha == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_empty_values);
|
||||||
|
AddAssert("Section is hidden", () => section.Alpha == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOneValue()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_one_value);
|
||||||
|
AddAssert("Section is hidden", () => section.Alpha == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTwoValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_two_values);
|
||||||
|
AddAssert("Section is visible", () => section.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestConstantValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_constant_values);
|
||||||
|
AddAssert("Section is visible", () => section.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestConstantZeroValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_zero_values);
|
||||||
|
AddAssert("Section is visible", () => section.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFilledValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_filled_values);
|
||||||
|
AddAssert("Section is visible", () => section.Alpha == 1);
|
||||||
|
AddAssert("Array length is the same", () => user_with_filled_values.MonthlyPlaycounts.Length == getChartValuesLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissingValues()
|
||||||
|
{
|
||||||
|
AddStep("Load user", () => user.Value = user_with_missing_values);
|
||||||
|
AddAssert("Section is visible", () => section.Alpha == 1);
|
||||||
|
AddAssert("Array length is 7", () => getChartValuesLength() == 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getChartValuesLength() => this.ChildrenOfType<ProfileLineChart>().Single().Values.Length;
|
||||||
|
|
||||||
|
private static readonly User user_with_null_values = new User
|
||||||
|
{
|
||||||
|
Id = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_empty_values = new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
MonthlyPlaycounts = Array.Empty<UserHistoryCount>()
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_one_value = new User
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
MonthlyPlaycounts = new[]
|
||||||
|
{
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 100 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_two_values = new User
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
MonthlyPlaycounts = new[]
|
||||||
|
{
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 2 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_constant_values = new User
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
MonthlyPlaycounts = new[]
|
||||||
|
{
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 5 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 5 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 5 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_zero_values = new User
|
||||||
|
{
|
||||||
|
Id = 6,
|
||||||
|
MonthlyPlaycounts = new[]
|
||||||
|
{
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 0 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 0 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 0 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_filled_values = new User
|
||||||
|
{
|
||||||
|
Id = 7,
|
||||||
|
MonthlyPlaycounts = new[]
|
||||||
|
{
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly User user_with_missing_values = new User
|
||||||
|
{
|
||||||
|
Id = 8,
|
||||||
|
MonthlyPlaycounts = new[]
|
||||||
|
{
|
||||||
|
new UserHistoryCount { Date = new DateTime(2020, 1, 1), Count = 100 },
|
||||||
|
new UserHistoryCount { Date = new DateTime(2020, 7, 1), Count = 200 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -196,7 +196,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
public Func<int, string> LookupSkinName { private get; set; }
|
public Func<int, string> LookupSkinName { private get; set; }
|
||||||
|
|
||||||
public Func<GlobalAction, string> LookupKeyBindings { private get; set; }
|
public Func<GlobalAction, string> LookupKeyBindings { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum OsuSetting
|
public enum OsuSetting
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
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.Containers;
|
||||||
@ -14,6 +15,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container that can scroll to each section inside it.
|
/// A container that can scroll to each section inside it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Cached]
|
||||||
public class SectionsContainer<T> : Container<T>
|
public class SectionsContainer<T> : Container<T>
|
||||||
where T : Drawable
|
where T : Drawable
|
||||||
{
|
{
|
||||||
|
@ -119,7 +119,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected float GetYPosition(float value)
|
protected float GetYPosition(float value)
|
||||||
{
|
{
|
||||||
if (ActualMaxValue == ActualMinValue) return 0;
|
if (ActualMaxValue == ActualMinValue)
|
||||||
|
// show line at top if the only value on the graph is positive,
|
||||||
|
// and at bottom if the only value on the graph is zero or negative.
|
||||||
|
// just kind of makes most sense intuitively.
|
||||||
|
return value > 1 ? 0 : 1;
|
||||||
|
|
||||||
return (ActualMaxValue - value) / (ActualMaxValue - ActualMinValue);
|
return (ActualMaxValue - value) / (ActualMaxValue - ActualMinValue);
|
||||||
}
|
}
|
||||||
|
@ -164,10 +164,10 @@ namespace osu.Game.Input.Bindings
|
|||||||
[Description("Toggle now playing overlay")]
|
[Description("Toggle now playing overlay")]
|
||||||
ToggleNowPlaying,
|
ToggleNowPlaying,
|
||||||
|
|
||||||
[Description("Previous Selection")]
|
[Description("Previous selection")]
|
||||||
SelectPrevious,
|
SelectPrevious,
|
||||||
|
|
||||||
[Description("Next Selection")]
|
[Description("Next selection")]
|
||||||
SelectNext,
|
SelectNext,
|
||||||
|
|
||||||
[Description("Home")]
|
[Description("Home")]
|
||||||
@ -176,26 +176,26 @@ namespace osu.Game.Input.Bindings
|
|||||||
[Description("Toggle notifications")]
|
[Description("Toggle notifications")]
|
||||||
ToggleNotifications,
|
ToggleNotifications,
|
||||||
|
|
||||||
[Description("Pause Gameplay")]
|
[Description("Pause gameplay")]
|
||||||
PauseGameplay,
|
PauseGameplay,
|
||||||
|
|
||||||
// Editor
|
// Editor
|
||||||
[Description("Setup Mode")]
|
[Description("Setup mode")]
|
||||||
EditorSetupMode,
|
EditorSetupMode,
|
||||||
|
|
||||||
[Description("Compose Mode")]
|
[Description("Compose mode")]
|
||||||
EditorComposeMode,
|
EditorComposeMode,
|
||||||
|
|
||||||
[Description("Design Mode")]
|
[Description("Design mode")]
|
||||||
EditorDesignMode,
|
EditorDesignMode,
|
||||||
|
|
||||||
[Description("Timing Mode")]
|
[Description("Timing mode")]
|
||||||
EditorTimingMode,
|
EditorTimingMode,
|
||||||
|
|
||||||
[Description("Hold for HUD")]
|
[Description("Hold for HUD")]
|
||||||
HoldForHUD,
|
HoldForHUD,
|
||||||
|
|
||||||
[Description("Random Skin")]
|
[Description("Random skin")]
|
||||||
RandomSkin,
|
RandomSkin,
|
||||||
|
|
||||||
[Description("Pause / resume replay")]
|
[Description("Pause / resume replay")]
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Overlays.OSD;
|
using osu.Game.Overlays.OSD;
|
||||||
|
|
||||||
@ -37,11 +38,11 @@ namespace osu.Game.Overlays.Music
|
|||||||
bool wasPlaying = musicController.IsPlaying;
|
bool wasPlaying = musicController.IsPlaying;
|
||||||
|
|
||||||
if (musicController.TogglePause())
|
if (musicController.TogglePause())
|
||||||
onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track"));
|
onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", action));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.MusicNext:
|
case GlobalAction.MusicNext:
|
||||||
musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track")));
|
musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", action)));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -51,11 +52,11 @@ namespace osu.Game.Overlays.Music
|
|||||||
switch (res)
|
switch (res)
|
||||||
{
|
{
|
||||||
case PreviousTrackResult.Restart:
|
case PreviousTrackResult.Restart:
|
||||||
onScreenDisplay?.Display(new MusicActionToast("Restart track"));
|
onScreenDisplay?.Display(new MusicActionToast("Restart track", action));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PreviousTrackResult.Previous:
|
case PreviousTrackResult.Previous:
|
||||||
onScreenDisplay?.Display(new MusicActionToast("Previous track"));
|
onScreenDisplay?.Display(new MusicActionToast("Previous track", action));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -72,9 +73,18 @@ namespace osu.Game.Overlays.Music
|
|||||||
|
|
||||||
private class MusicActionToast : Toast
|
private class MusicActionToast : Toast
|
||||||
{
|
{
|
||||||
public MusicActionToast(string action)
|
private readonly GlobalAction action;
|
||||||
: base("Music Playback", action, string.Empty)
|
|
||||||
|
public MusicActionToast(string value, GlobalAction action)
|
||||||
|
: base("Music Playback", value, string.Empty)
|
||||||
{
|
{
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
ShortcutText.Text = config.LookupKeyBindings(action).ToUpperInvariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,13 @@ namespace osu.Game.Overlays.OSD
|
|||||||
private const int toast_minimum_width = 240;
|
private const int toast_minimum_width = 240;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
protected readonly OsuSpriteText ValueText;
|
protected readonly OsuSpriteText ValueText;
|
||||||
|
|
||||||
|
protected readonly OsuSpriteText ShortcutText;
|
||||||
|
|
||||||
protected Toast(string description, string value, string shortcut)
|
protected Toast(string description, string value, string shortcut)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
@ -68,7 +71,7 @@ namespace osu.Game.Overlays.OSD
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = value
|
Text = value
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
ShortcutText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using static osu.Game.Users.User;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||||
|
{
|
||||||
|
public abstract class ChartProfileSubsection : ProfileSubsection
|
||||||
|
{
|
||||||
|
private ProfileLineChart chart;
|
||||||
|
|
||||||
|
protected ChartProfileSubsection(Bindable<User> user, string headerText)
|
||||||
|
: base(user, headerText)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateContent() => new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 10,
|
||||||
|
Left = 20,
|
||||||
|
Right = 40
|
||||||
|
},
|
||||||
|
Child = chart = new ProfileLineChart()
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
User.BindValueChanged(onUserChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserChanged(ValueChangedEvent<User> e)
|
||||||
|
{
|
||||||
|
var values = GetValues(e.NewValue);
|
||||||
|
|
||||||
|
if (values == null || values.Length <= 1)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.Values = fillZeroValues(values);
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add entries for any missing months (filled with zero values).
|
||||||
|
/// </summary>
|
||||||
|
private UserHistoryCount[] fillZeroValues(UserHistoryCount[] historyEntries)
|
||||||
|
{
|
||||||
|
var filledHistoryEntries = new List<UserHistoryCount>();
|
||||||
|
|
||||||
|
foreach (var entry in historyEntries)
|
||||||
|
{
|
||||||
|
var lastFilled = filledHistoryEntries.LastOrDefault();
|
||||||
|
|
||||||
|
while (lastFilled?.Date.AddMonths(1) < entry.Date)
|
||||||
|
{
|
||||||
|
filledHistoryEntries.Add(lastFilled = new UserHistoryCount
|
||||||
|
{
|
||||||
|
Count = 0,
|
||||||
|
Date = lastFilled.Date.AddMonths(1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filledHistoryEntries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filledHistoryEntries.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract UserHistoryCount[] GetValues(User user);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using static osu.Game.Users.User;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||||
|
{
|
||||||
|
public class PlayHistorySubsection : ChartProfileSubsection
|
||||||
|
{
|
||||||
|
public PlayHistorySubsection(Bindable<User> user)
|
||||||
|
: base(user, "Play History")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override UserHistoryCount[] GetValues(User user) => user?.MonthlyPlaycounts;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,259 @@
|
|||||||
|
// 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.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using static osu.Game.Users.User;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||||
|
{
|
||||||
|
public class ProfileLineChart : CompositeDrawable
|
||||||
|
{
|
||||||
|
private UserHistoryCount[] values;
|
||||||
|
|
||||||
|
[NotNull]
|
||||||
|
public UserHistoryCount[] Values
|
||||||
|
{
|
||||||
|
get => values;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Length == 0)
|
||||||
|
throw new ArgumentException("At least one value expected!", nameof(value));
|
||||||
|
|
||||||
|
graph.Values = values = value;
|
||||||
|
|
||||||
|
createRowTicks();
|
||||||
|
createColumnTicks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly UserHistoryGraph graph;
|
||||||
|
private readonly Container<TickText> rowTicksContainer;
|
||||||
|
private readonly Container<TickText> columnTicksContainer;
|
||||||
|
private readonly Container<TickLine> rowLinesContainer;
|
||||||
|
private readonly Container<TickLine> columnLinesContainer;
|
||||||
|
|
||||||
|
public ProfileLineChart()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 250;
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
rowTicksContainer = new Container<TickText>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
rowLinesContainer = new Container<TickLine>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
columnLinesContainer = new Container<TickLine>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
graph = new UserHistoryGraph
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Empty(),
|
||||||
|
columnTicksContainer = new Container<TickText>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Top = 10 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRowTicks()
|
||||||
|
{
|
||||||
|
rowTicksContainer.Clear();
|
||||||
|
rowLinesContainer.Clear();
|
||||||
|
|
||||||
|
var min = values.Select(v => v.Count).Min();
|
||||||
|
var max = values.Select(v => v.Count).Max();
|
||||||
|
|
||||||
|
var tickInterval = getTickInterval(max - min, 6);
|
||||||
|
|
||||||
|
for (long currentTick = 0; currentTick <= max; currentTick += tickInterval)
|
||||||
|
{
|
||||||
|
if (currentTick < min)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float y;
|
||||||
|
|
||||||
|
// special-case the min == max case to match LineGraph.
|
||||||
|
// lerp isn't really well-defined over a zero interval anyway.
|
||||||
|
if (min == max)
|
||||||
|
y = currentTick > 1 ? 1 : 0;
|
||||||
|
else
|
||||||
|
y = Interpolation.ValueAt(currentTick, 0, 1f, min, max);
|
||||||
|
|
||||||
|
// y axis is inverted in graph-like coordinates.
|
||||||
|
addRowTick(-y, currentTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createColumnTicks()
|
||||||
|
{
|
||||||
|
columnTicksContainer.Clear();
|
||||||
|
columnLinesContainer.Clear();
|
||||||
|
|
||||||
|
var totalMonths = values.Length;
|
||||||
|
|
||||||
|
int monthsPerTick = 1;
|
||||||
|
|
||||||
|
if (totalMonths > 80)
|
||||||
|
monthsPerTick = 12;
|
||||||
|
else if (totalMonths >= 45)
|
||||||
|
monthsPerTick = 3;
|
||||||
|
else if (totalMonths > 20)
|
||||||
|
monthsPerTick = 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < totalMonths; i += monthsPerTick)
|
||||||
|
{
|
||||||
|
var x = (float)i / (totalMonths - 1);
|
||||||
|
addColumnTick(x, values[i].Date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRowTick(float y, double value)
|
||||||
|
{
|
||||||
|
rowTicksContainer.Add(new TickText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativePositionAxes = Axes.Y,
|
||||||
|
Margin = new MarginPadding { Right = 3 },
|
||||||
|
Text = value.ToString("N0"),
|
||||||
|
Font = OsuFont.GetFont(size: 12),
|
||||||
|
Y = y
|
||||||
|
});
|
||||||
|
|
||||||
|
rowLinesContainer.Add(new TickLine
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
RelativePositionAxes = Axes.Y,
|
||||||
|
Height = 0.1f,
|
||||||
|
EdgeSmoothness = Vector2.One,
|
||||||
|
Y = y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addColumnTick(float x, DateTime value)
|
||||||
|
{
|
||||||
|
columnTicksContainer.Add(new TickText
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
Text = value.ToString("MMM yyyy"),
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
|
||||||
|
Rotation = 45,
|
||||||
|
X = x
|
||||||
|
});
|
||||||
|
|
||||||
|
columnLinesContainer.Add(new TickLine
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
Width = 0.1f,
|
||||||
|
EdgeSmoothness = Vector2.One,
|
||||||
|
X = x
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getTickInterval(long range, int maxTicksCount)
|
||||||
|
{
|
||||||
|
// this interval is what would be achieved if the interval was divided perfectly evenly into maxTicksCount ticks.
|
||||||
|
// can contain ugly fractional parts.
|
||||||
|
var exactTickInterval = (float)range / (maxTicksCount - 1);
|
||||||
|
|
||||||
|
// the ideal ticks start with a 1, 2 or 5, and are multipliers of powers of 10.
|
||||||
|
// first off, use log10 to calculate the number of digits in the "exact" interval.
|
||||||
|
var numberOfDigits = Math.Floor(Math.Log10(exactTickInterval));
|
||||||
|
var tickBase = Math.Pow(10, numberOfDigits);
|
||||||
|
|
||||||
|
// then see how the exact tick relates to the power of 10.
|
||||||
|
var exactTickMultiplier = exactTickInterval / tickBase;
|
||||||
|
|
||||||
|
double tickMultiplier;
|
||||||
|
|
||||||
|
// round up the fraction to start with a 1, 2 or 5. closest match wins.
|
||||||
|
if (exactTickMultiplier < 1.5)
|
||||||
|
tickMultiplier = 1.0;
|
||||||
|
else if (exactTickMultiplier < 3)
|
||||||
|
tickMultiplier = 2.0;
|
||||||
|
else if (exactTickMultiplier < 7)
|
||||||
|
tickMultiplier = 5.0;
|
||||||
|
else
|
||||||
|
tickMultiplier = 10.0;
|
||||||
|
|
||||||
|
return Math.Max((long)(tickMultiplier * tickBase), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TickText : OsuSpriteText
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Foreground1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TickLine : Box
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Background6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using static osu.Game.Users.User;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||||
|
{
|
||||||
|
public class ReplaysSubsection : ChartProfileSubsection
|
||||||
|
{
|
||||||
|
public ReplaysSubsection(Bindable<User> user)
|
||||||
|
: base(user, "Replays Watched History")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override UserHistoryCount[] GetValues(User user) => user?.ReplaysWatchedCounts;
|
||||||
|
}
|
||||||
|
}
|
@ -18,8 +18,10 @@ namespace osu.Game.Overlays.Profile.Sections
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new PlayHistorySubsection(User),
|
||||||
new PaginatedMostPlayedBeatmapContainer(User),
|
new PaginatedMostPlayedBeatmapContainer(User),
|
||||||
new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", CounterVisibilityState.VisibleWhenZero),
|
new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", CounterVisibilityState.VisibleWhenZero),
|
||||||
|
new ReplaysSubsection(User)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -442,6 +442,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
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 GetBeatSnapDistanceAt(double referenceTime);
|
||||||
|
|
||||||
public abstract float DurationToDistance(double referenceTime, double duration);
|
public abstract float DurationToDistance(double referenceTime, double duration);
|
||||||
|
@ -8,12 +8,22 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
public interface IPositionSnapProvider
|
public interface IPositionSnapProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a position, find a valid time snap.
|
/// Given a position, find a valid time and position snap.
|
||||||
/// </summary>
|
/// </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>
|
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
|
||||||
/// <returns>The time and position post-snapping.</returns>
|
/// <returns>The time and position post-snapping.</returns>
|
||||||
SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
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>
|
/// <summary>
|
||||||
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -74,6 +74,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when a new nested hit object is created by <see cref="CreateNestedHitObject" />.
|
||||||
|
/// </summary>
|
||||||
|
internal event Action<DrawableHitObject> OnNestedDrawableCreated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a visual indicator should be displayed when a scoring result occurs.
|
/// Whether a visual indicator should be displayed when a scoring result occurs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -141,6 +146,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
private Container<PausableSkinnableSound> samplesContainer;
|
private Container<PausableSkinnableSound> samplesContainer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the initialization logic in <see cref="Playfield" /> has applied.
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsInitialized;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="DrawableHitObject"/>.
|
/// Creates a new <see cref="DrawableHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -214,10 +224,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
foreach (var h in HitObject.NestedHitObjects)
|
foreach (var h in HitObject.NestedHitObjects)
|
||||||
{
|
{
|
||||||
var drawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h)
|
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h);
|
||||||
|
var drawableNested = pooledDrawableNested
|
||||||
?? CreateNestedHitObject(h)
|
?? CreateNestedHitObject(h)
|
||||||
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||||
|
|
||||||
|
// Invoke the event only if this nested object is just created by `CreateNestedHitObject`.
|
||||||
|
if (pooledDrawableNested == null)
|
||||||
|
OnNestedDrawableCreated?.Invoke(drawableNested);
|
||||||
|
|
||||||
drawableNested.OnNewResult += onNewResult;
|
drawableNested.OnNewResult += onNewResult;
|
||||||
drawableNested.OnRevertResult += onRevertResult;
|
drawableNested.OnRevertResult += onRevertResult;
|
||||||
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
||||||
@ -514,11 +529,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
private void updateComboColour()
|
private void updateComboColour()
|
||||||
{
|
{
|
||||||
if (!(HitObject is IHasComboInformation)) return;
|
if (!(HitObject is IHasComboInformation combo)) return;
|
||||||
|
|
||||||
var comboColours = CurrentSkin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
|
var comboColours = CurrentSkin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||||
|
AccentColour.Value = combo.GetComboColour(comboColours);
|
||||||
AccentColour.Value = GetComboColour(comboColours);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -529,6 +543,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// This will only be called if the <see cref="HitObject"/> implements <see cref="IHasComboInformation"/>.
|
/// This will only be called if the <see cref="HitObject"/> implements <see cref="IHasComboInformation"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="comboColours">A list of combo colours provided by the beatmap or skin. Can be null if not available.</param>
|
/// <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)
|
protected virtual Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||||
{
|
{
|
||||||
if (!(HitObject is IHasComboInformation combo))
|
if (!(HitObject is IHasComboInformation combo))
|
||||||
|
@ -1,12 +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.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Drawables
|
|
||||||
{
|
|
||||||
public interface IDrawableHitObjectWithProxiedApproach
|
|
||||||
{
|
|
||||||
Drawable ProxiedLayer { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,10 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types
|
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.
|
/// Whether this is the last object in the current combo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool LastInCombo { get; set; }
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
@ -113,6 +114,16 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onNewDrawableHitObject(DrawableHitObject d)
|
||||||
|
{
|
||||||
|
d.OnNestedDrawableCreated += onNewDrawableHitObject;
|
||||||
|
|
||||||
|
OnNewDrawableHitObject(d);
|
||||||
|
|
||||||
|
Debug.Assert(!d.IsInitialized);
|
||||||
|
d.IsInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield.
|
/// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -124,6 +135,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <param name="h">The DrawableHitObject to add.</param>
|
/// <param name="h">The DrawableHitObject to add.</param>
|
||||||
public virtual void Add(DrawableHitObject h)
|
public virtual void Add(DrawableHitObject h)
|
||||||
{
|
{
|
||||||
|
if (h.IsInitialized)
|
||||||
|
throw new InvalidOperationException($"{nameof(Add)} doesn't support {nameof(DrawableHitObject)} reuse. Use pooling instead.");
|
||||||
|
|
||||||
|
onNewDrawableHitObject(h);
|
||||||
|
|
||||||
HitObjectContainer.Add(h);
|
HitObjectContainer.Add(h);
|
||||||
OnHitObjectAdded(h.HitObject);
|
OnHitObjectAdded(h.HitObject);
|
||||||
}
|
}
|
||||||
@ -157,6 +173,17 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked before a new <see cref="DrawableHitObject"/> is added to this <see cref="Playfield"/>.
|
||||||
|
/// It is invoked only once even if the drawable is pooled and used multiple times for different <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is also invoked for nested <see cref="DrawableHitObject"/>s.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void OnNewDrawableHitObject(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
|
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -321,10 +348,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
var dho = (DrawableHitObject)d;
|
var dho = (DrawableHitObject)d;
|
||||||
|
|
||||||
// If this is the first time this DHO is being used (not loaded), then apply the DHO mods.
|
if (!dho.IsInitialized)
|
||||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
|
||||||
if (!dho.IsLoaded)
|
|
||||||
{
|
{
|
||||||
|
onNewDrawableHitObject(dho);
|
||||||
|
|
||||||
|
// If this is the first time this DHO is being used, then apply the DHO mods.
|
||||||
|
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||||
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
|
||||||
m.ApplyToDrawableHitObjects(dho.Yield());
|
m.ApplyToDrawableHitObjects(dho.Yield());
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (e.Button == MouseButton.Right)
|
if (e.Button == MouseButton.Right)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (movementBlueprint != null)
|
if (movementBlueprints != null)
|
||||||
{
|
{
|
||||||
isDraggingBlueprint = true;
|
isDraggingBlueprint = true;
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
@ -299,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
SelectionBlueprints.Remove(blueprint);
|
SelectionBlueprints.Remove(blueprint);
|
||||||
|
|
||||||
if (movementBlueprint == blueprint)
|
if (movementBlueprints?.Contains(blueprint) == true)
|
||||||
finishSelectionMovement();
|
finishSelectionMovement();
|
||||||
|
|
||||||
OnBlueprintRemoved(hitObject);
|
OnBlueprintRemoved(hitObject);
|
||||||
@ -424,8 +424,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
#region Selection Movement
|
#region Selection Movement
|
||||||
|
|
||||||
private Vector2? movementBlueprintOriginalPosition;
|
private Vector2[] movementBlueprintOriginalPositions;
|
||||||
private SelectionBlueprint movementBlueprint;
|
private SelectionBlueprint[] movementBlueprints;
|
||||||
private bool isDraggingBlueprint;
|
private bool isDraggingBlueprint;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -442,8 +442,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
// 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();
|
movementBlueprints = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).ToArray();
|
||||||
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
|
movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -453,30 +453,47 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <returns>Whether a movement was active.</returns>
|
/// <returns>Whether a movement was active.</returns>
|
||||||
private bool moveCurrentSelection(DragEvent e)
|
private bool moveCurrentSelection(DragEvent e)
|
||||||
{
|
{
|
||||||
if (movementBlueprint == null)
|
if (movementBlueprints == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (snapProvider == null)
|
if (snapProvider == null)
|
||||||
return true;
|
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.
|
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||||
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled;
|
||||||
|
|
||||||
// Retrieve a snapped position.
|
// Retrieve a snapped position.
|
||||||
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
|
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
|
||||||
|
|
||||||
// Move the hitobjects.
|
// Move the hitobjects.
|
||||||
if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition)))
|
if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), result.ScreenSpacePosition)))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (result.Time.HasValue)
|
if (result.Time.HasValue)
|
||||||
{
|
{
|
||||||
// Apply the start time at the newly snapped-to position
|
// 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)
|
foreach (HitObject obj in Beatmap.SelectedHitObjects)
|
||||||
{
|
{
|
||||||
@ -494,11 +511,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <returns>Whether a movement was active.</returns>
|
/// <returns>Whether a movement was active.</returns>
|
||||||
private bool finishSelectionMovement()
|
private bool finishSelectionMovement()
|
||||||
{
|
{
|
||||||
if (movementBlueprint == null)
|
if (movementBlueprints == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
movementBlueprintOriginalPosition = null;
|
movementBlueprintOriginalPositions = null;
|
||||||
movementBlueprint = null;
|
movementBlueprints = null;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double VisibleRange => track.Length / Zoom;
|
public double VisibleRange => track.Length / Zoom;
|
||||||
|
|
||||||
|
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||||
|
new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
|
||||||
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
|
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -19,8 +18,8 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -28,32 +27,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
public class TimelineHitObjectBlueprint : SelectionBlueprint
|
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]
|
[UsedImplicitly]
|
||||||
private readonly Bindable<double> startTime;
|
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 DragBar dragBar;
|
||||||
|
|
||||||
private readonly List<Container> shadowComponents = new List<Container>();
|
private readonly List<Container> shadowComponents = new List<Container>();
|
||||||
|
|
||||||
private DrawableHitObject drawableHitObject;
|
|
||||||
|
|
||||||
private Bindable<Color4> comboColour;
|
|
||||||
|
|
||||||
private readonly Container mainComponents;
|
private readonly Container mainComponents;
|
||||||
|
|
||||||
private readonly OsuSpriteText comboIndexText;
|
private readonly OsuSpriteText comboIndexText;
|
||||||
|
|
||||||
private Bindable<int> comboIndex;
|
[Resolved]
|
||||||
|
private ISkinSource skin { get; set; }
|
||||||
private const float thickness = 5;
|
|
||||||
|
|
||||||
private const float shadow_radius = 5;
|
|
||||||
|
|
||||||
private const float circle_size = 24;
|
|
||||||
|
|
||||||
public TimelineHitObjectBlueprint(HitObject hitObject)
|
public TimelineHitObjectBlueprint(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
@ -152,46 +145,42 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
updateShadows();
|
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()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (HitObject is IHasComboInformation comboInfo)
|
if (HitObject is IHasComboInformation comboInfo)
|
||||||
{
|
{
|
||||||
comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||||
comboIndex.BindValueChanged(combo =>
|
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||||
{
|
|
||||||
comboIndexText.Text = (combo.NewValue + 1).ToString();
|
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||||
}, true);
|
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||||
|
|
||||||
|
skin.SourceChanged += updateComboColour;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (drawableHitObject != null)
|
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
var col = mainComponents.Colour.TopLeft.Linear;
|
private void updateComboColour()
|
||||||
float brightness = col.R + col.G + col.B;
|
{
|
||||||
|
if (!(HitObject is IHasComboInformation combo))
|
||||||
|
return;
|
||||||
|
|
||||||
// decide the combo index colour based on brightness?
|
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||||
comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White;
|
var comboColour = combo.GetComboColour(comboColours);
|
||||||
}, true);
|
|
||||||
}
|
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()
|
protected override void Update()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -43,6 +44,21 @@ namespace osu.Game.Screens.Edit.Compose
|
|||||||
if (ruleset == null || composer == null)
|
if (ruleset == null || composer == null)
|
||||||
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
|
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);
|
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
||||||
|
|
||||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
// 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.
|
// 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.
|
// 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)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
||||||
|
return false;
|
||||||
|
|
||||||
const double precision = 1;
|
const double precision = 1;
|
||||||
|
|
||||||
double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y;
|
double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y;
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
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.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
@ -21,6 +23,9 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
private readonly IBindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SectionsContainer<SetupSection> sectionsContainer { get; set; }
|
||||||
|
|
||||||
public FileChooserLabelledTextBox()
|
public FileChooserLabelledTextBox()
|
||||||
{
|
{
|
||||||
currentFile.BindValueChanged(onFileSelected);
|
currentFile.BindValueChanged(onFileSelected);
|
||||||
@ -47,14 +52,16 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
|
|
||||||
public void DisplayFileChooser()
|
public void DisplayFileChooser()
|
||||||
{
|
{
|
||||||
Target.Child = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions)
|
FileSelector fileSelector;
|
||||||
|
|
||||||
|
Target.Child = fileSelector = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 400,
|
Height = 400,
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
CurrentFile = { BindTarget = currentFile }
|
CurrentFile = { BindTarget = currentFile }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sectionsContainer.ScrollTo(fileSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class FileChooserOsuTextBox : OsuTextBox
|
internal class FileChooserOsuTextBox : OsuTextBox
|
||||||
|
Reference in New Issue
Block a user