1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-06 01:43:01 +08:00

Merge branch 'master' into taiko-drum-refacor

This commit is contained in:
Dean Herbert 2021-08-30 14:12:52 +09:00
commit 4dd60e3299
55 changed files with 737 additions and 304 deletions

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.822.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.828.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -210,9 +210,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
new Vector2(50, 200), new Vector2(50, 200),
}), 0.5); }), 0.5);
AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count); AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count);
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type.Value == PathType.PerfectCurve); AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve);
addAddVertexSteps(150, 150); addAddVertexSteps(150, 150);
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type.Value == PathType.Linear); AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear);
} }
private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () => private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () =>

View File

@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests
} while (rng.Next(2) != 0); } while (rng.Next(2) != 0);
int length = sliderPath.ControlPoints.Count - start + 1; int length = sliderPath.ControlPoints.Count - start + 1;
sliderPath.ControlPoints[start].Type.Value = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier; sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier;
} while (rng.Next(3) != 0); } while (rng.Next(3) != 0);
if (rng.Next(5) == 0) if (rng.Next(5) == 0)
@ -210,13 +210,13 @@ namespace osu.Game.Rulesets.Catch.Tests
path.ConvertToSliderPath(sliderPath, sliderStartY); path.ConvertToSliderPath(sliderPath, sliderStartY);
Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3)); Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3));
Assert.That(sliderPath.ControlPoints[0].Position.Value.X, Is.EqualTo(path.Vertices[0].X)); Assert.That(sliderPath.ControlPoints[0].Position.X, Is.EqualTo(path.Vertices[0].X));
assertInvariants(path.Vertices, true); assertInvariants(path.Vertices, true);
foreach (var point in sliderPath.ControlPoints) foreach (var point in sliderPath.ControlPoints)
{ {
Assert.That(point.Type.Value, Is.EqualTo(PathType.Linear).Or.Null); Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null);
Assert.That(sliderStartY + point.Position.Value.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT)); Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
} }
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)

View File

@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream: case JuiceStream juiceStream:
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead. // Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X; lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.X;
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead. // Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
lastStartTime = juiceStream.StartTime; lastStartTime = juiceStream.StartTime;

View File

@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
path.ConvertFromSliderPath(sliderPath); path.ConvertFromSliderPath(sliderPath);
// If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices. // If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices.
if (sliderPath.ControlPoints.Any(p => p.Type.Value != null && p.Type.Value != PathType.Linear)) if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear))
{ {
path.ResampleVertices(hitObject.NestedHitObjects path.ResampleVertices(hitObject.NestedHitObjects
.Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used. .Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used.

View File

@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Catch.Edit
juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX); juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX);
foreach (var point in juiceStream.Path.ControlPoints) foreach (var point in juiceStream.Path.ControlPoints)
point.Position.Value *= new Vector2(-1, 1); point.Position *= new Vector2(-1, 1);
EditorBeatmap.Update(juiceStream); EditorBeatmap.Update(juiceStream);
return true; return true;

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Catch.Mods
/// </summary> /// </summary>
private static void mirrorJuiceStreamPath(JuiceStream juiceStream) private static void mirrorJuiceStreamPath(JuiceStream juiceStream)
{ {
var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
foreach (var point in controlPoints) foreach (var point in controlPoints)
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); point.Position = new Vector2(-point.Position.X, point.Position.Y);
juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value); juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value);
} }

View File

@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Objects
if (value != null) if (value != null)
{ {
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value))); path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value; path.ExpectedDistance.Value = value.ExpectedDistance.Value;
} }
} }

View File

@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Catch.Objects
for (int i = 1; i < vertices.Count; i++) for (int i = 1; i < vertices.Count; i++)
{ {
sliderPath.ControlPoints[^1].Type.Value = PathType.Linear; sliderPath.ControlPoints[^1].Type = PathType.Linear;
float deltaX = vertices[i].X - lastPosition.X; float deltaX = vertices[i].X - lastPosition.X;
double length = vertices[i].Distance - currentDistance; double length = vertices[i].Distance - currentDistance;

View File

@ -0,0 +1,64 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Editor
{
public class TestSceneManiaComposeScreen : EditorClockTestScene
{
[Resolved]
private SkinManager skins { get; set; }
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup compose screen", () =>
{
var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
};
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(EditorBeatmap), editorBeatmap),
(typeof(IBeatSnapProvider), editorBeatmap),
},
Child = new ComposeScreen { State = { Value = Visibility.Visible } },
};
});
AddUntilStep("wait for composer", () => this.ChildrenOfType<HitObjectComposer>().SingleOrDefault()?.IsLoaded == true);
}
[Test]
public void TestDefaultSkin()
{
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = SkinInfo.Default);
}
[Test]
public void TestLegacySkin()
{
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info);
}
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("last connection displayed", () => AddAssert("last connection displayed", () =>
{ {
var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position.Value == new Vector2(300)); var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position == new Vector2(300));
return lastConnection.DrawWidth > 50; return lastConnection.DrawWidth > 50;
}); });
} }
@ -166,14 +166,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
AddStep($"move mouse to control point {index}", () => AddStep($"move mouse to control point {index}", () =>
{ {
Vector2 position = slider.Path.ControlPoints[index].Position.Value; Vector2 position = slider.Path.ControlPoints[index].Position;
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position)); InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
}); });
} }
private void assertControlPointPathType(int controlPointIndex, PathType? type) private void assertControlPointPathType(int controlPointIndex, PathType? type)
{ {
AddAssert($"point {controlPointIndex} is {type}", () => slider.Path.ControlPoints[controlPointIndex].Type.Value == type); AddAssert($"point {controlPointIndex} is {type}", () => slider.Path.ControlPoints[controlPointIndex].Type == type);
} }
private void addContextMenuItemStep(string contextMenuText) private void addContextMenuItemStep(string contextMenuText)

View File

@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Test] [Test]
public void TestDragControlPointPathAfterChangingType() public void TestDragControlPointPathAfterChangingType()
{ {
AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type.Value = PathType.Bezier); AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.Bezier);
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type.Value = PathType.PerfectCurve); AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PerfectCurve);
moveMouseToControlPoint(4); moveMouseToControlPoint(4);
AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
@ -137,15 +137,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
AddStep($"move mouse to control point {index}", () => AddStep($"move mouse to control point {index}", () =>
{ {
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value; Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position;
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
}); });
} }
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type.Value == type); private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type == type);
private void assertControlPointPosition(int index, Vector2 position) => private void assertControlPointPosition(int index, Vector2 position) =>
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position.Value, 1)); AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position, 1));
private class TestSliderBlueprint : SliderSelectionBlueprint private class TestSliderBlueprint : SliderSelectionBlueprint
{ {

View File

@ -385,10 +385,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected); private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected);
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type.Value == type); private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type == type);
private void assertControlPointPosition(int index, Vector2 position) => private void assertControlPointPosition(int index, Vector2 position) =>
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position.Value, 1)); AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position, 1));
private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;

View File

@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
AddStep($"move mouse to control point {index}", () => AddStep($"move mouse to control point {index}", () =>
{ {
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value; Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position;
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
}); });
} }

View File

@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
private void updateConnectingPath() private void updateConnectingPath()
{ {
Position = slider.StackedPosition + ControlPoint.Position.Value; Position = slider.StackedPosition + ControlPoint.Position;
path.ClearVertices(); path.ClearVertices();
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return; return;
path.AddVertex(Vector2.Zero); path.AddVertex(Vector2.Zero);
path.AddVertex(slider.Path.ControlPoints[nextIndex].Position.Value - ControlPoint.Position.Value); path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
} }

View File

@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private IBindable<Vector2> sliderPosition; private IBindable<Vector2> sliderPosition;
private IBindable<float> sliderScale; private IBindable<float> sliderScale;
private IBindable<Vector2> controlPointPosition;
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
{ {
@ -69,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
updatePathType(); updatePathType();
}); });
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay()); controlPoint.Changed += updateMarkerDisplay;
Origin = Anchor.Centre; Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -117,9 +116,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
sliderPosition = slider.PositionBindable.GetBoundCopy(); sliderPosition = slider.PositionBindable.GetBoundCopy();
sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
controlPointPosition = ControlPoint.Position.GetBoundCopy();
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
sliderScale = slider.ScaleBindable.GetBoundCopy(); sliderScale = slider.ScaleBindable.GetBoundCopy();
sliderScale.BindValueChanged(_ => updateMarkerDisplay()); sliderScale.BindValueChanged(_ => updateMarkerDisplay());
@ -174,8 +170,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (e.Button == MouseButton.Left) if (e.Button == MouseButton.Left)
{ {
dragStartPosition = ControlPoint.Position.Value; dragStartPosition = ControlPoint.Position;
dragPathType = PointsInSegment[0].Type.Value; dragPathType = PointsInSegment[0].Type;
changeHandler?.BeginChange(); changeHandler?.BeginChange();
return true; return true;
@ -186,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDrag(DragEvent e) protected override void OnDrag(DragEvent e)
{ {
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position.Value).ToArray(); Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
var oldPosition = slider.Position; var oldPosition = slider.Position;
var oldStartTime = slider.StartTime; var oldStartTime = slider.StartTime;
@ -202,15 +198,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
for (int i = 1; i < slider.Path.ControlPoints.Count; i++) for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position.Value -= movementDelta; slider.Path.ControlPoints[i].Position -= movementDelta;
} }
else else
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition); ControlPoint.Position = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
if (!slider.Path.HasValidLength) if (!slider.Path.HasValidLength)
{ {
for (var i = 0; i < slider.Path.ControlPoints.Count; i++) for (var i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position.Value = oldControlPoints[i]; slider.Path.ControlPoints[i].Position = oldControlPoints[i];
slider.Position = oldPosition; slider.Position = oldPosition;
slider.StartTime = oldStartTime; slider.StartTime = oldStartTime;
@ -218,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
} }
// Maintain the path type in case it got defaulted to bezier at some point during the drag. // Maintain the path type in case it got defaulted to bezier at some point during the drag.
PointsInSegment[0].Type.Value = dragPathType; PointsInSegment[0].Type = dragPathType;
} }
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
@ -230,19 +226,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
private void updatePathType() private void updatePathType()
{ {
if (ControlPoint.Type.Value != PathType.PerfectCurve) if (ControlPoint.Type != PathType.PerfectCurve)
return; return;
if (PointsInSegment.Count > 3) if (PointsInSegment.Count > 3)
ControlPoint.Type.Value = PathType.Bezier; ControlPoint.Type = PathType.Bezier;
if (PointsInSegment.Count != 3) if (PointsInSegment.Count != 3)
return; return;
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position.Value).ToArray(); ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray();
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
if (boundingBox.Width >= 640 || boundingBox.Height >= 480) if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
ControlPoint.Type.Value = PathType.Bezier; ControlPoint.Type = PathType.Bezier;
} }
/// <summary> /// <summary>
@ -250,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
private void updateMarkerDisplay() private void updateMarkerDisplay()
{ {
Position = slider.StackedPosition + ControlPoint.Position.Value; Position = slider.StackedPosition + ControlPoint.Position;
markerRing.Alpha = IsSelected.Value ? 1 : 0; markerRing.Alpha = IsSelected.Value ? 1 : 0;
@ -265,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private Color4 getColourFromNodeType() private Color4 getColourFromNodeType()
{ {
if (!(ControlPoint.Type.Value is PathType pathType)) if (!(ControlPoint.Type is PathType pathType))
return colours.Yellow; return colours.Yellow;
switch (pathType) switch (pathType)
@ -284,6 +280,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
} }
} }
public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; public LocalisableString TooltipText => ControlPoint.Type.ToString() ?? string.Empty;
} }
} }

View File

@ -173,12 +173,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
int thirdPointIndex = indexInSegment + 2; int thirdPointIndex = indexInSegment + 2;
if (piece.PointsInSegment.Count > thirdPointIndex + 1) if (piece.PointsInSegment.Count > thirdPointIndex + 1)
piece.PointsInSegment[thirdPointIndex].Type.Value = piece.PointsInSegment[0].Type.Value; piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type;
break; break;
} }
piece.ControlPoint.Type.Value = type; piece.ControlPoint.Type = type;
} }
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private MenuItem createMenuItemForPathType(PathType? type) private MenuItem createMenuItemForPathType(PathType? type)
{ {
int totalCount = Pieces.Count(p => p.IsSelected.Value); int totalCount = Pieces.Count(p => p.IsSelected.Value);
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type);
var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ => var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ =>
{ {

View File

@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
Debug.Assert(lastPoint != null); Debug.Assert(lastPoint != null);
segmentStart = lastPoint; segmentStart = lastPoint;
segmentStart.Type.Value = PathType.Linear; segmentStart.Type = PathType.Linear;
currentSegmentLength = 1; currentSegmentLength = 1;
} }
@ -153,15 +153,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
case 1: case 1:
case 2: case 2:
segmentStart.Type.Value = PathType.Linear; segmentStart.Type = PathType.Linear;
break; break;
case 3: case 3:
segmentStart.Type.Value = PathType.PerfectCurve; segmentStart.Type = PathType.PerfectCurve;
break; break;
default: default:
segmentStart.Type.Value = PathType.Bezier; segmentStart.Type = PathType.Bezier;
break; break;
} }
} }
@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// The cursor does not overlap a previous control point, so it can be added if not already existing. // The cursor does not overlap a previous control point, so it can be added if not already existing.
if (cursor == null) if (cursor == null)
{ {
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } }); HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = Vector2.Zero });
// The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier). // The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier).
currentSegmentLength++; currentSegmentLength++;
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
} }
// Update the cursor position. // Update the cursor position.
cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
} }
else if (cursor != null) else if (cursor != null)
{ {

View File

@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
Debug.Assert(placementControlPointIndex != null); Debug.Assert(placementControlPointIndex != null);
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position; HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position = e.MousePosition - HitObject.Position;
} }
protected override void OnDragEnd(DragEndEvent e) protected override void OnDragEnd(DragEndEvent e)
@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
for (int i = 0; i < controlPoints.Count - 1; i++) for (int i = 0; i < controlPoints.Count - 1; i++)
{ {
float dist = new Line(controlPoints[i].Position.Value, controlPoints[i + 1].Position.Value).DistanceToPoint(position); float dist = new Line(controlPoints[i].Position, controlPoints[i + 1].Position).DistanceToPoint(position);
if (dist < minDistance) if (dist < minDistance)
{ {
@ -192,7 +192,7 @@ 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, new PathControlPoint { Position = { Value = position } }); controlPoints.Insert(insertionIndex, new PathControlPoint { Position = position });
return insertionIndex; return insertionIndex;
} }
@ -207,8 +207,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
// The first control point in the slider must have a type, so take it from the previous "first" one // The first control point in the slider must have a type, so take it from the previous "first" one
// Todo: Should be handled within SliderPath itself // Todo: Should be handled within SliderPath itself
if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type.Value == null) if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type == null)
controlPoints[1].Type.Value = controlPoints[0].Type.Value; controlPoints[1].Type = controlPoints[0].Type;
controlPoints.Remove(c); controlPoints.Remove(c);
} }
@ -222,9 +222,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position // The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0) // So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
Vector2 first = controlPoints[0].Position.Value; Vector2 first = controlPoints[0].Position;
foreach (var c in controlPoints) foreach (var c in controlPoints)
c.Position.Value -= first; c.Position -= first;
HitObject.Position += first; HitObject.Position += first;
} }

View File

@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
foreach (var point in slider.Path.ControlPoints) foreach (var point in slider.Path.ControlPoints)
{ {
point.Position.Value = new Vector2( point.Position = new Vector2(
(direction == Direction.Horizontal ? -1 : 1) * point.Position.Value.X, (direction == Direction.Horizontal ? -1 : 1) * point.Position.X,
(direction == Direction.Vertical ? -1 : 1) * point.Position.Value.Y (direction == Direction.Vertical ? -1 : 1) * point.Position.Y
); );
} }
} }
@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Edit
if (h is IHasPath path) if (h is IHasPath path)
{ {
foreach (var point in path.Path.ControlPoints) foreach (var point in path.Path.ControlPoints)
point.Position.Value = RotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta); point.Position = RotatePointAroundOrigin(point.Position, Vector2.Zero, delta);
} }
} }
@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Edit
private void scaleSlider(Slider slider, Vector2 scale) private void scaleSlider(Slider slider, Vector2 scale)
{ {
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList(); referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList();
Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position));
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
@ -178,13 +178,13 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var point in slider.Path.ControlPoints) foreach (var point in slider.Path.ControlPoints)
{ {
oldControlPoints.Enqueue(point.Position.Value); oldControlPoints.Enqueue(point.Position);
point.Position.Value *= pathRelativeDeltaScale; point.Position *= pathRelativeDeltaScale;
} }
// Maintain the path types in case they were defaulted to bezier at some point during scaling // Maintain the path types in case they were defaulted to bezier at some point during scaling
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.Value = referencePathTypes[i]; slider.Path.ControlPoints[i].Type = referencePathTypes[i];
//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 });
@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit
return; return;
foreach (var point in slider.Path.ControlPoints) foreach (var point in slider.Path.ControlPoints)
point.Position.Value = oldControlPoints.Dequeue(); point.Position = oldControlPoints.Dequeue();
} }
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)

View File

@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.PlayAnimation(); base.PlayAnimation();
if (Result != HitResult.Miss) if (Result != HitResult.Miss)
JudgementText.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); JudgementText.ScaleTo(new Vector2(0.8f, 1)).Then().ScaleTo(new Vector2(1.2f, 1), 1800, Easing.OutQuint);
} }
} }
} }

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects
if (value != null) if (value != null)
{ {
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value))); path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value; path.ExpectedDistance.Value = value.ExpectedDistance.Value;
} }
} }

View File

@ -119,9 +119,9 @@ namespace osu.Game.Rulesets.Osu.Utils
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y)); slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y)); slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
foreach (var point in controlPoints) foreach (var point in controlPoints)
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y); point.Position = new Vector2(-point.Position.X, point.Position.Y);
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
} }
@ -140,9 +140,9 @@ namespace osu.Game.Rulesets.Osu.Utils
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray(); var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
foreach (var point in controlPoints) foreach (var point in controlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y); point.Position = new Vector2(point.Position.X, -point.Position.Y);
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
} }

View File

@ -666,111 +666,111 @@ namespace osu.Game.Tests.Beatmaps.Formats
// Multi-segment // Multi-segment
var first = ((IHasPath)decoded.HitObjects[0]).Path; var first = ((IHasPath)decoded.HitObjects[0]).Path;
Assert.That(first.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(first.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve)); Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve));
Assert.That(first.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
Assert.That(first.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(first.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
Assert.That(first.ControlPoints[2].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier));
Assert.That(first.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(68, 15))); Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15)));
Assert.That(first.ControlPoints[3].Type.Value, Is.EqualTo(null)); Assert.That(first.ControlPoints[3].Type, Is.EqualTo(null));
Assert.That(first.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(259, -132))); Assert.That(first.ControlPoints[4].Position, Is.EqualTo(new Vector2(259, -132)));
Assert.That(first.ControlPoints[4].Type.Value, Is.EqualTo(null)); Assert.That(first.ControlPoints[4].Type, Is.EqualTo(null));
Assert.That(first.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(92, -107))); Assert.That(first.ControlPoints[5].Position, Is.EqualTo(new Vector2(92, -107)));
Assert.That(first.ControlPoints[5].Type.Value, Is.EqualTo(null)); Assert.That(first.ControlPoints[5].Type, Is.EqualTo(null));
// Single-segment // Single-segment
var second = ((IHasPath)decoded.HitObjects[1]).Path; var second = ((IHasPath)decoded.HitObjects[1]).Path;
Assert.That(second.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(second.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve)); Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve));
Assert.That(second.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244))); Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
Assert.That(second.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(second.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3))); Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
Assert.That(second.ControlPoints[2].Type.Value, Is.EqualTo(null)); Assert.That(second.ControlPoints[2].Type, Is.EqualTo(null));
// Implicit multi-segment // Implicit multi-segment
var third = ((IHasPath)decoded.HitObjects[2]).Path; var third = ((IHasPath)decoded.HitObjects[2]).Path;
Assert.That(third.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(third.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(third.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier));
Assert.That(third.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(0, 192))); Assert.That(third.ControlPoints[1].Position, Is.EqualTo(new Vector2(0, 192)));
Assert.That(third.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(224, 192))); Assert.That(third.ControlPoints[2].Position, Is.EqualTo(new Vector2(224, 192)));
Assert.That(third.ControlPoints[2].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[2].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(224, 0))); Assert.That(third.ControlPoints[3].Position, Is.EqualTo(new Vector2(224, 0)));
Assert.That(third.ControlPoints[3].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.Bezier));
Assert.That(third.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(224, -192))); Assert.That(third.ControlPoints[4].Position, Is.EqualTo(new Vector2(224, -192)));
Assert.That(third.ControlPoints[4].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[4].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(480, -192))); Assert.That(third.ControlPoints[5].Position, Is.EqualTo(new Vector2(480, -192)));
Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[5].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0))); Assert.That(third.ControlPoints[6].Position, Is.EqualTo(new Vector2(480, 0)));
Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[6].Type, Is.EqualTo(null));
// Last control point duplicated // Last control point duplicated
var fourth = ((IHasPath)decoded.HitObjects[3]).Path; var fourth = ((IHasPath)decoded.HitObjects[3]).Path;
Assert.That(fourth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(fourth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(fourth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier));
Assert.That(fourth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1))); Assert.That(fourth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1)));
Assert.That(fourth.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2))); Assert.That(fourth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2)));
Assert.That(fourth.ControlPoints[2].Type.Value, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[2].Type, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3))); Assert.That(fourth.ControlPoints[3].Position, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fourth.ControlPoints[3].Type.Value, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[3].Type, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3))); Assert.That(fourth.ControlPoints[4].Position, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fourth.ControlPoints[4].Type.Value, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[4].Type, Is.EqualTo(null));
// Last control point in segment duplicated // Last control point in segment duplicated
var fifth = ((IHasPath)decoded.HitObjects[4]).Path; var fifth = ((IHasPath)decoded.HitObjects[4]).Path;
Assert.That(fifth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(fifth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(fifth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier));
Assert.That(fifth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1))); Assert.That(fifth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1)));
Assert.That(fifth.ControlPoints[1].Type.Value, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2))); Assert.That(fifth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2)));
Assert.That(fifth.ControlPoints[2].Type.Value, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[2].Type, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3))); Assert.That(fifth.ControlPoints[3].Position, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fifth.ControlPoints[3].Type.Value, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[3].Type, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3))); Assert.That(fifth.ControlPoints[4].Position, Is.EqualTo(new Vector2(3, 3)));
Assert.That(fifth.ControlPoints[4].Type.Value, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[4].Type, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(4, 4))); Assert.That(fifth.ControlPoints[5].Position, Is.EqualTo(new Vector2(4, 4)));
Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.Bezier));
Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5))); Assert.That(fifth.ControlPoints[6].Position, Is.EqualTo(new Vector2(5, 5)));
Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[6].Type, Is.EqualTo(null));
// Implicit perfect-curve multi-segment(Should convert to bezier to match stable) // Implicit perfect-curve multi-segment(Should convert to bezier to match stable)
var sixth = ((IHasPath)decoded.HitObjects[5]).Path; var sixth = ((IHasPath)decoded.HitObjects[5]).Path;
Assert.That(sixth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(sixth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(sixth.ControlPoints[0].Type.Value == PathType.Bezier); Assert.That(sixth.ControlPoints[0].Type == PathType.Bezier);
Assert.That(sixth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145))); Assert.That(sixth.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145)));
Assert.That(sixth.ControlPoints[1].Type.Value == null); Assert.That(sixth.ControlPoints[1].Type == null);
Assert.That(sixth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75))); Assert.That(sixth.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75)));
Assert.That(sixth.ControlPoints[2].Type.Value == PathType.Bezier); Assert.That(sixth.ControlPoints[2].Type == PathType.Bezier);
Assert.That(sixth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145))); Assert.That(sixth.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145)));
Assert.That(sixth.ControlPoints[3].Type.Value == null); Assert.That(sixth.ControlPoints[3].Type == null);
Assert.That(sixth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20))); Assert.That(sixth.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20)));
Assert.That(sixth.ControlPoints[4].Type.Value == null); Assert.That(sixth.ControlPoints[4].Type == null);
// Explicit perfect-curve multi-segment(Should not convert to bezier) // Explicit perfect-curve multi-segment(Should not convert to bezier)
var seventh = ((IHasPath)decoded.HitObjects[6]).Path; var seventh = ((IHasPath)decoded.HitObjects[6]).Path;
Assert.That(seventh.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(seventh.ControlPoints[0].Type.Value == PathType.PerfectCurve); Assert.That(seventh.ControlPoints[0].Type == PathType.PerfectCurve);
Assert.That(seventh.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145))); Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145)));
Assert.That(seventh.ControlPoints[1].Type.Value == null); Assert.That(seventh.ControlPoints[1].Type == null);
Assert.That(seventh.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75))); Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75)));
Assert.That(seventh.ControlPoints[2].Type.Value == PathType.PerfectCurve); Assert.That(seventh.ControlPoints[2].Type == PathType.PerfectCurve);
Assert.That(seventh.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145))); Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145)));
Assert.That(seventh.ControlPoints[3].Type.Value == null); Assert.That(seventh.ControlPoints[3].Type == null);
Assert.That(seventh.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20))); Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20)));
Assert.That(seventh.ControlPoints[4].Type.Value == null); Assert.That(seventh.ControlPoints[4].Type == null);
} }
} }
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
@ -30,7 +31,10 @@ namespace osu.Game.Tests.Visual.Editing
{ {
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new ComposeScreen(); Child = new ComposeScreen
{
State = { Value = Visibility.Visible },
};
} }
} }
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
@ -26,7 +27,11 @@ namespace osu.Game.Tests.Visual.Editing
private void load() private void load()
{ {
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new SetupScreen();
Child = new SetupScreen
{
State = { Value = Visibility.Visible },
};
} }
} }
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
@ -30,7 +31,10 @@ namespace osu.Game.Tests.Visual.Editing
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Beatmap.Disabled = true; Beatmap.Disabled = true;
Child = new TimingScreen(); Child = new TimingScreen
{
State = { Value = Visibility.Visible },
};
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -74,14 +74,14 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestAddControlPoint() public void TestAddControlPoint()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100))));
AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = { Value = new Vector2(100) } })); AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = new Vector2(100) }));
} }
[Test] [Test]
public void TestInsertControlPoint() public void TestInsertControlPoint()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100))));
AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = { Value = new Vector2(0, 100) } })); AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = new Vector2(0, 100) }));
} }
[Test] [Test]
@ -95,14 +95,14 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestChangePathType() public void TestChangePathType()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("change type to bezier", () => path.ControlPoints[0].Type.Value = PathType.Bezier); AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.Bezier);
} }
[Test] [Test]
public void TestAddSegmentByChangingType() public void TestAddSegmentByChangingType()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))));
AddStep("change second point type to bezier", () => path.ControlPoints[1].Type.Value = PathType.Bezier); AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.Bezier);
} }
[Test] [Test]
@ -111,10 +111,10 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
path.ControlPoints[1].Type.Value = PathType.Bezier; path.ControlPoints[1].Type = PathType.Bezier;
}); });
AddStep("change second point type to null", () => path.ControlPoints[1].Type.Value = null); AddStep("change second point type to null", () => path.ControlPoints[1].Type = null);
} }
[Test] [Test]
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
path.ControlPoints[1].Type.Value = PathType.Bezier; path.ControlPoints[1].Type = PathType.Bezier;
}); });
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
@ -185,8 +185,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private List<PathControlPoint> createSegment(PathType type, params Vector2[] controlPoints) private List<PathControlPoint> createSegment(PathType type, params Vector2[] controlPoints)
{ {
var points = controlPoints.Select(p => new PathControlPoint { Position = { Value = p } }).ToList(); var points = controlPoints.Select(p => new PathControlPoint { Position = p }).ToList();
points[0].Type.Value = type; points[0].Type = type;
return points; return points;
} }
} }

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -19,6 +20,7 @@ using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Users; using osu.Game.Users;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestGeneral() public void TestGeneral()
{ {
int[] userIds = Enumerable.Range(0, 4).Select(i => PLAYER_1_ID + i).ToArray(); int[] userIds = getPlayerIds(4);
start(userIds); start(userIds);
loadSpectateScreen(); loadSpectateScreen();
@ -314,6 +316,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000); AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000);
} }
[Test]
public void TestPlayersLeaveWhileSpectating()
{
start(getPlayerIds(4));
sendFrames(getPlayerIds(4), 300);
loadSpectateScreen();
for (int count = 3; count >= 0; count--)
{
var id = PLAYER_1_ID + count;
end(id);
AddUntilStep($"{id} area grayed", () => getInstance(id).Colour != Color4.White);
AddUntilStep($"{id} score quit set", () => getLeaderboardScore(id).HasQuit.Value);
sendFrames(getPlayerIds(count), 300);
}
Player player = null;
AddStep($"get {PLAYER_1_ID} player instance", () => player = getInstance(PLAYER_1_ID).ChildrenOfType<Player>().Single());
start(new[] { PLAYER_1_ID });
sendFrames(PLAYER_1_ID, 300);
AddAssert($"{PLAYER_1_ID} player instance still same", () => getInstance(PLAYER_1_ID).ChildrenOfType<Player>().Single() == player);
AddAssert($"{PLAYER_1_ID} area still grayed", () => getInstance(PLAYER_1_ID).Colour != Color4.White);
AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value);
}
private void loadSpectateScreen(bool waitForPlayerLoad = true) private void loadSpectateScreen(bool waitForPlayerLoad = true)
{ {
AddStep("load screen", () => AddStep("load screen", () =>
@ -333,14 +365,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
foreach (int id in userIds) foreach (int id in userIds)
{ {
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true); var user = new MultiplayerRoomUser(id)
{
User = new User { Id = id },
};
OnlinePlayDependencies.Client.AddUser(user.User, true);
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
playingUsers.Add(new MultiplayerRoomUser(id));
playingUsers.Add(user);
} }
}); });
} }
private void end(int userId)
{
AddStep($"end play for {userId}", () =>
{
var user = playingUsers.Single(u => u.UserID == userId);
OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull());
SpectatorClient.EndPlay(userId);
playingUsers.Remove(user);
});
}
private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count); private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count);
private void sendFrames(int[] userIds, int count = 10) private void sendFrames(int[] userIds, int count = 10)
@ -374,5 +424,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single(); private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single();
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId); private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.Id == userId);
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
} }
} }

View File

@ -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; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -12,6 +13,7 @@ using osu.Framework.Input;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -94,6 +96,120 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("empty step", () => { }); AddStep("empty step", () => { });
} }
[Test]
public void TestLobbyEvents()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
AddRepeatStep("random stuff happens", performRandomAction, 30);
// ensure we have a handful of players so the ready-up sounds good :9
AddRepeatStep("player joins", addRandomPlayer, 5);
// all ready
AddUntilStep("all players ready", () =>
{
var nextUnready = client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle);
if (nextUnready != null)
client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready);
return client.Room?.Users.All(u => u.State == MultiplayerUserState.Ready) == true;
});
AddStep("unready all players at once", () =>
{
Debug.Assert(client.Room != null);
foreach (var u in client.Room.Users) client.ChangeUserState(u.UserID, MultiplayerUserState.Idle);
});
AddStep("ready all players at once", () =>
{
Debug.Assert(client.Room != null);
foreach (var u in client.Room.Users) client.ChangeUserState(u.UserID, MultiplayerUserState.Ready);
});
}
private void addRandomPlayer()
{
int randomUser = RNG.Next(200000, 500000);
client.AddUser(new User { Id = randomUser, Username = $"user {randomUser}" });
}
private void removeLastUser()
{
User lastUser = client.Room?.Users.Last().User;
if (lastUser == null || lastUser == client.LocalUser?.User)
return;
client.RemoveUser(lastUser);
}
private void kickLastUser()
{
User lastUser = client.Room?.Users.Last().User;
if (lastUser == null || lastUser == client.LocalUser?.User)
return;
client.KickUser(lastUser.Id);
}
private void markNextPlayerReady()
{
var nextUnready = client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle);
if (nextUnready != null)
client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready);
}
private void markNextPlayerIdle()
{
var nextUnready = client.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready);
if (nextUnready != null)
client.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Idle);
}
private void performRandomAction()
{
int eventToPerform = RNG.Next(1, 6);
switch (eventToPerform)
{
case 1:
addRandomPlayer();
break;
case 2:
removeLastUser();
break;
case 3:
kickLastUser();
break;
case 4:
markNextPlayerReady();
break;
case 5:
markNextPlayerIdle();
break;
}
}
[Test] [Test]
public void TestCreateRoomViaKeyboard() public void TestCreateRoomViaKeyboard()
{ {

View File

@ -4,8 +4,10 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Screens.Gameplay; using osu.Game.Tournament.Screens.Gameplay;
using osu.Game.Tournament.Screens.Gameplay.Components; using osu.Game.Tournament.Screens.Gameplay.Components;
@ -16,16 +18,26 @@ namespace osu.Game.Tournament.Tests.Screens
[Cached] [Cached]
private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay { Width = 0.5f }; private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay { Width = 0.5f };
[BackgroundDependencyLoader] [Test]
private void load() public void TestStartupState([Values] TourneyState state)
{ {
Add(new GameplayScreen()); AddStep("set state", () => IPCInfo.State.Value = state);
Add(chat); createScreen();
}
[Test]
public void TestStartupStateNoCurrentMatch([Values] TourneyState state)
{
AddStep("set null current", () => Ladder.CurrentMatch.Value = null);
AddStep("set state", () => IPCInfo.State.Value = state);
createScreen();
} }
[Test] [Test]
public void TestWarmup() public void TestWarmup()
{ {
createScreen();
checkScoreVisibility(false); checkScoreVisibility(false);
toggleWarmup(); toggleWarmup();
@ -35,6 +47,20 @@ namespace osu.Game.Tournament.Tests.Screens
checkScoreVisibility(false); checkScoreVisibility(false);
} }
private void createScreen()
{
AddStep("setup screen", () =>
{
Remove(chat);
Children = new Drawable[]
{
new GameplayScreen(),
chat,
};
});
}
private void checkScoreVisibility(bool visible) private void checkScoreVisibility(bool visible)
=> AddUntilStep($"scores {(visible ? "shown" : "hidden")}", => AddUntilStep($"scores {(visible ? "shown" : "hidden")}",
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0))); () => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));

View File

@ -2,7 +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.Framework.Allocation; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Tournament.Screens.TeamWin; using osu.Game.Tournament.Screens.TeamWin;
@ -10,19 +10,22 @@ namespace osu.Game.Tournament.Tests.Screens
{ {
public class TestSceneTeamWinScreen : TournamentTestScene public class TestSceneTeamWinScreen : TournamentTestScene
{ {
[BackgroundDependencyLoader] [Test]
private void load() public void TestBasic()
{ {
var match = Ladder.CurrentMatch.Value; AddStep("set up match", () =>
{
var match = Ladder.CurrentMatch.Value;
match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals");
match.Completed.Value = true; match.Completed.Value = true;
});
Add(new TeamWinScreen AddStep("create screen", () => Add(new TeamWinScreen
{ {
FillMode = FillMode.Fit, FillMode = FillMode.Fit,
FillAspectRatio = 16 / 9f FillAspectRatio = 16 / 9f
}); }));
} }
} }
} }

View File

@ -19,6 +19,8 @@ namespace osu.Game.Tournament.Tests
{ {
public abstract class TournamentTestScene : OsuTestScene public abstract class TournamentTestScene : OsuTestScene
{ {
private TournamentMatch match;
[Cached] [Cached]
protected LadderInfo Ladder { get; private set; } = new LadderInfo(); protected LadderInfo Ladder { get; private set; } = new LadderInfo();
@ -33,19 +35,23 @@ namespace osu.Game.Tournament.Tests
{ {
Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First();
TournamentMatch match = CreateSampleMatch(); match = CreateSampleMatch();
Ladder.Rounds.Add(match.Round.Value); Ladder.Rounds.Add(match.Round.Value);
Ladder.Matches.Add(match); Ladder.Matches.Add(match);
Ladder.Teams.Add(match.Team1.Value); Ladder.Teams.Add(match.Team1.Value);
Ladder.Teams.Add(match.Team2.Value); Ladder.Teams.Add(match.Team2.Value);
Ladder.CurrentMatch.Value = match;
Ruleset.BindTo(Ladder.Ruleset); Ruleset.BindTo(Ladder.Ruleset);
Dependencies.CacheAs(new StableInfo(storage)); Dependencies.CacheAs(new StableInfo(storage));
} }
[SetUpSteps]
public virtual void SetUpSteps()
{
AddStep("set current match", () => Ladder.CurrentMatch.Value = match);
}
public static TournamentMatch CreateSampleMatch() => new TournamentMatch public static TournamentMatch CreateSampleMatch() => new TournamentMatch
{ {
Team1 = Team1 =

View File

@ -20,6 +20,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
} }
private void matchChanged(ValueChangedEvent<TournamentMatch> match) => private void matchChanged(ValueChangedEvent<TournamentMatch> match) =>
Text.Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round"; Text.Text = match.NewValue?.Round.Value?.Name.Value ?? "Unknown Round";
} }
} }

View File

@ -124,9 +124,6 @@ namespace osu.Game.Tournament.Screens.Gameplay
} }
}); });
State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true);
ladder.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true); ladder.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true);
warmup.BindValueChanged(w => warmup.BindValueChanged(w =>
@ -136,6 +133,14 @@ namespace osu.Game.Tournament.Screens.Gameplay
}, true); }, true);
} }
protected override void LoadComplete()
{
base.LoadComplete();
State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true);
}
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match) protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
{ {
base.CurrentMatchChanged(match); base.CurrentMatchChanged(match);
@ -159,7 +164,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
{ {
if (state.NewValue == TourneyState.Ranking) if (state.NewValue == TourneyState.Ranking)
{ {
if (warmup.Value) return; if (warmup.Value || CurrentMatch.Value == null) return;
if (ipc.Score1.Value > ipc.Score2.Value) if (ipc.Score1.Value > ipc.Score2.Value)
CurrentMatch.Value.Team1Score.Value++; CurrentMatch.Value.Team1Score.Value++;

View File

@ -35,7 +35,7 @@ namespace osu.Game.Audio
{ {
// this is a temporary solution to get around muting ourselves. // this is a temporary solution to get around muting ourselves.
// todo: update this once we have a BackgroundTrackManager or similar. // todo: update this once we have a BackgroundTrackManager or similar.
trackStore = new PreviewTrackStore(audioManager.Mixer, new OnlineStore()); trackStore = new PreviewTrackStore(audioManager.TrackMixer, new OnlineStore());
audio.AddItem(trackStore); audio.AddItem(trackStore);
trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);

View File

@ -323,22 +323,22 @@ namespace osu.Game.Beatmaps.Formats
{ {
PathControlPoint point = pathData.Path.ControlPoints[i]; PathControlPoint point = pathData.Path.ControlPoints[i];
if (point.Type.Value != null) if (point.Type != null)
{ {
// We've reached a new (explicit) segment! // We've reached a new (explicit) segment!
// Explicit segments have a new format in which the type is injected into the middle of the control point string. // Explicit segments have a new format in which the type is injected into the middle of the control point string.
// To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point.
// One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments
bool needsExplicitSegment = point.Type.Value != lastType || point.Type.Value == PathType.PerfectCurve; bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PerfectCurve;
// Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable.
// Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder.
if (i > 1) if (i > 1)
{ {
// We need to use the absolute control point position to determine equality, otherwise floating point issues may arise. // We need to use the absolute control point position to determine equality, otherwise floating point issues may arise.
Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position.Value; Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position;
Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position.Value; Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position;
if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y) if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y)
needsExplicitSegment = true; needsExplicitSegment = true;
@ -346,7 +346,7 @@ namespace osu.Game.Beatmaps.Formats
if (needsExplicitSegment) if (needsExplicitSegment)
{ {
switch (point.Type.Value) switch (point.Type)
{ {
case PathType.Bezier: case PathType.Bezier:
writer.Write("B|"); writer.Write("B|");
@ -365,18 +365,18 @@ namespace osu.Game.Beatmaps.Formats
break; break;
} }
lastType = point.Type.Value; lastType = point.Type;
} }
else else
{ {
// New segment with the same type - duplicate the control point // New segment with the same type - duplicate the control point
writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}|")); writer.Write(FormattableString.Invariant($"{position.X + point.Position.X}:{position.Y + point.Position.Y}|"));
} }
} }
if (i != 0) if (i != 0)
{ {
writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}")); writer.Write(FormattableString.Invariant($"{position.X + point.Position.X}:{position.Y + point.Position.Y}"));
writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ","); writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ",");
} }
} }

View File

@ -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;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -91,7 +92,7 @@ namespace osu.Game.Rulesets.Mods
{ {
// This is required as SettingsItem relies heavily on this bindable for internal use. // This is required as SettingsItem relies heavily on this bindable for internal use.
// The actual update flow is done via the bindable provided in the constructor. // The actual update flow is done via the bindable provided in the constructor.
private readonly BindableWithCurrent<float?> current = new BindableWithCurrent<float?>(); private readonly DifficultyBindableWithCurrent current = new DifficultyBindableWithCurrent();
public Bindable<float?> Current public Bindable<float?> Current
{ {
@ -114,5 +115,30 @@ namespace osu.Game.Rulesets.Mods
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
} }
} }
private class DifficultyBindableWithCurrent : DifficultyBindable, IHasCurrentValue<float?>
{
private Bindable<float?> currentBound;
public Bindable<float?> Current
{
get => this;
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (currentBound != null) UnbindFrom(currentBound);
BindTo(currentBound = value);
}
}
public DifficultyBindableWithCurrent(float? defaultValue = default)
: base(defaultValue)
{
}
protected override Bindable<float?> CreateInstance() => new DifficultyBindableWithCurrent();
}
} }
} }

View File

@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
// The first control point must have a definite type. // The first control point must have a definite type.
vertices[0].Type.Value = type; vertices[0].Type = type;
// A path can have multiple implicit segments of the same type if there are two sequential control points with the same position. // A path can have multiple implicit segments of the same type if there are two sequential control points with the same position.
// To handle such cases, this code may return multiple path segments with the final control point in each segment having a non-null type. // To handle such cases, this code may return multiple path segments with the final control point in each segment having a non-null type.
@ -337,7 +337,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
while (++endIndex < vertices.Length - endPointLength) while (++endIndex < vertices.Length - endPointLength)
{ {
// Keep incrementing while an implicit segment doesn't need to be started // Keep incrementing while an implicit segment doesn't need to be started
if (vertices[endIndex].Position.Value != vertices[endIndex - 1].Position.Value) if (vertices[endIndex].Position != vertices[endIndex - 1].Position)
continue; continue;
// The last control point of each segment is not allowed to start a new implicit segment. // The last control point of each segment is not allowed to start a new implicit segment.
@ -345,7 +345,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
continue; continue;
// Force a type on the last point, and return the current control point set as a segment. // Force a type on the last point, and return the current control point set as a segment.
vertices[endIndex - 1].Type.Value = type; vertices[endIndex - 1].Type = type;
yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex);
// Skip the current control point - as it's the same as the one that's just been returned. // Skip the current control point - as it's the same as the one that's just been returned.
@ -360,11 +360,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
string[] vertexSplit = value.Split(':'); string[] vertexSplit = value.Split(':');
Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos; Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos;
point = new PathControlPoint { Position = { Value = pos } }; point = new PathControlPoint { Position = pos };
} }
static bool isLinear(PathControlPoint[] p) => Precision.AlmostEquals(0, (p[1].Position.Value.Y - p[0].Position.Value.Y) * (p[2].Position.Value.X - p[0].Position.Value.X) static bool isLinear(PathControlPoint[] p) => Precision.AlmostEquals(0, (p[1].Position.Y - p[0].Position.Y) * (p[2].Position.X - p[0].Position.X)
- (p[1].Position.Value.X - p[0].Position.Value.X) * (p[2].Position.Value.Y - p[0].Position.Value.Y)); - (p[1].Position.X - p[0].Position.X) * (p[2].Position.Y - p[0].Position.Y));
} }
private PathControlPoint[] mergePointsLists(List<Memory<PathControlPoint>> controlPointList) private PathControlPoint[] mergePointsLists(List<Memory<PathControlPoint>> controlPointList)

View File

@ -3,7 +3,6 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osuTK; using osuTK;
@ -11,31 +10,55 @@ namespace osu.Game.Rulesets.Objects
{ {
public class PathControlPoint : IEquatable<PathControlPoint> public class PathControlPoint : IEquatable<PathControlPoint>
{ {
private Vector2 position;
/// <summary> /// <summary>
/// The position of this <see cref="PathControlPoint"/>. /// The position of this <see cref="PathControlPoint"/>.
/// </summary> /// </summary>
[JsonProperty] [JsonProperty]
public readonly Bindable<Vector2> Position = new Bindable<Vector2>(); public Vector2 Position
{
get => position;
set
{
if (value == position)
return;
position = value;
Changed?.Invoke();
}
}
private PathType? type;
/// <summary> /// <summary>
/// The type of path segment starting at this <see cref="PathControlPoint"/>. /// The type of path segment starting at this <see cref="PathControlPoint"/>.
/// If null, this <see cref="PathControlPoint"/> will be a part of the previous path segment. /// If null, this <see cref="PathControlPoint"/> will be a part of the previous path segment.
/// </summary> /// </summary>
[JsonProperty] [JsonProperty]
public readonly Bindable<PathType?> Type = new Bindable<PathType?>(); public PathType? Type
{
get => type;
set
{
if (value == type)
return;
type = value;
Changed?.Invoke();
}
}
/// <summary> /// <summary>
/// Invoked when any property of this <see cref="PathControlPoint"/> is changed. /// Invoked when any property of this <see cref="PathControlPoint"/> is changed.
/// </summary> /// </summary>
internal event Action Changed; public event Action Changed;
/// <summary> /// <summary>
/// Creates a new <see cref="PathControlPoint"/>. /// Creates a new <see cref="PathControlPoint"/>.
/// </summary> /// </summary>
public PathControlPoint() public PathControlPoint()
{ {
Position.ValueChanged += _ => Changed?.Invoke();
Type.ValueChanged += _ => Changed?.Invoke();
} }
/// <summary> /// <summary>
@ -46,10 +69,10 @@ namespace osu.Game.Rulesets.Objects
public PathControlPoint(Vector2 position, PathType? type = null) public PathControlPoint(Vector2 position, PathType? type = null)
: this() : this()
{ {
Position.Value = position; Position = position;
Type.Value = type; Type = type;
} }
public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value; public bool Equals(PathControlPoint other) => Position == other?.Position && Type == other.Type;
} }
} }

View File

@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Objects
foreach (PathControlPoint point in ControlPoints) foreach (PathControlPoint point in ControlPoints)
{ {
if (point.Type.Value != null) if (point.Type != null)
{ {
if (!found) if (!found)
pointsInCurrentSegment.Clear(); pointsInCurrentSegment.Clear();
@ -215,18 +215,18 @@ namespace osu.Game.Rulesets.Objects
Vector2[] vertices = new Vector2[ControlPoints.Count]; Vector2[] vertices = new Vector2[ControlPoints.Count];
for (int i = 0; i < ControlPoints.Count; i++) for (int i = 0; i < ControlPoints.Count; i++)
vertices[i] = ControlPoints[i].Position.Value; vertices[i] = ControlPoints[i].Position;
int start = 0; int start = 0;
for (int i = 0; i < ControlPoints.Count; i++) for (int i = 0; i < ControlPoints.Count; i++)
{ {
if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1) if (ControlPoints[i].Type == null && i < ControlPoints.Count - 1)
continue; continue;
// The current vertex ends the segment // The current vertex ends the segment
var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1);
var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear; var segmentType = ControlPoints[start].Type ?? PathType.Linear;
foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType)) foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType))
{ {

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Objects
public static void Reverse(this SliderPath sliderPath, out Vector2 positionalOffset) public static void Reverse(this SliderPath sliderPath, out Vector2 positionalOffset)
{ {
var points = sliderPath.ControlPoints.ToArray(); var points = sliderPath.ControlPoints.ToArray();
positionalOffset = points.Last().Position.Value; positionalOffset = points.Last().Position;
sliderPath.ControlPoints.Clear(); sliderPath.ControlPoints.Clear();
@ -28,17 +28,13 @@ namespace osu.Game.Rulesets.Objects
for (var i = 0; i < points.Length; i++) for (var i = 0; i < points.Length; i++)
{ {
var p = points[i]; var p = points[i];
p.Position.Value -= positionalOffset; p.Position -= positionalOffset;
// propagate types forwards to last null type // propagate types forwards to last null type
if (i == points.Length - 1) if (i == points.Length - 1)
p.Type.Value = lastType; p.Type = lastType;
else if (p.Type.Value != null) else if (p.Type != null)
{ (p.Type, lastType) = (lastType, p.Type);
var newType = p.Type.Value;
p.Type.Value = lastType;
lastType = newType;
}
sliderPath.ControlPoints.Insert(0, p); sliderPath.ControlPoints.Insert(0, p);
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit
private readonly EditorBeatmapSkin? beatmapSkin; private readonly EditorBeatmapSkin? beatmapSkin;
public EditorSkinProvidingContainer(EditorBeatmap editorBeatmap) public EditorSkinProvidingContainer(EditorBeatmap editorBeatmap)
: base(editorBeatmap.PlayableBeatmap.BeatmapInfo.Ruleset.CreateInstance(), editorBeatmap, editorBeatmap.BeatmapSkin) : base(editorBeatmap.PlayableBeatmap.BeatmapInfo.Ruleset.CreateInstance(), editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin)
{ {
beatmapSkin = editorBeatmap.BeatmapSkin; beatmapSkin = editorBeatmap.BeatmapSkin;
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osuTK; using osuTK;
@ -75,6 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
}, },
}, },
}, },
new HoverClickSounds(),
}; };
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Threading;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -35,12 +36,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private IBindable<bool> operationInProgress; private IBindable<bool> operationInProgress;
private Sample sampleReadyCount; private Sample sampleReady;
private Sample sampleReadyAll;
private Sample sampleUnready;
private readonly ButtonWithTrianglesExposed button; private readonly ButtonWithTrianglesExposed button;
private int countReady; private int countReady;
private ScheduledDelegate readySampleDelegate;
public MultiplayerReadyButton() public MultiplayerReadyButton()
{ {
InternalChild = button = new ButtonWithTrianglesExposed InternalChild = button = new ButtonWithTrianglesExposed
@ -54,10 +59,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
{ {
sampleReadyCount = audio.Samples.Get(@"SongSelect/select-difficulty");
operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy(); operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
operationInProgress.BindValueChanged(_ => updateState()); operationInProgress.BindValueChanged(_ => updateState());
sampleReady = audio.Samples.Get(@"Multiplayer/player-ready");
sampleReadyAll = audio.Samples.Get(@"Multiplayer/player-ready-all");
sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready");
} }
protected override void OnRoomUpdated() protected override void OnRoomUpdated()
@ -107,21 +114,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
button.Enabled.Value = enableButton; button.Enabled.Value = enableButton;
if (newCountReady != countReady) if (newCountReady == countReady)
{
countReady = newCountReady;
Scheduler.AddOnce(playSound);
}
}
private void playSound()
{
if (sampleReadyCount == null)
return; return;
var channel = sampleReadyCount.GetChannel(); readySampleDelegate?.Cancel();
channel.Frequency.Value = 0.77f + countReady * 0.06f; readySampleDelegate = Schedule(() =>
channel.Play(); {
if (newCountReady > countReady)
{
if (newCountReady == newCountTotal)
sampleReadyAll?.Play();
else
sampleReady?.Play();
}
else if (newCountReady < countReady)
{
sampleUnready?.Play();
}
countReady = newCountReady;
});
} }
private void updateButtonColour(bool green) private void updateButtonColour(bool green)

View File

@ -3,10 +3,13 @@
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Online.Multiplayer;
using osuTK; using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
@ -15,8 +18,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{ {
private FillFlowContainer<ParticipantPanel> panels; private FillFlowContainer<ParticipantPanel> panels;
private Sample userJoinSample;
private Sample userLeftSample;
private Sample userKickedSample;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(AudioManager audio)
{ {
InternalChild = new OsuContextMenuContainer InternalChild = new OsuContextMenuContainer
{ {
@ -34,6 +41,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
} }
} }
}; };
userJoinSample = audio.Samples.Get(@"Multiplayer/player-joined");
userLeftSample = audio.Samples.Get(@"Multiplayer/player-left");
userKickedSample = audio.Samples.Get(@"Multiplayer/player-kicked");
}
protected override void UserJoined(MultiplayerRoomUser user)
{
base.UserJoined(user);
userJoinSample?.Play();
}
protected override void UserLeft(MultiplayerRoomUser user)
{
base.UserLeft(user);
userLeftSample?.Play();
}
protected override void UserKicked(MultiplayerRoomUser user)
{
base.UserKicked(user);
userKickedSample?.Play();
} }
protected override void OnRoomUpdated() protected override void OnRoomUpdated()

View File

@ -61,7 +61,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
playerClocks.Add(clock); playerClocks.Add(clock);
} }
public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); public void RemovePlayerClock(ISpectatorPlayerClock clock)
{
playerClocks.Remove(clock);
clock.Stop();
}
protected override void Update() protected override void Update()
{ {

View File

@ -8,6 +8,7 @@ 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.Game.Graphics;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator; using osu.Game.Online.Spectator;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary> /// </summary>
public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true);
[Resolved]
private OsuColour colours { get; set; }
[Resolved] [Resolved]
private SpectatorClient spectatorClient { get; set; } private SpectatorClient spectatorClient { get; set; }
@ -215,6 +219,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
protected override void EndGameplay(int userId) protected override void EndGameplay(int userId)
{ {
RemoveUser(userId); RemoveUser(userId);
var instance = instances.Single(i => i.UserId == userId);
instance.FadeColour(colours.Gray4, 400, Easing.OutQuint);
syncManager.RemovePlayerClock(instance.GameplayClock);
leaderboard.RemoveClock(userId); leaderboard.RemoveClock(userId);
} }

View File

@ -12,7 +12,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -37,6 +36,8 @@ namespace osu.Game.Screens.Play
private FadeContainer fadeContainer; private FadeContainer fadeContainer;
private double displayTime; private double displayTime;
private bool isClickable;
[Resolved] [Resolved]
private GameplayClock gameplayClock { get; set; } private GameplayClock gameplayClock { get; set; }
@ -101,7 +102,7 @@ namespace osu.Game.Screens.Play
public override void Show() public override void Show()
{ {
base.Show(); base.Show();
fadeContainer.Show(); fadeContainer.TriggerShow();
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -118,24 +119,27 @@ namespace osu.Game.Screens.Play
button.Action = () => RequestSkip?.Invoke(); button.Action = () => RequestSkip?.Invoke();
displayTime = gameplayClock.CurrentTime; displayTime = gameplayClock.CurrentTime;
fadeContainer.TriggerShow();
} }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
var progress = fadeOutBeginTime <= displayTime ? 1 : Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime)); double progress = fadeOutBeginTime <= displayTime ? 1 : Math.Max(0, 1 - (gameplayClock.CurrentTime - displayTime) / (fadeOutBeginTime - displayTime));
remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
button.Enabled.Value = progress > 0; isClickable = progress > 0;
buttonContainer.State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden; button.Enabled.Value = isClickable;
buttonContainer.State.Value = isClickable ? Visibility.Visible : Visibility.Hidden;
} }
protected override bool OnMouseMove(MouseMoveEvent e) protected override bool OnMouseMove(MouseMoveEvent e)
{ {
if (!e.HasAnyButtonPressed) if (isClickable && !e.HasAnyButtonPressed)
fadeContainer.Show(); fadeContainer.TriggerShow();
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
@ -164,34 +168,45 @@ namespace osu.Game.Screens.Play
public event Action<Visibility> StateChanged; public event Action<Visibility> StateChanged;
private Visibility state; private Visibility state;
private ScheduledDelegate scheduledHide; private double? nextHideTime;
public override bool IsPresent => true; public override bool IsPresent => true;
public void TriggerShow()
{
Show();
if (!IsHovered && !IsDragged)
nextHideTime = Time.Current + 1000;
else
nextHideTime = null;
}
protected override void Update()
{
base.Update();
if (nextHideTime != null && nextHideTime <= Time.Current)
{
Hide();
nextHideTime = null;
}
}
public Visibility State public Visibility State
{ {
get => state; get => state;
set set
{ {
bool stateChanged = value != state; if (value == state)
return;
state = value; state = value;
scheduledHide?.Cancel();
switch (state) switch (state)
{ {
case Visibility.Visible: case Visibility.Visible:
// we may be triggered to become visible multiple times but we only want to transform once. this.FadeIn(500, Easing.OutExpo);
if (stateChanged)
this.FadeIn(500, Easing.OutExpo);
if (!IsHovered && !IsDragged)
{
using (BeginDelayedSequence(1000))
scheduledHide = Schedule(Hide);
}
break; break;
case Visibility.Hidden: case Visibility.Hidden:
@ -212,7 +227,7 @@ namespace osu.Game.Screens.Play
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
Show(); Show();
scheduledHide?.Cancel(); nextHideTime = null;
return true; return true;
} }

View File

@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play
Beatmap.Value = gameplayState.Beatmap; Beatmap.Value = gameplayState.Beatmap;
Ruleset.Value = gameplayState.Ruleset.RulesetInfo; Ruleset.Value = gameplayState.Ruleset.RulesetInfo;
this.Push(new SpectatorPlayerLoader(gameplayState.Score)); this.Push(new SpectatorPlayerLoader(gameplayState.Score, () => new SoloSpectatorPlayer(gameplayState.Score)));
} }
} }

View File

@ -0,0 +1,52 @@
// 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.Screens;
using osu.Game.Online.Spectator;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
{
public class SoloSpectatorPlayer : SpectatorPlayer
{
private readonly Score score;
public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null)
: base(score, configuration)
{
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
{
SpectatorClient.OnUserBeganPlaying += userBeganPlaying;
}
public override bool OnExiting(IScreen next)
{
SpectatorClient.OnUserBeganPlaying -= userBeganPlaying;
return base.OnExiting(next);
}
private void userBeganPlaying(int userId, SpectatorState state)
{
if (userId != score.ScoreInfo.UserID) return;
Schedule(() =>
{
if (this.IsCurrentScreen()) this.Exit();
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (SpectatorClient != null)
SpectatorClient.OnUserBeganPlaying -= userBeganPlaying;
}
}
}

View File

@ -14,16 +14,16 @@ using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
public class SpectatorPlayer : Player public abstract class SpectatorPlayer : Player
{ {
[Resolved] [Resolved]
private SpectatorClient spectatorClient { get; set; } protected SpectatorClient SpectatorClient { get; private set; }
private readonly Score score; private readonly Score score;
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
public SpectatorPlayer(Score score, PlayerConfiguration configuration = null) protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
: base(configuration) : base(configuration)
{ {
this.score = score; this.score = score;
@ -32,8 +32,6 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
spectatorClient.OnUserBeganPlaying += userBeganPlaying;
AddInternal(new OsuSpriteText AddInternal(new OsuSpriteText
{ {
Text = $"Watching {score.ScoreInfo.User.Username} playing live!", Text = $"Watching {score.ScoreInfo.User.Username} playing live!",
@ -50,7 +48,7 @@ namespace osu.Game.Screens.Play
// Start gameplay along with the very first arrival frame (the latest one). // Start gameplay along with the very first arrival frame (the latest one).
score.Replay.Frames.Clear(); score.Replay.Frames.Clear();
spectatorClient.OnNewFrames += userSentFrames; SpectatorClient.OnNewFrames += userSentFrames;
} }
private void userSentFrames(int userId, FrameDataBundle bundle) private void userSentFrames(int userId, FrameDataBundle bundle)
@ -93,31 +91,17 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
{ {
spectatorClient.OnUserBeganPlaying -= userBeganPlaying; SpectatorClient.OnNewFrames -= userSentFrames;
spectatorClient.OnNewFrames -= userSentFrames;
return base.OnExiting(next); return base.OnExiting(next);
} }
private void userBeganPlaying(int userId, SpectatorState state)
{
if (userId != score.ScoreInfo.UserID) return;
Schedule(() =>
{
if (this.IsCurrentScreen()) this.Exit();
});
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
if (spectatorClient != null) if (SpectatorClient != null)
{ SpectatorClient.OnNewFrames -= userSentFrames;
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
spectatorClient.OnNewFrames -= userSentFrames;
}
} }
} }
} }

View File

@ -11,12 +11,7 @@ namespace osu.Game.Screens.Play
{ {
public readonly ScoreInfo Score; public readonly ScoreInfo Score;
public SpectatorPlayerLoader(Score score) public SpectatorPlayerLoader(Score score, Func<SpectatorPlayer> createPlayer)
: this(score, () => new SpectatorPlayer(score))
{
}
public SpectatorPlayerLoader(Score score, Func<Player> createPlayer)
: base(createPlayer) : base(createPlayer)
{ {
if (score.Replay == null) if (score.Replay == null)

View File

@ -36,8 +36,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.828.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.822.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
<PackageReference Include="Sentry" Version="3.8.3" /> <PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />

View File

@ -70,8 +70,8 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.828.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.822.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup> <PropertyGroup>
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.828.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />