2024-06-20 14:27:07 +00: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.
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
using osu.Framework.Bindables;
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
using osu.Framework.Graphics.Containers;
|
|
|
|
using osu.Game.Beatmaps.ControlPoints;
|
|
|
|
using osu.Game.Graphics.UserInterface;
|
|
|
|
using osu.Game.Graphics.UserInterfaceV2;
|
|
|
|
using osu.Game.Rulesets.Edit;
|
|
|
|
using osu.Game.Rulesets.Objects;
|
|
|
|
using osu.Game.Rulesets.Objects.Legacy;
|
|
|
|
using osu.Game.Rulesets.Objects.Types;
|
|
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
|
|
using osu.Game.Rulesets.Osu.UI;
|
|
|
|
using osu.Game.Screens.Edit;
|
|
|
|
using osu.Game.Screens.Edit.Compose.Components;
|
|
|
|
using osuTK;
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Osu.Edit
|
|
|
|
{
|
|
|
|
public partial class PolygonGenerationPopover : OsuPopover
|
|
|
|
{
|
|
|
|
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
|
|
|
|
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
|
|
|
|
private SliderWithTextBoxInput<int> repeatCountInput = null!;
|
|
|
|
private SliderWithTextBoxInput<int> pointInput = null!;
|
|
|
|
private RoundedButton commitButton = null!;
|
|
|
|
|
|
|
|
private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
|
|
|
|
private bool began;
|
|
|
|
private bool committed;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private EditorClock editorClock { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private IEditorChangeHandler? changeHandler { get; set; }
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private HitObjectComposer composer { get; set; } = null!;
|
|
|
|
|
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
private void load()
|
|
|
|
{
|
|
|
|
Child = new FillFlowContainer
|
|
|
|
{
|
|
|
|
Width = 220,
|
|
|
|
AutoSizeAxes = Axes.Y,
|
|
|
|
Spacing = new Vector2(20),
|
|
|
|
Children = new Drawable[]
|
|
|
|
{
|
|
|
|
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
|
|
|
|
{
|
|
|
|
Current = new BindableNumber<double>(1)
|
|
|
|
{
|
|
|
|
MinValue = 0.1,
|
|
|
|
MaxValue = 6,
|
|
|
|
Precision = 0.1,
|
|
|
|
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
|
|
|
|
},
|
|
|
|
Instantaneous = true
|
|
|
|
},
|
|
|
|
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
|
|
|
|
{
|
|
|
|
Current = new BindableNumber<int>
|
|
|
|
{
|
|
|
|
MinValue = 0,
|
|
|
|
MaxValue = 180,
|
|
|
|
Precision = 1
|
|
|
|
},
|
|
|
|
Instantaneous = true
|
|
|
|
},
|
|
|
|
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
|
|
|
|
{
|
|
|
|
Current = new BindableNumber<int>(1)
|
|
|
|
{
|
|
|
|
MinValue = 1,
|
|
|
|
MaxValue = 10,
|
|
|
|
Precision = 1
|
|
|
|
},
|
|
|
|
Instantaneous = true
|
|
|
|
},
|
|
|
|
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
|
|
|
|
{
|
|
|
|
Current = new BindableNumber<int>(3)
|
|
|
|
{
|
|
|
|
MinValue = 3,
|
|
|
|
MaxValue = 10,
|
|
|
|
Precision = 1,
|
|
|
|
},
|
|
|
|
Instantaneous = true
|
|
|
|
},
|
|
|
|
commitButton = new RoundedButton
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.X,
|
|
|
|
Text = "Create",
|
|
|
|
Action = commit
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
|
|
|
changeHandler?.BeginChange();
|
|
|
|
began = true;
|
|
|
|
|
2024-10-11 09:38:32 +00:00
|
|
|
distanceSnapInput.Current.BindValueChanged(_ => scheduleRefresh());
|
|
|
|
offsetAngleInput.Current.BindValueChanged(_ => scheduleRefresh());
|
|
|
|
repeatCountInput.Current.BindValueChanged(_ => scheduleRefresh());
|
|
|
|
pointInput.Current.BindValueChanged(_ => scheduleRefresh());
|
2024-06-20 14:27:07 +00:00
|
|
|
tryCreatePolygon();
|
|
|
|
}
|
|
|
|
|
2024-10-11 09:38:32 +00:00
|
|
|
private void scheduleRefresh() => Scheduler.AddOnce(tryCreatePolygon);
|
|
|
|
|
2024-06-20 14:27:07 +00:00
|
|
|
private void tryCreatePolygon()
|
|
|
|
{
|
|
|
|
double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
|
|
|
|
TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime);
|
|
|
|
double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor;
|
|
|
|
IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType<IHasSliderVelocity>().LastOrDefault() ?? new Slider();
|
|
|
|
double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier
|
|
|
|
/ LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME);
|
|
|
|
double length = distanceSnapInput.Current.Value * velocity * timeSpacing;
|
|
|
|
float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value)));
|
|
|
|
|
2024-10-11 09:33:53 +00:00
|
|
|
int totalPoints = pointInput.Current.Value * repeatCountInput.Current.Value;
|
|
|
|
|
|
|
|
if (insertedCircles.Count > totalPoints)
|
|
|
|
{
|
|
|
|
editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1));
|
|
|
|
insertedCircles.RemoveRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1);
|
|
|
|
}
|
2024-06-20 14:27:07 +00:00
|
|
|
|
|
|
|
var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler;
|
|
|
|
bool first = true;
|
|
|
|
|
2024-10-11 09:33:53 +00:00
|
|
|
var newlyAdded = new List<HitCircle>();
|
|
|
|
|
|
|
|
for (int i = 0; i < totalPoints; ++i)
|
2024-06-20 14:27:07 +00:00
|
|
|
{
|
2024-10-11 09:33:53 +00:00
|
|
|
float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + (i + 1) * (2 * float.Pi / pointInput.Current.Value);
|
2024-06-20 14:27:07 +00:00
|
|
|
var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle));
|
|
|
|
|
2024-10-11 09:33:53 +00:00
|
|
|
bool alreadyAdded = i < insertedCircles.Count;
|
|
|
|
|
|
|
|
var circle = alreadyAdded
|
|
|
|
? insertedCircles[i]
|
|
|
|
: new HitCircle();
|
|
|
|
|
|
|
|
circle.Position = position;
|
|
|
|
circle.StartTime = startTime;
|
|
|
|
circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True;
|
|
|
|
|
2024-06-20 14:27:07 +00:00
|
|
|
// TODO: probably ensure samples also follow current ternary status (not trivial)
|
|
|
|
circle.Samples.Add(circle.CreateHitSampleInfo());
|
|
|
|
|
|
|
|
if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y)
|
|
|
|
{
|
|
|
|
commitButton.Enabled.Value = false;
|
2024-10-11 09:46:57 +00:00
|
|
|
editorBeatmap.RemoveRange(insertedCircles);
|
|
|
|
insertedCircles.Clear();
|
2024-06-20 14:27:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-11 09:33:53 +00:00
|
|
|
if (!alreadyAdded)
|
|
|
|
newlyAdded.Add(circle);
|
|
|
|
|
2024-06-20 14:27:07 +00:00
|
|
|
startTime = beatSnapProvider.SnapTime(startTime + timeSpacing);
|
|
|
|
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
|
2024-10-11 09:33:53 +00:00
|
|
|
insertedCircles.AddRange(newlyAdded);
|
|
|
|
editorBeatmap.AddRange(newlyAdded);
|
|
|
|
|
2024-06-20 14:27:07 +00:00
|
|
|
commitButton.Enabled.Value = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void commit()
|
|
|
|
{
|
|
|
|
changeHandler?.EndChange();
|
|
|
|
committed = true;
|
|
|
|
Hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PopOut()
|
|
|
|
{
|
|
|
|
base.PopOut();
|
|
|
|
|
|
|
|
if (began && !committed)
|
|
|
|
{
|
|
|
|
editorBeatmap.RemoveRange(insertedCircles);
|
|
|
|
changeHandler?.EndChange();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|