mirror of
https://github.com/osukey/osukey.git
synced 2025-05-30 09:57:21 +09:00
Merge branch 'master' into slider-paste-parsing-failures-2
This commit is contained in:
commit
07fc772c24
@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi
|
|||||||
|
|
||||||
**Latest build:**
|
**Latest build:**
|
||||||
|
|
||||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||||
|
|
||||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
||||||
|
|
||||||
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5);
|
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5);
|
||||||
|
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
|
||||||
|
|
||||||
|
23
osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
Normal file
23
osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Scoring
|
||||||
|
{
|
||||||
|
public class ManiaHealthProcessor : DrainingHealthProcessor
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ManiaHealthProcessor(double drainStartTime, double drainLenience = 0)
|
||||||
|
: base(drainStartTime, drainLenience)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override HitResult GetSimulatedHitResult(Judgement judgement)
|
||||||
|
{
|
||||||
|
// Users are not expected to attain perfect judgements for all notes due to the tighter hit window.
|
||||||
|
return judgement.MaxResult == HitResult.Perfect ? HitResult.Great : judgement.MaxResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
225
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
Normal file
225
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
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 osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneSliderSnapping : EditorTestScene
|
||||||
|
{
|
||||||
|
private const double beat_length = 1000;
|
||||||
|
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||||
|
return new TestBeatmap(ruleset, false)
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Slider slider;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("add unsnapped slider", () => EditorBeatmap.Add(slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
Position = OsuPlayfield.BASE_SIZE / 5,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(Vector2.Zero),
|
||||||
|
new PathControlPoint(OsuPlayfield.BASE_SIZE * 2 / 5),
|
||||||
|
new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
AddStep("set beat divisor to 1/1", () =>
|
||||||
|
{
|
||||||
|
var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
|
||||||
|
beatDivisor.Value = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMovingUnsnappedSliderNodesSnaps()
|
||||||
|
{
|
||||||
|
PathControlPointPiece sliderEnd = null;
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("select slider end", () =>
|
||||||
|
{
|
||||||
|
sliderEnd = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
|
||||||
|
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
|
||||||
|
});
|
||||||
|
AddStep("move slider end", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre - new Vector2(0, 20));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
assertSliderSnapped(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddingControlPointToUnsnappedSliderNodesSnaps()
|
||||||
|
{
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("move mouse to new point location", () =>
|
||||||
|
{
|
||||||
|
var firstPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
|
||||||
|
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
|
||||||
|
InputManager.MoveMouseTo((firstPiece.ScreenSpaceDrawQuad.Centre + secondPiece.ScreenSpaceDrawQuad.Centre) / 2);
|
||||||
|
});
|
||||||
|
AddStep("move slider end", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
assertSliderSnapped(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemovingControlPointFromUnsnappedSliderNodesSnaps()
|
||||||
|
{
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("move mouse to second control point", () =>
|
||||||
|
{
|
||||||
|
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
|
||||||
|
InputManager.MoveMouseTo(secondPiece);
|
||||||
|
});
|
||||||
|
AddStep("quick delete", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.PressButton(MouseButton.Right);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
});
|
||||||
|
assertSliderSnapped(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResizingUnsnappedSliderSnaps()
|
||||||
|
{
|
||||||
|
SelectionBoxScaleHandle handle = null;
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("move mouse to scale handle", () =>
|
||||||
|
{
|
||||||
|
handle = this.ChildrenOfType<SelectionBoxScaleHandle>().First();
|
||||||
|
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
|
||||||
|
});
|
||||||
|
AddStep("scale slider", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(20, 20));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
assertSliderSnapped(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRotatingUnsnappedSliderDoesNotSnap()
|
||||||
|
{
|
||||||
|
SelectionBoxRotationHandle handle = null;
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("move mouse to rotate handle", () =>
|
||||||
|
{
|
||||||
|
handle = this.ChildrenOfType<SelectionBoxRotationHandle>().First();
|
||||||
|
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
|
||||||
|
});
|
||||||
|
AddStep("scale slider", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(0, 20));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFlippingSliderDoesNotSnap()
|
||||||
|
{
|
||||||
|
OsuSelectionHandler selectionHandler = null;
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("flip slider horizontally", () =>
|
||||||
|
{
|
||||||
|
selectionHandler = this.ChildrenOfType<OsuSelectionHandler>().Single();
|
||||||
|
selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipHorizontally));
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("flip slider vertically", () =>
|
||||||
|
{
|
||||||
|
selectionHandler = this.ChildrenOfType<OsuSelectionHandler>().Single();
|
||||||
|
selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipVertically));
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReversingSliderDoesNotSnap()
|
||||||
|
{
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("reverse slider", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.G);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertSliderSnapped(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSliderSnapped(bool snapped)
|
||||||
|
=> AddAssert($"slider is {(snapped ? "" : "not ")}snapped", () =>
|
||||||
|
{
|
||||||
|
double durationInBeatLengths = slider.Duration / beat_length;
|
||||||
|
double fractionalPart = durationInBeatLengths - (int)durationInBeatLengths;
|
||||||
|
return Precision.AlmostEquals(fractionalPart, 0) == snapped;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -283,6 +283,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snap the path to the current beat divisor before checking length validity.
|
||||||
|
slider.SnapTo(snapProvider);
|
||||||
|
|
||||||
if (!slider.Path.HasValidLength)
|
if (!slider.Path.HasValidLength)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
@ -290,6 +293,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
slider.Position = oldPosition;
|
slider.Position = oldPosition;
|
||||||
slider.StartTime = oldStartTime;
|
slider.StartTime = oldStartTime;
|
||||||
|
// Snap the path length again to undo the invalid length.
|
||||||
|
slider.SnapTo(snapProvider);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
||||||
|
|
||||||
pathVersion.BindTo(HitObject.Path.Version);
|
pathVersion.BindTo(HitObject.Path.Version);
|
||||||
pathVersion.BindValueChanged(_ => updatePath());
|
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
|
||||||
|
|
||||||
BodyPiece.UpdateFrom(HitObject);
|
BodyPiece.UpdateFrom(HitObject);
|
||||||
}
|
}
|
||||||
@ -208,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// Move the control points from the insertion index onwards to make room for the insertion
|
// Move the control points from the insertion index onwards to make room for the insertion
|
||||||
controlPoints.Insert(insertionIndex, pathControlPoint);
|
controlPoints.Insert(insertionIndex, pathControlPoint);
|
||||||
|
|
||||||
|
HitObject.SnapTo(composer);
|
||||||
|
|
||||||
return pathControlPoint;
|
return pathControlPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +229,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
controlPoints.Remove(c);
|
controlPoints.Remove(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
// Snap the slider to the current beat divisor before checking length validity.
|
||||||
|
HitObject.SnapTo(composer);
|
||||||
|
|
||||||
|
// If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
|
||||||
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
||||||
{
|
{
|
||||||
placementHandler?.Delete(HitObject);
|
placementHandler?.Delete(HitObject);
|
||||||
@ -242,12 +247,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
HitObject.Position += first;
|
HitObject.Position += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePath()
|
|
||||||
{
|
|
||||||
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
|
||||||
editorBeatmap?.Update(HitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void convertToStream()
|
private void convertToStream()
|
||||||
{
|
{
|
||||||
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
|
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -18,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public class OsuSelectionHandler : EditorSelectionHandler
|
public class OsuSelectionHandler : EditorSelectionHandler
|
||||||
{
|
{
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IPositionSnapProvider? positionSnapProvider { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// During a transform, the initial origin is stored so it can be used throughout the operation.
|
/// During a transform, the initial origin is stored so it can be used throughout the operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -27,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// During a transform, the initial path types of a single selected slider are stored so they
|
/// During a transform, the initial path types of a single selected slider are stored so they
|
||||||
/// can be maintained throughout the operation.
|
/// can be maintained throughout the operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<PathType?> referencePathTypes;
|
private List<PathType?>? referencePathTypes;
|
||||||
|
|
||||||
protected override void OnSelectionChanged()
|
protected override void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
@ -197,6 +204,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
||||||
slider.Path.ControlPoints[i].Type = referencePathTypes[i];
|
slider.Path.ControlPoints[i].Type = referencePathTypes[i];
|
||||||
|
|
||||||
|
// Snap the slider's length to the current beat divisor
|
||||||
|
// to calculate the final resulting duration / bounding box before the final checks.
|
||||||
|
slider.SnapTo(positionSnapProvider);
|
||||||
|
|
||||||
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
||||||
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
||||||
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
||||||
@ -206,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
point.Position = oldControlPoints.Dequeue();
|
point.Position = oldControlPoints.Dequeue();
|
||||||
|
|
||||||
|
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
|
||||||
|
slider.SnapTo(positionSnapProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
||||||
|
@ -6,18 +6,24 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Storyboards.Drawables;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Background
|
namespace osu.Game.Tests.Visual.Background
|
||||||
@ -129,6 +135,46 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
|
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapBackgroundWithStoryboardClockAlwaysUsesCurrentTrack()
|
||||||
|
{
|
||||||
|
BackgroundScreenBeatmap nestedScreen = null;
|
||||||
|
WorkingBeatmap originalWorking = null;
|
||||||
|
|
||||||
|
setSupporter(true);
|
||||||
|
setSourceMode(BackgroundSource.BeatmapWithStoryboard);
|
||||||
|
|
||||||
|
AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
|
||||||
|
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
|
||||||
|
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard));
|
||||||
|
|
||||||
|
AddStep("start music", () => MusicController.Play());
|
||||||
|
AddUntilStep("storyboard clock running", () => screen.ChildrenOfType<DrawableStoryboard>().SingleOrDefault()?.Clock.IsRunning == true);
|
||||||
|
|
||||||
|
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
|
||||||
|
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
|
||||||
|
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
|
||||||
|
|
||||||
|
// we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
|
||||||
|
AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
|
||||||
|
|
||||||
|
AddStep("stop music", () => MusicController.Stop());
|
||||||
|
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
|
||||||
|
AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
|
||||||
|
AddStep("restart music", () => MusicController.Play());
|
||||||
|
|
||||||
|
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
|
||||||
|
|
||||||
|
AddStep("pop screen back to top level", () => screen.MakeCurrent());
|
||||||
|
|
||||||
|
AddStep("top level screen is current", () => screen.IsCurrentScreen());
|
||||||
|
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
|
||||||
|
AddUntilStep("storyboard clock running", () => screen.ChildrenOfType<DrawableStoryboard>().Single().Clock.IsRunning);
|
||||||
|
|
||||||
|
AddStep("stop music", () => MusicController.Stop());
|
||||||
|
AddStep("restore default beatmap", () => Beatmap.SetDefault());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBackgroundTypeSwitch()
|
public void TestBackgroundTypeSwitch()
|
||||||
{
|
{
|
||||||
@ -198,6 +244,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
});
|
});
|
||||||
|
|
||||||
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
|
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
|
||||||
|
private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio);
|
||||||
|
|
||||||
private class TestBackgroundScreenDefault : BackgroundScreenDefault
|
private class TestBackgroundScreenDefault : BackgroundScreenDefault
|
||||||
{
|
{
|
||||||
@ -233,6 +280,51 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
protected override Texture GetBackground() => new Texture(1, 1);
|
protected override Texture GetBackground() => new Texture(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
|
||||||
|
{
|
||||||
|
public TestWorkingBeatmapWithStoryboard(AudioManager audioManager)
|
||||||
|
: base(new Beatmap(), createStoryboard(), audioManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Track GetBeatmapTrack() => new TrackVirtual(100000);
|
||||||
|
|
||||||
|
private static Storyboard createStoryboard()
|
||||||
|
{
|
||||||
|
var storyboard = new Storyboard();
|
||||||
|
storyboard.Layers.Last().Add(new TestStoryboardElement());
|
||||||
|
return storyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestStoryboardElement : IStoryboardElementWithDuration
|
||||||
|
{
|
||||||
|
public string Path => string.Empty;
|
||||||
|
public bool IsDrawable => true;
|
||||||
|
public double StartTime => double.MinValue;
|
||||||
|
public double EndTime => double.MaxValue;
|
||||||
|
|
||||||
|
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DrawableTestStoryboardElement : OsuSpriteText
|
||||||
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
|
||||||
|
public DrawableTestStoryboardElement()
|
||||||
|
{
|
||||||
|
Anchor = Origin = Anchor.Centre;
|
||||||
|
Font = OsuFont.Default.With(size: 32);
|
||||||
|
Text = "(not started)";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
Text = Time.Current.ToString("N2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setCustomSkin()
|
private void setCustomSkin()
|
||||||
{
|
{
|
||||||
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
|
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
|
||||||
|
@ -36,7 +36,7 @@ using osuTK.Graphics;
|
|||||||
namespace osu.Game.Tests.Visual.Background
|
namespace osu.Game.Tests.Visual.Background
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene
|
public class TestSceneUserDimBackgrounds : ScreenTestScene
|
||||||
{
|
{
|
||||||
private DummySongSelect songSelect;
|
private DummySongSelect songSelect;
|
||||||
private TestPlayerLoader playerLoader;
|
private TestPlayerLoader playerLoader;
|
||||||
@ -56,14 +56,12 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
public override void SetUpSteps()
|
||||||
public virtual void SetUp() => Schedule(() =>
|
|
||||||
{
|
{
|
||||||
var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
|
base.SetUpSteps();
|
||||||
Child = stack;
|
|
||||||
|
|
||||||
stack.Push(songSelect = new DummySongSelect());
|
AddStep("push song select", () => Stack.Push(songSelect = new DummySongSelect()));
|
||||||
});
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User settings should always be ignored on song select screen.
|
/// User settings should always be ignored on song select screen.
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
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.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Backgrounds
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
{
|
{
|
||||||
public class BeatmapBackgroundWithStoryboard : BeatmapBackground
|
public class BeatmapBackgroundWithStoryboard : BeatmapBackground
|
||||||
{
|
{
|
||||||
|
private readonly InterpolatingFramedClock storyboardClock;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private MusicController? musicController { get; set; }
|
||||||
|
|
||||||
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
|
||||||
: base(beatmap, fallbackTextureName)
|
: base(beatmap, fallbackTextureName)
|
||||||
{
|
{
|
||||||
|
storyboardClock = new InterpolatingFramedClock();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -30,8 +39,40 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Volume = { Value = 0 },
|
Volume = { Value = 0 },
|
||||||
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) }
|
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock }
|
||||||
}, AddInternal);
|
}, AddInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
if (musicController != null)
|
||||||
|
musicController.TrackChanged += onTrackChanged;
|
||||||
|
|
||||||
|
updateStoryboardClockSource(Beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTrackChanged(WorkingBeatmap newBeatmap, TrackChangeDirection _) => updateStoryboardClockSource(newBeatmap);
|
||||||
|
|
||||||
|
private void updateStoryboardClockSource(WorkingBeatmap newBeatmap)
|
||||||
|
{
|
||||||
|
if (newBeatmap != Beatmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// `MusicController` will sometimes reload the track, even when the working beatmap technically hasn't changed.
|
||||||
|
// ensure that the storyboard's clock is always using the latest track instance.
|
||||||
|
storyboardClock.ChangeSource(newBeatmap.Track);
|
||||||
|
// more often than not, the previous source track's time will be in the future relative to the new source track.
|
||||||
|
// explicitly process a single frame so that `InterpolatingFramedClock`'s interpolation logic is bypassed
|
||||||
|
// and the storyboard clock is correctly rewound to the source track's time exactly.
|
||||||
|
storyboardClock.ProcessFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
if (musicController != null)
|
||||||
|
musicController.TrackChanged -= onTrackChanged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
|
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Compact realm"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Configuration;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
||||||
@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
|||||||
protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
|
protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(FrameworkDebugConfigManager config, GameHost host)
|
private void load(FrameworkDebugConfigManager config, GameHost host, RealmContextFactory realmFactory)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -24,6 +25,17 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
|||||||
Text = DebugSettingsStrings.ClearAllCaches,
|
Text = DebugSettingsStrings.ClearAllCaches,
|
||||||
Action = host.Collect
|
Action = host.Collect
|
||||||
},
|
},
|
||||||
|
new SettingsButton
|
||||||
|
{
|
||||||
|
Text = DebugSettingsStrings.CompactRealm,
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
// Blocking operations implicitly causes a Compact().
|
||||||
|
using (realmFactory.BlockAllOperations())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -11,6 +12,15 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
public static class SliderPathExtensions
|
public static class SliderPathExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Snaps the provided <paramref name="hitObject"/>'s duration using the <paramref name="snapProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static void SnapTo<THitObject>(this THitObject hitObject, IPositionSnapProvider? snapProvider)
|
||||||
|
where THitObject : HitObject, IHasPath
|
||||||
|
{
|
||||||
|
hitObject.Path.ExpectedDistance.Value = snapProvider?.GetSnappedDistanceFromDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reverse the direction of this path.
|
/// Reverse the direction of this path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
if (result == null)
|
if (result == null)
|
||||||
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||||
|
|
||||||
result.Type = judgement.MaxResult;
|
result.Type = GetSimulatedHitResult(judgement);
|
||||||
ApplyResult(result);
|
ApplyResult(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,5 +145,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
base.Update();
|
base.Update();
|
||||||
hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime);
|
hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a simulated <see cref="HitResult"/> for a judgement. Used during <see cref="SimulateAutoplay"/> to simulate a "perfect" play.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="judgement">The judgement to simulate a <see cref="HitResult"/> for.</param>
|
||||||
|
/// <returns>The simulated <see cref="HitResult"/> for the judgement.</returns>
|
||||||
|
protected virtual HitResult GetSimulatedHitResult(Judgement judgement) => judgement.MaxResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,17 @@ namespace osu.Game.Rulesets.UI
|
|||||||
return base.Handle(e);
|
return base.Handle(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
|
||||||
|
{
|
||||||
|
if (mouseDisabled.Value)
|
||||||
|
{
|
||||||
|
// Only propagate positional data when mouse buttons are disabled.
|
||||||
|
e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, e.LastPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.HandleMouseTouchStateChange(e);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Key Counter Attachment
|
#region Key Counter Attachment
|
||||||
|
@ -116,25 +116,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
if (RelativeScaleBeatLengths)
|
if (RelativeScaleBeatLengths)
|
||||||
{
|
{
|
||||||
IReadOnlyList<TimingControlPoint> timingPoints = Beatmap.ControlPointInfo.TimingPoints;
|
baseBeatLength = Beatmap.GetMostCommonBeatLength();
|
||||||
double maxDuration = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < timingPoints.Count; i++)
|
// The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths
|
||||||
{
|
// the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here
|
||||||
if (timingPoints[i].Time > lastObjectTime)
|
baseBeatLength /= Beatmap.Difficulty.SliderMultiplier;
|
||||||
break;
|
|
||||||
|
|
||||||
double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime;
|
|
||||||
double duration = endTime - timingPoints[i].Time;
|
|
||||||
|
|
||||||
if (duration > maxDuration)
|
|
||||||
{
|
|
||||||
maxDuration = duration;
|
|
||||||
// The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths
|
|
||||||
// the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here
|
|
||||||
baseBeatLength = timingPoints[i].BeatLength / Beatmap.Difficulty.SliderMultiplier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point
|
// Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.PlayerSettings
|
namespace osu.Game.Screens.Play.PlayerSettings
|
||||||
{
|
{
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
mouseButtonsCheckbox = new PlayerCheckbox
|
mouseButtonsCheckbox = new PlayerCheckbox
|
||||||
{
|
{
|
||||||
LabelText = "Disable mouse buttons"
|
LabelText = MouseSettingsStrings.DisableMouseButtons
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -210,13 +210,13 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
using (var realm = ContextFactory.CreateContext())
|
using (var realm = ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var skinsWithoutHashes = realm.All<SkinInfo>().Where(i => string.IsNullOrEmpty(i.Hash)).ToArray();
|
var skinsWithoutHashes = realm.All<SkinInfo>().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray();
|
||||||
|
|
||||||
foreach (SkinInfo skin in skinsWithoutHashes)
|
foreach (SkinInfo skin in skinsWithoutHashes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Update(skin);
|
realm.Write(r => skin.Hash = ComputeHash(skin));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user