2021-06-22 09:05:29 +08:00
|
|
|
// 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.
|
|
|
|
|
2022-06-17 15:37:17 +08:00
|
|
|
#nullable disable
|
|
|
|
|
2021-06-22 09:05:29 +08:00
|
|
|
using System.Collections.Generic;
|
2021-10-25 15:37:44 +08:00
|
|
|
using System.Linq;
|
|
|
|
using JetBrains.Annotations;
|
2021-07-07 15:10:24 +08:00
|
|
|
using osu.Framework.Allocation;
|
2021-10-25 15:37:44 +08:00
|
|
|
using osu.Framework.Bindables;
|
2022-05-12 14:23:41 +08:00
|
|
|
using osu.Framework.Extensions.EnumExtensions;
|
2021-07-07 15:10:24 +08:00
|
|
|
using osu.Framework.Graphics;
|
2021-10-25 15:37:44 +08:00
|
|
|
using osu.Framework.Graphics.Sprites;
|
|
|
|
using osu.Framework.Input;
|
2022-10-06 17:06:16 +08:00
|
|
|
using osu.Framework.Input.Events;
|
2021-06-22 11:47:24 +08:00
|
|
|
using osu.Game.Beatmaps;
|
2021-10-25 15:37:44 +08:00
|
|
|
using osu.Game.Graphics.UserInterface;
|
2022-10-06 17:06:16 +08:00
|
|
|
using osu.Game.Input.Bindings;
|
2021-06-22 09:05:29 +08:00
|
|
|
using osu.Game.Rulesets.Catch.Objects;
|
2021-10-25 15:37:44 +08:00
|
|
|
using osu.Game.Rulesets.Catch.UI;
|
2021-06-22 09:05:29 +08:00
|
|
|
using osu.Game.Rulesets.Edit;
|
|
|
|
using osu.Game.Rulesets.Edit.Tools;
|
2021-06-22 11:47:24 +08:00
|
|
|
using osu.Game.Rulesets.Mods;
|
2021-10-25 15:37:44 +08:00
|
|
|
using osu.Game.Rulesets.Objects;
|
2021-06-22 11:47:24 +08:00
|
|
|
using osu.Game.Rulesets.UI;
|
2021-10-25 15:37:44 +08:00
|
|
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
2021-06-22 09:39:32 +08:00
|
|
|
using osu.Game.Screens.Edit.Compose.Components;
|
|
|
|
using osuTK;
|
2021-06-22 09:05:29 +08:00
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Catch.Edit
|
|
|
|
{
|
2022-04-28 10:50:55 +08:00
|
|
|
public class CatchHitObjectComposer : DistancedHitObjectComposer<CatchHitObject>
|
2021-06-22 09:05:29 +08:00
|
|
|
{
|
2021-10-25 15:37:44 +08:00
|
|
|
private const float distance_snap_radius = 50;
|
|
|
|
|
|
|
|
private CatchDistanceSnapGrid distanceSnapGrid;
|
|
|
|
|
|
|
|
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
|
|
|
|
|
|
|
private InputManager inputManager;
|
|
|
|
|
2022-10-06 17:06:16 +08:00
|
|
|
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
|
|
|
{
|
|
|
|
MinValue = 1,
|
|
|
|
MaxValue = 10,
|
|
|
|
};
|
|
|
|
|
2021-06-22 09:05:29 +08:00
|
|
|
public CatchHitObjectComposer(CatchRuleset ruleset)
|
|
|
|
: base(ruleset)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-07-07 15:10:24 +08:00
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
private void load()
|
|
|
|
{
|
2022-04-28 15:57:14 +08:00
|
|
|
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
2022-04-28 10:50:55 +08:00
|
|
|
RightSideToolboxContainer.Alpha = 0;
|
|
|
|
DistanceSpacingMultiplier.Disabled = true;
|
|
|
|
|
2021-07-07 15:10:24 +08:00
|
|
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
|
|
|
{
|
2022-10-06 16:26:03 +08:00
|
|
|
Anchor = Anchor.BottomCentre,
|
|
|
|
Origin = Anchor.BottomCentre,
|
|
|
|
RelativeSizeAxes = Axes.X,
|
|
|
|
Height = CatchPlayfield.HEIGHT,
|
2021-07-07 15:10:24 +08:00
|
|
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
|
|
|
});
|
2021-10-25 15:37:44 +08:00
|
|
|
|
|
|
|
LayerBelowRuleset.Add(distanceSnapGrid = new CatchDistanceSnapGrid(new[]
|
|
|
|
{
|
|
|
|
0.0,
|
2021-10-26 19:09:48 +08:00
|
|
|
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
|
|
|
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
2021-10-25 15:37:44 +08:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
|
|
|
inputManager = GetContainingInputManager();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
{
|
|
|
|
base.Update();
|
|
|
|
|
|
|
|
updateDistanceSnapGrid();
|
2021-07-07 15:10:24 +08:00
|
|
|
}
|
|
|
|
|
2022-10-06 17:06:16 +08:00
|
|
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
|
|
{
|
|
|
|
switch (e.Action)
|
|
|
|
{
|
2022-10-18 11:10:30 +08:00
|
|
|
// Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
|
|
|
|
// In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
|
|
|
|
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
|
2022-10-06 17:06:16 +08:00
|
|
|
case GlobalAction.IncreaseScrollSpeed:
|
|
|
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GlobalAction.DecreaseScrollSpeed:
|
|
|
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return base.OnPressed(e);
|
|
|
|
}
|
|
|
|
|
2021-06-22 11:47:24 +08:00
|
|
|
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
2022-10-06 17:06:16 +08:00
|
|
|
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
|
|
|
|
{
|
|
|
|
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
|
|
|
|
};
|
2021-06-22 11:47:24 +08:00
|
|
|
|
2021-06-22 11:10:16 +08:00
|
|
|
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
|
|
|
{
|
|
|
|
new FruitCompositionTool(),
|
2021-07-22 14:20:33 +08:00
|
|
|
new JuiceStreamCompositionTool(),
|
2021-06-22 11:30:09 +08:00
|
|
|
new BananaShowerCompositionTool()
|
2021-06-22 11:10:16 +08:00
|
|
|
};
|
2021-06-22 09:39:32 +08:00
|
|
|
|
2021-10-25 15:37:44 +08:00
|
|
|
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
|
|
|
{
|
|
|
|
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
|
|
|
|
});
|
|
|
|
|
2022-05-12 14:23:41 +08:00
|
|
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
2021-06-22 09:39:32 +08:00
|
|
|
{
|
2022-05-12 14:23:41 +08:00
|
|
|
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
|
|
|
|
2021-06-22 09:39:32 +08:00
|
|
|
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
2021-10-25 15:37:44 +08:00
|
|
|
|
2022-05-12 14:23:41 +08:00
|
|
|
if (snapType.HasFlagFast(SnapType.Grids))
|
2021-10-25 15:37:44 +08:00
|
|
|
{
|
2022-05-12 14:23:41 +08:00
|
|
|
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
|
|
|
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
|
|
|
{
|
|
|
|
result = snapResult;
|
|
|
|
}
|
2021-10-25 15:37:44 +08:00
|
|
|
}
|
|
|
|
|
2021-06-22 09:39:32 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
|
2021-10-25 15:37:44 +08:00
|
|
|
|
|
|
|
[CanBeNull]
|
2021-10-26 10:14:12 +08:00
|
|
|
private PalpableCatchHitObject getLastSnappableHitObject(double time)
|
2021-10-25 15:37:44 +08:00
|
|
|
{
|
|
|
|
var hitObject = EditorBeatmap.HitObjects.OfType<CatchHitObject>().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
|
|
|
|
|
|
|
|
switch (hitObject)
|
|
|
|
{
|
|
|
|
case Fruit fruit:
|
|
|
|
return fruit;
|
|
|
|
|
|
|
|
case JuiceStream juiceStream:
|
|
|
|
return juiceStream.NestedHitObjects.OfType<PalpableCatchHitObject>().LastOrDefault(h => !(h is TinyDroplet));
|
|
|
|
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[CanBeNull]
|
|
|
|
private PalpableCatchHitObject getDistanceSnapGridSourceHitObject()
|
|
|
|
{
|
|
|
|
switch (BlueprintContainer.CurrentTool)
|
|
|
|
{
|
2022-06-24 20:25:23 +08:00
|
|
|
case SelectTool:
|
2021-10-25 15:37:44 +08:00
|
|
|
if (EditorBeatmap.SelectedHitObjects.Count == 0)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime);
|
2021-10-26 10:14:12 +08:00
|
|
|
return getLastSnappableHitObject(minTime);
|
2021-10-25 15:37:44 +08:00
|
|
|
|
2022-06-24 20:25:23 +08:00
|
|
|
case FruitCompositionTool:
|
|
|
|
case JuiceStreamCompositionTool:
|
2021-10-25 15:37:44 +08:00
|
|
|
if (!CursorInPlacementArea)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (EditorBeatmap.PlacementObject.Value is JuiceStream)
|
|
|
|
{
|
|
|
|
// Juice stream path is not subject to snapping.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(inputManager.CurrentState.Mouse.Position);
|
2021-10-26 10:14:12 +08:00
|
|
|
return getLastSnappableHitObject(timeAtCursor);
|
2021-10-25 15:37:44 +08:00
|
|
|
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateDistanceSnapGrid()
|
|
|
|
{
|
2021-10-26 10:17:04 +08:00
|
|
|
if (distanceSnapToggle.Value != TernaryState.True)
|
2021-10-25 15:37:44 +08:00
|
|
|
{
|
|
|
|
distanceSnapGrid.Hide();
|
2021-10-26 10:17:04 +08:00
|
|
|
return;
|
2021-10-25 15:37:44 +08:00
|
|
|
}
|
2021-10-26 10:17:04 +08:00
|
|
|
|
|
|
|
var sourceHitObject = getDistanceSnapGridSourceHitObject();
|
|
|
|
|
|
|
|
if (sourceHitObject == null)
|
2021-10-25 15:37:44 +08:00
|
|
|
{
|
2021-10-26 10:17:04 +08:00
|
|
|
distanceSnapGrid.Hide();
|
|
|
|
return;
|
2021-10-25 15:37:44 +08:00
|
|
|
}
|
2021-10-26 10:17:04 +08:00
|
|
|
|
|
|
|
distanceSnapGrid.Show();
|
|
|
|
distanceSnapGrid.StartTime = sourceHitObject.GetEndTime();
|
|
|
|
distanceSnapGrid.StartX = sourceHitObject.EffectiveX;
|
2021-10-25 15:37:44 +08:00
|
|
|
}
|
2021-06-22 09:05:29 +08:00
|
|
|
}
|
|
|
|
}
|