mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 20:43:21 +08:00
Merge branch 'master' into osu-target-mod
This commit is contained in:
commit
304eac26d3
@ -27,7 +27,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2021.608.0",
|
"version": "2021.705.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||||
|
|
||||||
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ We are accepting bug reports (please report with as much detail as possible and
|
|||||||
|
|
||||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||||
- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where lazer is currently and the roadmap going forward.
|
- Read peppy's [blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward.
|
||||||
|
|
||||||
## Running osu!
|
## Running osu!
|
||||||
|
|
||||||
|
@ -51,11 +51,11 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.701.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.702.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.714.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. -->
|
||||||
<PackageReference Include="Realm" Version="10.2.1" />
|
<PackageReference Include="Realm" Version="10.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -68,6 +68,8 @@ namespace osu.Desktop.Updater
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleRecheck = false;
|
||||||
|
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
|
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
|
||||||
@ -98,7 +100,6 @@ namespace osu.Desktop.Updater
|
|||||||
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
|
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
|
||||||
// try again without deltas.
|
// try again without deltas.
|
||||||
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
||||||
scheduleRecheck = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -110,6 +111,7 @@ namespace osu.Desktop.Updater
|
|||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||||
|
scheduleRecheck = true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class CatchEditorTestSceneContainer : Container
|
||||||
|
{
|
||||||
|
[Cached(typeof(Playfield))]
|
||||||
|
public readonly ScrollingPlayfield Playfield;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
public CatchEditorTestSceneContainer()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Width = CatchPlayfield.WIDTH;
|
||||||
|
Height = 1000;
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Bottom = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
TimeRange = 1000,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Playfield = new TestCatchPlayfield
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PlayfieldBorder
|
||||||
|
{
|
||||||
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Full },
|
||||||
|
Clock = new FramedClock(new StopwatchClock(true))
|
||||||
|
},
|
||||||
|
Content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestCatchPlayfield : CatchEditorPlayfield
|
||||||
|
{
|
||||||
|
public TestCatchPlayfield()
|
||||||
|
: base(new BeatmapDifficulty { CircleSize = 0 })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public abstract class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected const double TIME_SNAP = 100;
|
||||||
|
|
||||||
|
protected DrawableCatchHitObject LastObject;
|
||||||
|
|
||||||
|
protected new ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => contentContainer;
|
||||||
|
|
||||||
|
private readonly CatchEditorTestSceneContainer contentContainer;
|
||||||
|
|
||||||
|
protected CatchPlacementBlueprintTestScene()
|
||||||
|
{
|
||||||
|
base.Content.Add(contentContainer = new CatchEditorTestSceneContainer());
|
||||||
|
|
||||||
|
contentContainer.Playfield.Clock = new FramedClock(new ManualClock());
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
HitObjectContainer.Clear();
|
||||||
|
ResetPlacement();
|
||||||
|
LastObject = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected void AddMoveStep(double time, float x) => AddStep($"move to time={time}, x={x}", () =>
|
||||||
|
{
|
||||||
|
float y = HitObjectContainer.PositionAtTime(time);
|
||||||
|
Vector2 pos = HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight));
|
||||||
|
InputManager.MoveMouseTo(pos);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected void AddClickStep(MouseButton button) => AddStep($"click {button}", () =>
|
||||||
|
{
|
||||||
|
InputManager.Click(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected IEnumerable<FruitOutline> FruitOutlines => Content.ChildrenOfType<FruitOutline>();
|
||||||
|
|
||||||
|
// Unused because AddHitObject is overriden
|
||||||
|
protected override Container CreateHitObjectContainer() => new Container();
|
||||||
|
|
||||||
|
protected override void AddHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
LastObject = (DrawableCatchHitObject)hitObject;
|
||||||
|
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
||||||
|
{
|
||||||
|
var result = base.SnapForBlueprint(blueprint);
|
||||||
|
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public abstract class CatchSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => contentContainer;
|
||||||
|
|
||||||
|
private readonly CatchEditorTestSceneContainer contentContainer;
|
||||||
|
|
||||||
|
protected CatchSelectionBlueprintTestScene()
|
||||||
|
{
|
||||||
|
base.Content.Add(contentContainer = new CatchEditorTestSceneContainer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneBananaShowerPlacementBlueprint : CatchPlacementBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
|
||||||
|
|
||||||
|
protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
|
|
||||||
|
protected override void AddHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
// Create nested bananas (but positions are not randomized because beatmap processing is not done).
|
||||||
|
hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), Beatmap.Value.BeatmapInfo.BaseDifficulty);
|
||||||
|
|
||||||
|
base.AddHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicPlacement()
|
||||||
|
{
|
||||||
|
const double start_time = 100;
|
||||||
|
const double end_time = 500;
|
||||||
|
|
||||||
|
AddMoveStep(start_time, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddMoveStep(end_time, 0);
|
||||||
|
AddClickStep(MouseButton.Right);
|
||||||
|
AddAssert("banana shower is placed", () => LastObject is DrawableBananaShower);
|
||||||
|
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
||||||
|
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReversePlacement()
|
||||||
|
{
|
||||||
|
const double start_time = 100;
|
||||||
|
const double end_time = 500;
|
||||||
|
|
||||||
|
AddMoveStep(end_time, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddMoveStep(start_time, 0);
|
||||||
|
AddClickStep(MouseButton.Right);
|
||||||
|
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
||||||
|
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFinishWithZeroDuration()
|
||||||
|
{
|
||||||
|
AddMoveStep(100, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddClickStep(MouseButton.Right);
|
||||||
|
AddAssert("banana shower is not placed", () => LastObject == null);
|
||||||
|
AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpacity()
|
||||||
|
{
|
||||||
|
AddMoveStep(100, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha));
|
||||||
|
AddMoveStep(200, 0);
|
||||||
|
AddUntilStep("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1));
|
||||||
|
AddMoveStep(100, 0);
|
||||||
|
AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpanOutline timeSpanOutline => Content.ChildrenOfType<TimeSpanOutline>().Single();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneFruitPlacementBlueprint : CatchPlacementBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
|
||||||
|
|
||||||
|
protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitPlacementPosition()
|
||||||
|
{
|
||||||
|
const double time = 300;
|
||||||
|
const float x = CatchPlayfield.CENTER_X;
|
||||||
|
|
||||||
|
AddMoveStep(time, x);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
AddAssert("outline position is correct", () =>
|
||||||
|
{
|
||||||
|
var outline = FruitOutlines.Single();
|
||||||
|
return Precision.AlmostEquals(outline.X, x) &&
|
||||||
|
Precision.AlmostEquals(outline.Y, HitObjectContainer.PositionAtTime(time));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("fruit time is correct", () => Precision.AlmostEquals(LastObject.StartTimeBindable.Value, time));
|
||||||
|
AddAssert("fruit position is correct", () => Precision.AlmostEquals(LastObject.X, x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneJuiceStreamSelectionBlueprint : CatchSelectionBlueprintTestScene
|
||||||
|
{
|
||||||
|
public TestSceneJuiceStreamSelectionBlueprint()
|
||||||
|
{
|
||||||
|
var hitObject = new JuiceStream
|
||||||
|
{
|
||||||
|
OriginalX = 100,
|
||||||
|
StartTime = 100,
|
||||||
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(200, 100),
|
||||||
|
new Vector2(0, 200),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
var controlPoint = new ControlPointInfo();
|
||||||
|
controlPoint.Add(0, new TimingControlPoint
|
||||||
|
{
|
||||||
|
BeatLength = 100
|
||||||
|
});
|
||||||
|
hitObject.ApplyDefaults(controlPoint, new BeatmapDifficulty { CircleSize = 0 });
|
||||||
|
AddBlueprint(new JuiceStreamSelectionBlueprint(hitObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("finish hyper-dashing", () =>
|
AddStep("finish hyper-dashing", () =>
|
||||||
{
|
{
|
||||||
catcherArea.MovableCatcher.SetHyperDashState(1);
|
catcherArea.MovableCatcher.SetHyperDashState();
|
||||||
catcherArea.MovableCatcher.FinishTransforms();
|
catcherArea.MovableCatcher.FinishTransforms();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
{
|
{
|
||||||
@ -23,5 +24,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
: base(new THitObject())
|
: base(new THitObject())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
public abstract class CatchSelectionBlueprint<THitObject> : HitObjectSelectionBlueprint<THitObject>
|
public abstract class CatchSelectionBlueprint<THitObject> : HitObjectSelectionBlueprint<THitObject>
|
||||||
where THitObject : CatchHitObject
|
where THitObject : CatchHitObject
|
||||||
{
|
{
|
||||||
|
protected override bool AlwaysShowWhenSelected => true;
|
||||||
|
|
||||||
public override Vector2 ScreenSpaceSelectionPoint
|
public override Vector2 ScreenSpaceSelectionPoint
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -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 JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -18,7 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft;
|
Anchor = Anchor.BottomLeft;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS);
|
|
||||||
InternalChild = new BorderPiece();
|
InternalChild = new BorderPiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,10 +28,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
Colour = osuColour.Yellow;
|
Colour = osuColour.Yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
|
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject, [CanBeNull] CatchHitObject parent = null)
|
||||||
{
|
{
|
||||||
X = hitObject.EffectiveX;
|
X = hitObject.EffectiveX - (parent?.OriginalX ?? 0);
|
||||||
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime);
|
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime, parent?.StartTime ?? hitObjectContainer.Time.Current);
|
||||||
Scale = new Vector2(hitObject.Scale);
|
Scale = new Vector2(hitObject.Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class NestedOutlineContainer : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly List<CatchHitObject> nestedHitObjects = new List<CatchHitObject>();
|
||||||
|
|
||||||
|
public NestedOutlineContainer()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
|
||||||
|
{
|
||||||
|
X = parentHitObject.OriginalX;
|
||||||
|
Y = hitObjectContainer.PositionAtTime(parentHitObject.StartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
|
||||||
|
{
|
||||||
|
nestedHitObjects.Clear();
|
||||||
|
nestedHitObjects.AddRange(parentHitObject.NestedHitObjects
|
||||||
|
.OfType<CatchHitObject>()
|
||||||
|
.Where(h => !(h is TinyDroplet)));
|
||||||
|
|
||||||
|
while (nestedHitObjects.Count < InternalChildren.Count)
|
||||||
|
RemoveInternal(InternalChildren[^1]);
|
||||||
|
|
||||||
|
while (InternalChildren.Count < nestedHitObjects.Count)
|
||||||
|
AddInternal(new FruitOutline());
|
||||||
|
|
||||||
|
for (int i = 0; i < nestedHitObjects.Count; i++)
|
||||||
|
{
|
||||||
|
var hitObject = nestedHitObjects[i];
|
||||||
|
var outline = (FruitOutline)InternalChildren[i];
|
||||||
|
outline.UpdateFrom(hitObjectContainer, hitObject, parentHitObject);
|
||||||
|
outline.Scale *= hitObject is Droplet ? 0.5f : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Lines;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class ScrollingPath : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Path drawablePath;
|
||||||
|
|
||||||
|
private readonly List<(double Distance, float X)> vertices = new List<(double, float)>();
|
||||||
|
|
||||||
|
public ScrollingPath()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
drawablePath = new SmoothPath
|
||||||
|
{
|
||||||
|
PathRadius = 2,
|
||||||
|
Alpha = 0.5f
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePositionFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
|
||||||
|
{
|
||||||
|
X = hitObject.OriginalX;
|
||||||
|
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject)
|
||||||
|
{
|
||||||
|
double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity);
|
||||||
|
|
||||||
|
computeDistanceXs(hitObject);
|
||||||
|
drawablePath.Vertices = vertices
|
||||||
|
.Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor)))
|
||||||
|
.ToArray();
|
||||||
|
drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeDistanceXs(JuiceStream hitObject)
|
||||||
|
{
|
||||||
|
vertices.Clear();
|
||||||
|
|
||||||
|
var sliderVertices = new List<Vector2>();
|
||||||
|
hitObject.Path.GetPathToProgress(sliderVertices, 0, 1);
|
||||||
|
|
||||||
|
if (sliderVertices.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double distance = 0;
|
||||||
|
Vector2 lastPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++)
|
||||||
|
{
|
||||||
|
foreach (var position in sliderVertices)
|
||||||
|
{
|
||||||
|
distance += Vector2.Distance(lastPosition, position);
|
||||||
|
lastPosition = position;
|
||||||
|
|
||||||
|
vertices.Add((distance, position.X));
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderVertices.Reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because this has 0x0 size, the contents are otherwise masked away if the start position is outside the screen.
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Caching;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -17,9 +20,20 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
private float minNestedX;
|
private float minNestedX;
|
||||||
private float maxNestedX;
|
private float maxNestedX;
|
||||||
|
|
||||||
|
private readonly ScrollingPath scrollingPath;
|
||||||
|
|
||||||
|
private readonly NestedOutlineContainer nestedOutlineContainer;
|
||||||
|
|
||||||
|
private readonly Cached pathCache = new Cached();
|
||||||
|
|
||||||
public JuiceStreamSelectionBlueprint(JuiceStream hitObject)
|
public JuiceStreamSelectionBlueprint(JuiceStream hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
scrollingPath = new ScrollingPath(),
|
||||||
|
nestedOutlineContainer = new NestedOutlineContainer()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -29,7 +43,28 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
computeObjectBounds();
|
computeObjectBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDefaultsApplied(HitObject _) => computeObjectBounds();
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!IsSelected) return;
|
||||||
|
|
||||||
|
scrollingPath.UpdatePositionFrom(HitObjectContainer, HitObject);
|
||||||
|
nestedOutlineContainer.UpdatePositionFrom(HitObjectContainer, HitObject);
|
||||||
|
|
||||||
|
if (pathCache.IsValid) return;
|
||||||
|
|
||||||
|
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
||||||
|
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
||||||
|
|
||||||
|
pathCache.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDefaultsApplied(HitObject _)
|
||||||
|
{
|
||||||
|
computeObjectBounds();
|
||||||
|
pathCache.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
private void computeObjectBounds()
|
private void computeObjectBounds()
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -20,6 +22,16 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
||||||
new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
|
new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
// 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 System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -24,23 +27,90 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
var blueprint = moveEvent.Blueprint;
|
var blueprint = moveEvent.Blueprint;
|
||||||
Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint);
|
Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint);
|
||||||
Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta);
|
Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta);
|
||||||
|
|
||||||
float deltaX = targetPosition.X - originalPosition.X;
|
float deltaX = targetPosition.X - originalPosition.X;
|
||||||
|
deltaX = limitMovement(deltaX, EditorBeatmap.SelectedHitObjects);
|
||||||
|
|
||||||
|
if (deltaX == 0)
|
||||||
|
{
|
||||||
|
// Even if there is no positional change, there may be a time change.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (!(h is CatchHitObject hitObject)) return;
|
if (!(h is CatchHitObject hitObject)) return;
|
||||||
|
|
||||||
if (hitObject is BananaShower) return;
|
hitObject.OriginalX += deltaX;
|
||||||
|
|
||||||
// TODO: confine in bounds
|
|
||||||
hitObject.OriginalXBindable.Value += deltaX;
|
|
||||||
|
|
||||||
// Move the nested hit objects to give an instant result before nested objects are recreated.
|
// Move the nested hit objects to give an instant result before nested objects are recreated.
|
||||||
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>())
|
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>())
|
||||||
nested.OriginalXBindable.Value += deltaX;
|
nested.OriginalX += deltaX;
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limit positional movement of the objects by the constraint that moved objects should stay in bounds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deltaX">The positional movement.</param>
|
||||||
|
/// <param name="movingObjects">The objects to be moved.</param>
|
||||||
|
/// <returns>The positional movement with the restriction applied.</returns>
|
||||||
|
private float limitMovement(float deltaX, IEnumerable<HitObject> movingObjects)
|
||||||
|
{
|
||||||
|
float minX = float.PositiveInfinity;
|
||||||
|
float maxX = float.NegativeInfinity;
|
||||||
|
|
||||||
|
foreach (float x in movingObjects.SelectMany(getOriginalPositions))
|
||||||
|
{
|
||||||
|
minX = Math.Min(minX, x);
|
||||||
|
maxX = Math.Max(maxX, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To make an object with position `x` stay in bounds after `deltaX` movement, `0 <= x + deltaX <= WIDTH` should be satisfied.
|
||||||
|
// Subtracting `x`, we get `-x <= deltaX <= WIDTH - x`.
|
||||||
|
// We only need to apply the inequality to extreme values of `x`.
|
||||||
|
float lowerBound = -minX;
|
||||||
|
float upperBound = CatchPlayfield.WIDTH - maxX;
|
||||||
|
// The inequality may be unsatisfiable if the objects were already out of bounds.
|
||||||
|
// In that case, don't move objects at all.
|
||||||
|
if (lowerBound > upperBound)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return Math.Clamp(deltaX, lowerBound, upperBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerate X positions that should be contained in-bounds after move offset is applied.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<float> getOriginalPositions(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Fruit fruit:
|
||||||
|
yield return fruit.OriginalX;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JuiceStream juiceStream:
|
||||||
|
foreach (var nested in juiceStream.NestedHitObjects.OfType<CatchHitObject>())
|
||||||
|
{
|
||||||
|
// Even if `OriginalX` is outside the playfield, tiny droplets can be moved inside the playfield after the random offset application.
|
||||||
|
if (!(nested is TinyDroplet))
|
||||||
|
yield return nested.OriginalX;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BananaShower _:
|
||||||
|
// A banana shower occupies the whole screen width.
|
||||||
|
// If the selection contains a banana shower, the selection cannot be moved horizontally.
|
||||||
|
yield return 0;
|
||||||
|
yield return CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,37 +12,29 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor
|
public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor
|
||||||
{
|
{
|
||||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
|
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Default = 5,
|
ExtendedMaxValue = 11,
|
||||||
Value = 5,
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
|
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Default = 5,
|
ExtendedMaxValue = 11,
|
||||||
Value = 5,
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||||
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
||||||
|
|
||||||
protected override void ApplyLimits(bool extended)
|
|
||||||
{
|
|
||||||
base.ApplyLimits(extended);
|
|
||||||
|
|
||||||
CircleSize.MaxValue = extended ? 11 : 10;
|
|
||||||
ApproachRate.MaxValue = extended ? 11 : 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string SettingDescription
|
public override string SettingDescription
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -61,20 +53,12 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void TransferSettings(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
base.TransferSettings(difficulty);
|
|
||||||
|
|
||||||
TransferSetting(CircleSize, difficulty.CircleSize);
|
|
||||||
TransferSetting(ApproachRate, difficulty.ApproachRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value;
|
||||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
||||||
|
@ -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 Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -20,6 +21,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only setter is exposed.
|
||||||
|
/// Use <see cref="OriginalX"/> or <see cref="EffectiveX"/> to get the horizontal position.
|
||||||
|
/// </remarks>
|
||||||
|
[JsonIgnore]
|
||||||
public float X
|
public float X
|
||||||
{
|
{
|
||||||
set => OriginalXBindable.Value = value;
|
set => OriginalXBindable.Value = value;
|
||||||
@ -34,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float XOffset
|
public float XOffset
|
||||||
{
|
{
|
||||||
|
get => XOffsetBindable.Value;
|
||||||
set => XOffsetBindable.Value = value;
|
set => XOffsetBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +51,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
|
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
|
||||||
/// Use <see cref="EffectiveX"/> for a gameplay.
|
/// Use <see cref="EffectiveX"/> for a gameplay.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float OriginalX => OriginalXBindable.Value;
|
public float OriginalX
|
||||||
|
{
|
||||||
|
get => OriginalXBindable.Value;
|
||||||
|
set => OriginalXBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
@ -53,9 +64,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
||||||
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
|
public float EffectiveX => OriginalX + XOffset;
|
||||||
|
|
||||||
public double TimePreempt = 1000;
|
public double TimePreempt { get; set; } = 1000;
|
||||||
|
|
||||||
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -25,7 +26,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int RepeatCount { get; set; }
|
public int RepeatCount { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public double Velocity { get; private set; }
|
public double Velocity { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public double TickDistance { get; private set; }
|
public double TickDistance { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public double Duration
|
public double Duration
|
||||||
{
|
{
|
||||||
get => this.SpanCount() * Path.Distance / Velocity;
|
get => this.SpanCount() * Path.Distance / Velocity;
|
||||||
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The target fruit if we are to initiate a hyperdash.
|
/// The target fruit if we are to initiate a hyperdash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public CatchHitObject HyperDashTarget
|
public CatchHitObject HyperDashTarget
|
||||||
{
|
{
|
||||||
get => hyperDashTarget;
|
get => hyperDashTarget;
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTooShortSlidersTest
|
||||||
|
{
|
||||||
|
private CheckTooShortSliders check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTooShortSliders();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLongSlider()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(100, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortSlider()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(25, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSliderExpert()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(10, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { slider }, DifficultyRating.Expert);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSlider()
|
||||||
|
{
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 0,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(10, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertTooShort(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSliderWithRepeats()
|
||||||
|
{
|
||||||
|
// Would be ok if we looked at the duration, but not if we look at the span duration.
|
||||||
|
Slider slider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
RepeatCount = 2,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(10, 0))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
assertTooShort(new List<HitObject> { slider });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(List<HitObject> hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy)
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(getContext(hitObjects, difficultyRating)), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTooShort(List<HitObject> hitObjects, DifficultyRating difficultyRating = DifficultyRating.Easy)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects, difficultyRating)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckTooShortSliders.IssueTemplateTooShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(List<HitObject> hitObjects, DifficultyRating difficultyRating)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject> { HitObjects = hitObjects };
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTooShortSpinnersTest
|
||||||
|
{
|
||||||
|
private CheckTooShortSpinners check;
|
||||||
|
private BeatmapDifficulty difficulty;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTooShortSpinners();
|
||||||
|
difficulty = new BeatmapDifficulty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLongSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 4000 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 750 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVeryShortSpinner()
|
||||||
|
{
|
||||||
|
// Spinners at a certain duration only get 1000 points if approached by auto at a certain angle, making it difficult to determine.
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 475 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertVeryShort(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = new Spinner { StartTime = 0, Duration = 400 };
|
||||||
|
spinner.ApplyDefaults(new ControlPointInfo(), difficulty);
|
||||||
|
|
||||||
|
assertTooShort(new List<HitObject> { spinner }, difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooShortSpinnerVaryingOd()
|
||||||
|
{
|
||||||
|
const double duration = 450;
|
||||||
|
|
||||||
|
var difficultyLowOd = new BeatmapDifficulty { OverallDifficulty = 1 };
|
||||||
|
Spinner spinnerLowOd = new Spinner { StartTime = 0, Duration = duration };
|
||||||
|
spinnerLowOd.ApplyDefaults(new ControlPointInfo(), difficultyLowOd);
|
||||||
|
|
||||||
|
var difficultyHighOd = new BeatmapDifficulty { OverallDifficulty = 10 };
|
||||||
|
Spinner spinnerHighOd = new Spinner { StartTime = 0, Duration = duration };
|
||||||
|
spinnerHighOd.ApplyDefaults(new ControlPointInfo(), difficultyHighOd);
|
||||||
|
|
||||||
|
assertOk(new List<HitObject> { spinnerLowOd }, difficultyLowOd);
|
||||||
|
assertTooShort(new List<HitObject> { spinnerHighOd }, difficultyHighOd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(getContext(hitObjects, beatmapDifficulty)), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertVeryShort(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateVeryShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTooShort(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateTooShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(List<HitObject> hitObjects, BeatmapDifficulty beatmapDifficulty)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = hitObjects,
|
||||||
|
BeatmapInfo = new BeatmapInfo { BaseDifficulty = beatmapDifficulty }
|
||||||
|
};
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
case OsuSkinConfiguration osuLookup:
|
case OsuSkinConfiguration osuLookup:
|
||||||
if (osuLookup == OsuSkinConfiguration.CursorCentre)
|
if (osuLookup == OsuSkinConfiguration.CursorCentre)
|
||||||
return SkinUtils.As<TValue>(new BindableBool(false));
|
return SkinUtils.As<TValue>(new BindableBool());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
48
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs
Normal file
48
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSliders.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckTooShortSliders : ICheck
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The shortest acceptable duration between the head and tail of the slider (so ignoring repeats).
|
||||||
|
/// </summary>
|
||||||
|
private const double span_duration_threshold = 125; // 240 BPM 1/2
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short sliders");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateTooShort(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
if (context.InterpretedDifficulty > DifficultyRating.Easy)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
foreach (var hitObject in context.Beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
if (hitObject is Slider slider && slider.SpanDuration < span_duration_threshold)
|
||||||
|
yield return new IssueTemplateTooShort(this).Create(slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateTooShort : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateTooShort(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This slider is too short ({0:0} ms), expected at least {1:0} ms.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Slider slider) => new Issue(slider, this, slider.SpanDuration, span_duration_threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
Normal file
61
osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckTooShortSpinners : ICheck
|
||||||
|
{
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Too short spinners");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateTooShort(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
double od = context.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty;
|
||||||
|
|
||||||
|
// These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner.
|
||||||
|
// It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners.
|
||||||
|
double warningThreshold = 500 + (od < 5 ? (5 - od) * -21.8 : (od - 5) * 20); // Anything above this is always ok.
|
||||||
|
double problemThreshold = 450 + (od < 5 ? (5 - od) * -17 : (od - 5) * 17); // Anything below this is never ok.
|
||||||
|
|
||||||
|
foreach (var hitObject in context.Beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
if (!(hitObject is Spinner spinner))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (spinner.Duration < problemThreshold)
|
||||||
|
yield return new IssueTemplateTooShort(this).Create(spinner);
|
||||||
|
else if (spinner.Duration < warningThreshold)
|
||||||
|
yield return new IssueTemplateVeryShort(this).Create(spinner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateTooShort : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateTooShort(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This spinner is too short. Auto cannot achieve 1000 points on this.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Spinner spinner) => new Issue(spinner, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateVeryShort : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateVeryShort(ICheck check)
|
||||||
|
: base(check, IssueType.Warning, "This spinner may be too short. Ensure auto can achieve 1000 points on this.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Spinner spinner) => new Issue(spinner, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
// Compose
|
// Compose
|
||||||
new CheckOffscreenObjects(),
|
new CheckOffscreenObjects(),
|
||||||
|
new CheckTooShortSpinners(),
|
||||||
|
|
||||||
// Spread
|
// Spread
|
||||||
new CheckTimeDistanceEquality(),
|
new CheckTimeDistanceEquality(),
|
||||||
new CheckLowDiffOverlaps()
|
new CheckLowDiffOverlaps(),
|
||||||
|
new CheckTooShortSliders(),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
@ -158,17 +158,17 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var firstObj = beatmap.HitObjects[0];
|
var firstObj = beatmap.HitObjects[0];
|
||||||
var startDelay = firstObj.StartTime - firstObj.TimePreempt;
|
var startDelay = firstObj.StartTime - firstObj.TimePreempt;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(startDelay + break_close_late, true))
|
using (BeginAbsoluteSequence(startDelay + break_close_late))
|
||||||
leaveBreak();
|
leaveBreak();
|
||||||
|
|
||||||
foreach (var breakInfo in beatmap.Breaks)
|
foreach (var breakInfo in beatmap.Breaks)
|
||||||
{
|
{
|
||||||
if (breakInfo.HasEffect)
|
if (breakInfo.HasEffect)
|
||||||
{
|
{
|
||||||
using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early, true))
|
using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early))
|
||||||
{
|
{
|
||||||
enterBreak();
|
enterBreak();
|
||||||
using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late, true))
|
using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late))
|
||||||
leaveBreak();
|
leaveBreak();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.Bindables;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -11,34 +10,26 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModDifficultyAdjust : ModDifficultyAdjust
|
public class OsuModDifficultyAdjust : ModDifficultyAdjust
|
||||||
{
|
{
|
||||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
|
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Default = 5,
|
ExtendedMaxValue = 11,
|
||||||
Value = 5,
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
|
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Default = 5,
|
ExtendedMaxValue = 11,
|
||||||
Value = 5,
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override void ApplyLimits(bool extended)
|
|
||||||
{
|
|
||||||
base.ApplyLimits(extended);
|
|
||||||
|
|
||||||
CircleSize.MaxValue = extended ? 11 : 10;
|
|
||||||
ApproachRate.MaxValue = extended ? 11 : 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string SettingDescription
|
public override string SettingDescription
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -55,20 +46,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void TransferSettings(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
base.TransferSettings(difficulty);
|
|
||||||
|
|
||||||
TransferSetting(CircleSize, difficulty.CircleSize);
|
|
||||||
TransferSetting(ApproachRate, difficulty.ApproachRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value;
|
||||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of previous hitobjects to be shifted together when another object is being moved.
|
||||||
|
/// </summary>
|
||||||
|
private const int preceding_hitobjects_to_shift = 10;
|
||||||
|
|
||||||
private Random rng;
|
private Random rng;
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
@ -49,8 +54,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
var current = new RandomObjectInfo(hitObject);
|
var current = new RandomObjectInfo(hitObject);
|
||||||
|
|
||||||
// rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams
|
// rateOfChangeMultiplier only changes every 5 iterations in a combo
|
||||||
if (i % 3 == 0)
|
// to prevent shaky-line-shaped streams
|
||||||
|
if (hitObject.IndexInCurrentCombo % 5 == 0)
|
||||||
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
|
||||||
|
|
||||||
if (hitObject is Spinner)
|
if (hitObject is Spinner)
|
||||||
@ -61,13 +67,35 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
applyRandomisation(rateOfChangeMultiplier, previous, current);
|
applyRandomisation(rateOfChangeMultiplier, previous, current);
|
||||||
|
|
||||||
hitObject.Position = current.PositionRandomised;
|
// Move hit objects back into the playfield if they are outside of it
|
||||||
|
Vector2 shift = Vector2.Zero;
|
||||||
|
|
||||||
// update end position as it may have changed as a result of the position update.
|
switch (hitObject)
|
||||||
current.EndPositionRandomised = current.PositionRandomised;
|
{
|
||||||
|
case HitCircle circle:
|
||||||
|
shift = clampHitCircleToPlayfield(circle, current);
|
||||||
|
break;
|
||||||
|
|
||||||
if (hitObject is Slider slider)
|
case Slider slider:
|
||||||
moveSliderIntoPlayfield(slider, current);
|
shift = clampSliderToPlayfield(slider, current);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shift != Vector2.Zero)
|
||||||
|
{
|
||||||
|
var toBeShifted = new List<OsuHitObject>();
|
||||||
|
|
||||||
|
for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--)
|
||||||
|
{
|
||||||
|
// only shift hit circles
|
||||||
|
if (!(hitObjects[j] is HitCircle)) break;
|
||||||
|
|
||||||
|
toBeShifted.Add(hitObjects[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toBeShifted.Count > 0)
|
||||||
|
applyDecreasingShift(toBeShifted, shift);
|
||||||
|
}
|
||||||
|
|
||||||
previous = current;
|
previous = current;
|
||||||
}
|
}
|
||||||
@ -94,7 +122,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
// The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
|
// The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
|
||||||
// is proportional to the distance between the last and the current hit object
|
// is proportional to the distance between the last and the current hit object
|
||||||
// to allow jumps and prevent too sharp turns during streams.
|
// to allow jumps and prevent too sharp turns during streams.
|
||||||
var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal;
|
|
||||||
|
// Allow maximum jump angle when jump distance is more than half of playfield diagonal length
|
||||||
|
var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f));
|
||||||
|
|
||||||
current.AngleRad = (float)randomAngleRad + previous.AngleRad;
|
current.AngleRad = (float)randomAngleRad + previous.AngleRad;
|
||||||
if (current.AngleRad < 0)
|
if (current.AngleRad < 0)
|
||||||
@ -109,56 +139,120 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
|
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
|
||||||
|
|
||||||
var position = previous.EndPositionRandomised + posRelativeToPrev;
|
current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev;
|
||||||
|
}
|
||||||
|
|
||||||
// Move hit objects back into the playfield if they are outside of it,
|
/// <summary>
|
||||||
// which would sometimes happen during big jumps otherwise.
|
/// Move the randomised position of a hit circle so that it fits inside the playfield.
|
||||||
position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X);
|
/// </summary>
|
||||||
position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y);
|
/// <returns>The deviation from the original randomised position in order to fit within the playfield.</returns>
|
||||||
|
private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo)
|
||||||
|
{
|
||||||
|
var previousPosition = objectInfo.PositionRandomised;
|
||||||
|
objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding(
|
||||||
|
objectInfo.PositionRandomised,
|
||||||
|
(float)circle.Radius
|
||||||
|
);
|
||||||
|
|
||||||
current.PositionRandomised = position;
|
circle.Position = objectInfo.PositionRandomised;
|
||||||
|
|
||||||
|
return objectInfo.PositionRandomised - previousPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
|
/// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
|
/// <returns>The deviation from the original randomised position in order to fit within the playfield.</returns>
|
||||||
|
private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo)
|
||||||
{
|
{
|
||||||
var minMargin = getMinSliderMargin(slider);
|
var possibleMovementBounds = calculatePossibleMovementBounds(slider);
|
||||||
|
|
||||||
slider.Position = new Vector2(
|
var previousPosition = objectInfo.PositionRandomised;
|
||||||
Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right),
|
|
||||||
Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom)
|
|
||||||
);
|
|
||||||
|
|
||||||
currentObjectInfo.PositionRandomised = slider.Position;
|
// Clamp slider position to the placement area
|
||||||
currentObjectInfo.EndPositionRandomised = slider.EndPosition;
|
// If the slider is larger than the playfield, force it to stay at the original position
|
||||||
|
var newX = possibleMovementBounds.Width < 0
|
||||||
|
? objectInfo.PositionOriginal.X
|
||||||
|
: Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right);
|
||||||
|
|
||||||
shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal);
|
var newY = possibleMovementBounds.Height < 0
|
||||||
|
? objectInfo.PositionOriginal.Y
|
||||||
|
: Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom);
|
||||||
|
|
||||||
|
slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY);
|
||||||
|
objectInfo.EndPositionRandomised = slider.EndPosition;
|
||||||
|
|
||||||
|
shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal);
|
||||||
|
|
||||||
|
return objectInfo.PositionRandomised - previousPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the min. distances from the <see cref="Slider"/>'s position to the playfield border for the slider to be fully inside of the playfield.
|
/// Decreasingly shift a list of <see cref="OsuHitObject"/>s by a specified amount.
|
||||||
|
/// The first item in the list is shifted by the largest amount, while the last item is shifted by the smallest amount.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private MarginPadding getMinSliderMargin(Slider slider)
|
/// <param name="hitObjects">The list of hit objects to be shifted.</param>
|
||||||
|
/// <param name="shift">The amount to be shifted.</param>
|
||||||
|
private void applyDecreasingShift(IList<OsuHitObject> hitObjects, Vector2 shift)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < hitObjects.Count; i++)
|
||||||
|
{
|
||||||
|
var hitObject = hitObjects[i];
|
||||||
|
// The first object is shifted by a vector slightly smaller than shift
|
||||||
|
// The last object is shifted by a vector slightly larger than zero
|
||||||
|
Vector2 position = hitObject.Position + shift * ((hitObjects.Count - i) / (float)(hitObjects.Count + 1));
|
||||||
|
|
||||||
|
hitObject.Position = clampToPlayfieldWithPadding(position, (float)hitObject.Radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates a <see cref="RectangleF"/> which contains all of the possible movements of the slider (in relative X/Y coordinates)
|
||||||
|
/// such that the entire slider is inside the playfield.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
|
||||||
|
/// </remarks>
|
||||||
|
private RectangleF calculatePossibleMovementBounds(Slider slider)
|
||||||
{
|
{
|
||||||
var pathPositions = new List<Vector2>();
|
var pathPositions = new List<Vector2>();
|
||||||
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
||||||
|
|
||||||
var minMargin = new MarginPadding();
|
float minX = float.PositiveInfinity;
|
||||||
|
float maxX = float.NegativeInfinity;
|
||||||
|
|
||||||
|
float minY = float.PositiveInfinity;
|
||||||
|
float maxY = float.NegativeInfinity;
|
||||||
|
|
||||||
|
// Compute the bounding box of the slider.
|
||||||
foreach (var pos in pathPositions)
|
foreach (var pos in pathPositions)
|
||||||
{
|
{
|
||||||
minMargin.Left = Math.Max(minMargin.Left, -pos.X);
|
minX = MathF.Min(minX, pos.X);
|
||||||
minMargin.Right = Math.Max(minMargin.Right, pos.X);
|
maxX = MathF.Max(maxX, pos.X);
|
||||||
minMargin.Top = Math.Max(minMargin.Top, -pos.Y);
|
|
||||||
minMargin.Bottom = Math.Max(minMargin.Bottom, pos.Y);
|
minY = MathF.Min(minY, pos.Y);
|
||||||
|
maxY = MathF.Max(maxY, pos.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
minMargin.Left = Math.Min(minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right);
|
// Take the circle radius into account.
|
||||||
minMargin.Top = Math.Min(minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom);
|
var radius = (float)slider.Radius;
|
||||||
|
|
||||||
return minMargin;
|
minX -= radius;
|
||||||
|
minY -= radius;
|
||||||
|
|
||||||
|
maxX += radius;
|
||||||
|
maxY += radius;
|
||||||
|
|
||||||
|
// Given the bounding box of the slider (via min/max X/Y),
|
||||||
|
// the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right),
|
||||||
|
// and the amount that it can move to the right is WIDTH - maxX.
|
||||||
|
// Same calculation applies for the Y axis.
|
||||||
|
float left = -minX;
|
||||||
|
float right = OsuPlayfield.BASE_SIZE.X - maxX;
|
||||||
|
float top = -minY;
|
||||||
|
float bottom = OsuPlayfield.BASE_SIZE.Y - maxY;
|
||||||
|
|
||||||
|
return new RectangleF(left, top, right - left, bottom - top);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -177,6 +271,20 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamp a position to playfield, keeping a specified distance from the edges.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position to be clamped.</param>
|
||||||
|
/// <param name="padding">The minimum distance allowed from playfield edges.</param>
|
||||||
|
/// <returns>The clamped position.</returns>
|
||||||
|
private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding)
|
||||||
|
{
|
||||||
|
return new Vector2(
|
||||||
|
Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding),
|
||||||
|
Math.Clamp(position.Y, padding, OsuPlayfield.BASE_SIZE.Y - padding)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private class RandomObjectInfo
|
private class RandomObjectInfo
|
||||||
{
|
{
|
||||||
public float AngleRad { get; set; }
|
public float AngleRad { get; set; }
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
switch (drawable)
|
switch (drawable)
|
||||||
{
|
{
|
||||||
case DrawableHitCircle circle:
|
case DrawableHitCircle circle:
|
||||||
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
|
||||||
{
|
{
|
||||||
circle.ApproachCircle.Hide();
|
circle.ApproachCircle.Hide();
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
|
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
|
||||||
{
|
{
|
||||||
var h = hitObject.HitObject;
|
var h = hitObject.HitObject;
|
||||||
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
|
||||||
(hitCircle ?? hitObject).Hide();
|
(hitCircle ?? hitObject).Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
|
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
|
||||||
double moveDuration = hitObject.TimePreempt + 1;
|
double moveDuration = hitObject.TimePreempt + 1;
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(appearTime, true))
|
using (drawable.BeginAbsoluteSequence(appearTime))
|
||||||
{
|
{
|
||||||
drawable
|
drawable
|
||||||
.MoveToOffset(appearOffset)
|
.MoveToOffset(appearOffset)
|
||||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
for (int i = 0; i < amountWiggles; i++)
|
for (int i = 0; i < amountWiggles; i++)
|
||||||
{
|
{
|
||||||
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
|
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration))
|
||||||
wiggle();
|
wiggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
for (int i = 0; i < amountWiggles; i++)
|
for (int i = 0; i < amountWiggles; i++)
|
||||||
{
|
{
|
||||||
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
|
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration))
|
||||||
wiggle();
|
wiggle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,35 +233,43 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
// Wait until Auto could "see and react" to the next note.
|
// Wait until Auto could "see and react" to the next note.
|
||||||
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt));
|
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt));
|
||||||
|
bool hasWaited = false;
|
||||||
|
|
||||||
if (waitTime > lastFrame.Time)
|
if (waitTime > lastFrame.Time)
|
||||||
{
|
{
|
||||||
lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions };
|
lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions };
|
||||||
|
hasWaited = true;
|
||||||
AddFrameToReplay(lastFrame);
|
AddFrameToReplay(lastFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 lastPosition = lastFrame.Position;
|
|
||||||
|
|
||||||
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
|
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
|
||||||
|
OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null;
|
||||||
|
|
||||||
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
|
if (timeDifference > 0)
|
||||||
if (timeDifference > 0 && // Sanity checks
|
|
||||||
((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
|
|
||||||
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
|
|
||||||
{
|
{
|
||||||
// Perform eased movement
|
// If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously.
|
||||||
|
if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited)
|
||||||
|
{
|
||||||
|
// [lastLastFrame] ... [lastFrame] ... [current frame]
|
||||||
|
// We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position.
|
||||||
|
lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 lastPosition = lastFrame.Position;
|
||||||
|
|
||||||
|
// Perform the rest of the eased movement until the target position is reached.
|
||||||
for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time))
|
for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time))
|
||||||
{
|
{
|
||||||
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
|
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
|
||||||
AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions });
|
AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buttonIndex = 0;
|
// Start alternating once the time separation is too small (faster than ~225BPM).
|
||||||
}
|
if (timeDifference > 0 && timeDifference < 266)
|
||||||
else
|
|
||||||
{
|
|
||||||
buttonIndex++;
|
buttonIndex++;
|
||||||
}
|
else
|
||||||
|
buttonIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -284,7 +292,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
|
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
|
||||||
double hEndTime = h.GetEndTime() + KEY_UP_DELAY;
|
double hEndTime = h.GetEndTime() + KEY_UP_DELAY;
|
||||||
int endDelay = h is Spinner ? 1 : 0;
|
int endDelay = h is Spinner ? 1 : 0;
|
||||||
var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
|
var endFrame = new OsuKeyUpReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
|
||||||
|
|
||||||
// Decrement because we want the previous frame, not the next one
|
// Decrement because we want the previous frame, not the next one
|
||||||
int index = FindInsertionIndex(startFrame) - 1;
|
int index = FindInsertionIndex(startFrame) - 1;
|
||||||
@ -381,5 +389,13 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private class OsuKeyUpReplayFrame : OsuReplayFrame
|
||||||
|
{
|
||||||
|
public OsuKeyUpReplayFrame(double time, Vector2 position)
|
||||||
|
: base(time, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,18 +130,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
Spinner spinner = drawableSpinner.HitObject;
|
Spinner spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
{
|
{
|
||||||
this.ScaleTo(initial_scale);
|
this.ScaleTo(initial_scale);
|
||||||
this.RotateTo(0);
|
this.RotateTo(0);
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
{
|
{
|
||||||
// constant ambient rotation to give the spinner "spinning" character.
|
// constant ambient rotation to give the spinner "spinning" character.
|
||||||
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true))
|
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset))
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
@ -157,17 +157,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
{
|
{
|
||||||
centre.ScaleTo(0);
|
centre.ScaleTo(0);
|
||||||
mainContainer.ScaleTo(0);
|
mainContainer.ScaleTo(0);
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
{
|
{
|
||||||
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
{
|
{
|
||||||
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
|
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
|
|
||||||
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
updateComplete(state == ArmedState.Hit, 0);
|
updateComplete(state == ArmedState.Hit, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
|
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
|
||||||
{
|
{
|
||||||
// For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
|
// For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
|
||||||
|
// ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter
|
||||||
base.ApplyTransformsAt(time, false);
|
base.ApplyTransformsAt(time, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,17 +100,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
case DrawableSpinner d:
|
case DrawableSpinner d:
|
||||||
Spinner spinner = d.HitObject;
|
Spinner spinner = d.HitObject;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
this.FadeOut();
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
{
|
{
|
||||||
fixedMiddle.FadeColour(Color4.White);
|
fixedMiddle.FadeColour(Color4.White);
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt, true))
|
using (BeginDelayedSequence(spinner.TimePreempt))
|
||||||
fixedMiddle.FadeColour(Color4.Red, spinner.Duration);
|
fixedMiddle.FadeColour(Color4.Red, spinner.Duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +89,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
Spinner spinner = d.HitObject;
|
Spinner spinner = d.HitObject;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
this.FadeOut();
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
double startTime = Math.Min(Time.Current, DrawableSpinner.HitStateUpdateTime - 400);
|
double startTime = Math.Min(Time.Current, DrawableSpinner.HitStateUpdateTime - 400);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(startTime, true))
|
using (BeginAbsoluteSequence(startTime))
|
||||||
{
|
{
|
||||||
clear.FadeInFromZero(400, Easing.Out);
|
clear.FadeInFromZero(400, Easing.Out);
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
const double fade_out_duration = 50;
|
const double fade_out_duration = 50;
|
||||||
using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration, true))
|
using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration))
|
||||||
clear.FadeOut(fade_out_duration);
|
clear.FadeOut(fade_out_duration);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -182,14 +182,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
double spinFadeOutLength = Math.Min(400, d.HitObject.Duration);
|
double spinFadeOutLength = Math.Min(400, d.HitObject.Duration);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true))
|
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength))
|
||||||
spin.FadeOutFromOne(spinFadeOutLength);
|
spin.FadeOutFromOne(spinFadeOutLength);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSpinnerTick d:
|
case DrawableSpinnerTick d:
|
||||||
if (state == ArmedState.Hit)
|
if (state == ArmedState.Hit)
|
||||||
{
|
{
|
||||||
using (BeginAbsoluteSequence(d.HitStateUpdateTime, true))
|
using (BeginAbsoluteSequence(d.HitStateUpdateTime))
|
||||||
spin.FadeOut(300);
|
spin.FadeOut(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.Bindables;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -11,14 +10,13 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public class TaikoModDifficultyAdjust : ModDifficultyAdjust
|
public class TaikoModDifficultyAdjust : ModDifficultyAdjust
|
||||||
{
|
{
|
||||||
[SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)]
|
[SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> ScrollSpeed { get; } = new BindableFloat
|
public DifficultyBindable ScrollSpeed { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.05f,
|
Precision = 0.05f,
|
||||||
MinValue = 0.25f,
|
MinValue = 0.25f,
|
||||||
MaxValue = 4,
|
MaxValue = 4,
|
||||||
Default = 1,
|
ReadCurrentFromDifficulty = _ => 1,
|
||||||
Value = 1,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override string SettingDescription
|
public override string SettingDescription
|
||||||
@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
ApplySetting(ScrollSpeed, scroll => difficulty.SliderMultiplier *= scroll);
|
if (ScrollSpeed.Value != null) difficulty.SliderMultiplier *= ScrollSpeed.Value.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateStartTimeStateTransforms();
|
base.UpdateStartTimeStateTransforms();
|
||||||
|
|
||||||
using (BeginDelayedSequence(-ring_appear_offset, true))
|
using (BeginDelayedSequence(-ring_appear_offset))
|
||||||
targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint);
|
targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSingleSpan()
|
public void TestSingleSpan()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
|
||||||
|
|
||||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||||
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRepeat()
|
public void TestRepeat()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
|
||||||
|
|
||||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||||
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestNonEvenTicks()
|
public void TestNonEvenTicks()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
|
||||||
|
|
||||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||||
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLegacyLastTickOffset()
|
public void TestLegacyLastTickOffset()
|
||||||
{
|
{
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
|
||||||
|
|
||||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
|
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
|
||||||
Assert.That(events[2].Time, Is.EqualTo(900));
|
Assert.That(events[2].Time, Is.EqualTo(900));
|
||||||
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
const double velocity = 5;
|
const double velocity = 5;
|
||||||
const double min_distance = velocity * 10;
|
const double min_distance = velocity * 10;
|
||||||
|
|
||||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray();
|
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
94
osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs
Normal file
94
osu.Game.Tests/Editing/Checks/CheckZeroLengthObjectsTest.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckZeroLengthObjectsTest
|
||||||
|
{
|
||||||
|
private CheckZeroLengthObjects check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckZeroLengthObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircle()
|
||||||
|
{
|
||||||
|
assertOk(new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(0, 0) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRegularSlider()
|
||||||
|
{
|
||||||
|
assertOk(new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(1000).Object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestZeroLengthSlider()
|
||||||
|
{
|
||||||
|
assertZeroLength(new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(0).Object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNegativeLengthSlider()
|
||||||
|
{
|
||||||
|
assertZeroLength(new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(-1000).Object
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<Slider> getSliderMock(double duration)
|
||||||
|
{
|
||||||
|
var mockSlider = new Mock<Slider>();
|
||||||
|
mockSlider.As<IHasDuration>().Setup(d => d.Duration).Returns(duration);
|
||||||
|
|
||||||
|
return mockSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(List<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertZeroLength(List<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(hitObjects)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.First().Template is CheckZeroLengthObjects.IssueTemplateZeroLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(List<HitObject> hitObjects)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject> { HitObjects = hitObjects };
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs
Normal file
165
osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Mods
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ModDifficultyAdjustTest
|
||||||
|
{
|
||||||
|
private TestModDifficultyAdjust testMod;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
testMod = new TestModDifficultyAdjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnchangedSettingsFollowAppliedDifficulty()
|
||||||
|
{
|
||||||
|
var result = applyDifficulty(new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
DrainRate = 10,
|
||||||
|
OverallDifficulty = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(result.DrainRate, Is.EqualTo(10));
|
||||||
|
Assert.That(result.OverallDifficulty, Is.EqualTo(10));
|
||||||
|
|
||||||
|
result = applyDifficulty(new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
DrainRate = 1,
|
||||||
|
OverallDifficulty = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(result.DrainRate, Is.EqualTo(1));
|
||||||
|
Assert.That(result.OverallDifficulty, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangedSettingsOverrideAppliedDifficulty()
|
||||||
|
{
|
||||||
|
testMod.OverallDifficulty.Value = 4;
|
||||||
|
|
||||||
|
var result = applyDifficulty(new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
DrainRate = 10,
|
||||||
|
OverallDifficulty = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(result.DrainRate, Is.EqualTo(10));
|
||||||
|
Assert.That(result.OverallDifficulty, Is.EqualTo(4));
|
||||||
|
|
||||||
|
result = applyDifficulty(new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
DrainRate = 1,
|
||||||
|
OverallDifficulty = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(result.DrainRate, Is.EqualTo(1));
|
||||||
|
Assert.That(result.OverallDifficulty, Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangedSettingsRetainedWhenSameValueIsApplied()
|
||||||
|
{
|
||||||
|
testMod.OverallDifficulty.Value = 4;
|
||||||
|
|
||||||
|
// Apply and de-apply the same value as the mod.
|
||||||
|
applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 4 });
|
||||||
|
var result = applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 10 });
|
||||||
|
|
||||||
|
Assert.That(result.OverallDifficulty, Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangedSettingSerialisedWhenSameValueIsApplied()
|
||||||
|
{
|
||||||
|
applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 4 });
|
||||||
|
testMod.OverallDifficulty.Value = 4;
|
||||||
|
|
||||||
|
var result = (TestModDifficultyAdjust)new APIMod(testMod).ToMod(new TestRuleset());
|
||||||
|
|
||||||
|
Assert.That(result.OverallDifficulty.Value, Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangedSettingsRevertedToDefault()
|
||||||
|
{
|
||||||
|
applyDifficulty(new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
DrainRate = 10,
|
||||||
|
OverallDifficulty = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
testMod.OverallDifficulty.Value = 4;
|
||||||
|
testMod.ResetSettingsToDefaults();
|
||||||
|
|
||||||
|
Assert.That(testMod.DrainRate.Value, Is.Null);
|
||||||
|
Assert.That(testMod.OverallDifficulty.Value, Is.Null);
|
||||||
|
|
||||||
|
var applied = applyDifficulty(new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
DrainRate = 10,
|
||||||
|
OverallDifficulty = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(applied.OverallDifficulty, Is.EqualTo(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a <see cref="BeatmapDifficulty"/> to the mod and returns a new <see cref="BeatmapDifficulty"/>
|
||||||
|
/// representing the result if the mod were applied to a fresh <see cref="BeatmapDifficulty"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
private BeatmapDifficulty applyDifficulty(BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
// ensure that ReadFromDifficulty doesn't pollute the values.
|
||||||
|
var newDifficulty = difficulty.Clone();
|
||||||
|
|
||||||
|
testMod.ReadFromDifficulty(difficulty);
|
||||||
|
|
||||||
|
testMod.ApplyToDifficulty(newDifficulty);
|
||||||
|
return newDifficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestModDifficultyAdjust : ModDifficultyAdjust
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRuleset : Ruleset
|
||||||
|
{
|
||||||
|
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||||
|
{
|
||||||
|
if (type == ModType.DifficultyIncrease)
|
||||||
|
yield return new TestModDifficultyAdjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Description => string.Empty;
|
||||||
|
public override string ShortName => string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -184,6 +184,9 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.DoesNotThrow(() => osu.Migrate(customPath2));
|
Assert.DoesNotThrow(() => osu.Migrate(customPath2));
|
||||||
Assert.That(File.Exists(Path.Combine(customPath2, database_filename)));
|
Assert.That(File.Exists(Path.Combine(customPath2, database_filename)));
|
||||||
|
|
||||||
|
// some files may have been left behind for whatever reason, but that's not what we're testing here.
|
||||||
|
customPath = prepareCustomPath();
|
||||||
|
|
||||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
|
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
|
||||||
}
|
}
|
||||||
|
11
osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs
Normal file
11
osu.Game.Tests/Resources/Shaders/sh_TestFragment.fs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include "sh_Utils.h"
|
||||||
|
|
||||||
|
varying mediump vec2 v_TexCoord;
|
||||||
|
varying mediump vec4 v_TexRect;
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]);
|
||||||
|
gl_FragColor = hsv2rgb(vec4(hueValue, 1, 1, 1));
|
||||||
|
}
|
||||||
|
|
31
osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs
Normal file
31
osu.Game.Tests/Resources/Shaders/sh_TestVertex.vs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#include "sh_Utils.h"
|
||||||
|
|
||||||
|
attribute highp vec2 m_Position;
|
||||||
|
attribute lowp vec4 m_Colour;
|
||||||
|
attribute mediump vec2 m_TexCoord;
|
||||||
|
attribute mediump vec4 m_TexRect;
|
||||||
|
attribute mediump vec2 m_BlendRange;
|
||||||
|
|
||||||
|
varying highp vec2 v_MaskingPosition;
|
||||||
|
varying lowp vec4 v_Colour;
|
||||||
|
varying mediump vec2 v_TexCoord;
|
||||||
|
varying mediump vec4 v_TexRect;
|
||||||
|
varying mediump vec2 v_BlendRange;
|
||||||
|
|
||||||
|
uniform highp mat4 g_ProjMatrix;
|
||||||
|
uniform highp mat3 g_ToMaskingSpace;
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
// Transform from screen space to masking space.
|
||||||
|
highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
|
||||||
|
v_MaskingPosition = maskingPos.xy / maskingPos.z;
|
||||||
|
|
||||||
|
v_Colour = m_Colour;
|
||||||
|
v_TexCoord = m_TexCoord;
|
||||||
|
v_TexRect = m_TexRect;
|
||||||
|
v_BlendRange = m_BlendRange;
|
||||||
|
|
||||||
|
gl_Position = gProjMatrix * vec4(m_Position, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -13,8 +13,10 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
|
using osu.Framework.Graphics.Shaders;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -31,12 +33,14 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
DrawableWithDependencies drawable = null;
|
DrawableWithDependencies drawable = null;
|
||||||
TestTextureStore textureStore = null;
|
TestTextureStore textureStore = null;
|
||||||
TestSampleStore sampleStore = null;
|
TestSampleStore sampleStore = null;
|
||||||
|
TestShaderManager shaderManager = null;
|
||||||
|
|
||||||
AddStep("add dependencies", () =>
|
AddStep("add dependencies", () =>
|
||||||
{
|
{
|
||||||
Child = drawable = new DrawableWithDependencies();
|
Child = drawable = new DrawableWithDependencies();
|
||||||
textureStore = drawable.ParentTextureStore;
|
textureStore = drawable.ParentTextureStore;
|
||||||
sampleStore = drawable.ParentSampleStore;
|
sampleStore = drawable.ParentSampleStore;
|
||||||
|
shaderManager = drawable.ParentShaderManager;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("clear children", Clear);
|
AddStep("clear children", Clear);
|
||||||
@ -52,12 +56,14 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
AddAssert("parent texture store not disposed", () => !textureStore.IsDisposed);
|
AddAssert("parent texture store not disposed", () => !textureStore.IsDisposed);
|
||||||
AddAssert("parent sample store not disposed", () => !sampleStore.IsDisposed);
|
AddAssert("parent sample store not disposed", () => !sampleStore.IsDisposed);
|
||||||
|
AddAssert("parent shader manager not disposed", () => !shaderManager.IsDisposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DrawableWithDependencies : CompositeDrawable
|
private class DrawableWithDependencies : CompositeDrawable
|
||||||
{
|
{
|
||||||
public TestTextureStore ParentTextureStore { get; private set; }
|
public TestTextureStore ParentTextureStore { get; private set; }
|
||||||
public TestSampleStore ParentSampleStore { get; private set; }
|
public TestSampleStore ParentSampleStore { get; private set; }
|
||||||
|
public TestShaderManager ParentShaderManager { get; private set; }
|
||||||
|
|
||||||
public DrawableWithDependencies()
|
public DrawableWithDependencies()
|
||||||
{
|
{
|
||||||
@ -70,6 +76,7 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore());
|
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore());
|
||||||
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
||||||
|
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager());
|
||||||
|
|
||||||
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
||||||
}
|
}
|
||||||
@ -135,5 +142,23 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
public int PlaybackConcurrency { get; set; }
|
public int PlaybackConcurrency { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestShaderManager : ShaderManager
|
||||||
|
{
|
||||||
|
public TestShaderManager()
|
||||||
|
: base(new ResourceStore<byte[]>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] LoadRaw(string name) => null;
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
94
osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
Normal file
94
osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Skins
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneSkinProvidingContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the first inserted skin after resetting (via source change)
|
||||||
|
/// is always prioritised over others when providing the same resource.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPriorityPreservation()
|
||||||
|
{
|
||||||
|
TestSkinProvidingContainer provider = null;
|
||||||
|
TestSkin mostPrioritisedSource = null;
|
||||||
|
|
||||||
|
AddStep("setup sources", () =>
|
||||||
|
{
|
||||||
|
var sources = new List<TestSkin>();
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
sources.Add(new TestSkin());
|
||||||
|
|
||||||
|
mostPrioritisedSource = sources.First();
|
||||||
|
|
||||||
|
Child = provider = new TestSkinProvidingContainer(sources);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("texture provided by expected skin", () =>
|
||||||
|
{
|
||||||
|
return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("trigger source change", () => provider.TriggerSourceChanged());
|
||||||
|
|
||||||
|
AddAssert("texture still provided by expected skin", () =>
|
||||||
|
{
|
||||||
|
return provider.FindProvider(s => s.GetTexture(TestSkin.TEXTURE_NAME) != null) == mostPrioritisedSource;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSkinProvidingContainer : SkinProvidingContainer
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<ISkin> sources;
|
||||||
|
|
||||||
|
public TestSkinProvidingContainer(IEnumerable<ISkin> sources)
|
||||||
|
{
|
||||||
|
this.sources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void TriggerSourceChanged() => base.TriggerSourceChanged();
|
||||||
|
|
||||||
|
protected override void OnSourceChanged()
|
||||||
|
{
|
||||||
|
ResetSources();
|
||||||
|
sources.ForEach(AddSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSkin : ISkin
|
||||||
|
{
|
||||||
|
public const string TEXTURE_NAME = "virtual-texture";
|
||||||
|
|
||||||
|
public Drawable GetDrawableComponent(ISkinComponent component) => throw new System.NotImplementedException();
|
||||||
|
|
||||||
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
|
{
|
||||||
|
if (componentName == TEXTURE_NAME)
|
||||||
|
return Texture.WhitePixel;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISample GetSample(ISampleInfo sampleInfo) => throw new System.NotImplementedException();
|
||||||
|
|
||||||
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Configuration.Tracking;
|
using osu.Framework.Configuration.Tracking;
|
||||||
|
using osu.Framework.Graphics.Shaders;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -45,6 +46,14 @@ namespace osu.Game.Tests.Testing
|
|||||||
Dependencies.Get<ISampleStore>().Get(@"test-sample") != null);
|
Dependencies.Get<ISampleStore>().Get(@"test-sample") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRetrieveShader()
|
||||||
|
{
|
||||||
|
AddAssert("ruleset shaders retrieved", () =>
|
||||||
|
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs") != null &&
|
||||||
|
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs") != null);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestResolveConfigManager()
|
public void TestResolveConfigManager()
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,6 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.Break;
|
using osu.Game.Screens.Play.Break;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -36,18 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
||||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
||||||
|
|
||||||
double? time = null;
|
|
||||||
|
|
||||||
AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime);
|
|
||||||
|
|
||||||
// test seek via keyboard
|
|
||||||
AddStep("seek with right arrow key", () => InputManager.Key(Key.Right));
|
|
||||||
AddAssert("time seeked forward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime > time + 2000);
|
|
||||||
|
|
||||||
AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime);
|
|
||||||
AddStep("seek with left arrow key", () => InputManager.Key(Key.Left));
|
|
||||||
AddAssert("time seeked backward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < time);
|
|
||||||
|
|
||||||
seekToBreak(0);
|
seekToBreak(0);
|
||||||
seekToBreak(1);
|
seekToBreak(1);
|
||||||
|
|
||||||
|
@ -114,11 +114,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public bool ResultsCreated { get; private set; }
|
public bool ResultsCreated { get; private set; }
|
||||||
|
|
||||||
public FakeRankingPushPlayer()
|
|
||||||
: base(true, true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||||
{
|
{
|
||||||
var results = base.CreateResults(score);
|
var results = base.CreateResults(score);
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
showOverlay();
|
showOverlay();
|
||||||
|
|
||||||
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
||||||
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
|
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
showOverlay();
|
showOverlay();
|
||||||
|
|
||||||
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
||||||
AddAssert("First button selected", () => getButton(0).Selected.Value);
|
AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -111,11 +111,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Show overlay", () => failOverlay.Show());
|
AddStep("Show overlay", () => failOverlay.Show());
|
||||||
|
|
||||||
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
||||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected);
|
||||||
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
||||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected);
|
||||||
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
||||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -127,11 +127,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Show overlay", () => failOverlay.Show());
|
AddStep("Show overlay", () => failOverlay.Show());
|
||||||
|
|
||||||
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
||||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected);
|
||||||
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
||||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
AddAssert("Last button selected", () => failOverlay.Buttons.Last().State == SelectionState.Selected);
|
||||||
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
||||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
AddAssert("First button selected", () => failOverlay.Buttons.First().State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First()));
|
AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First()));
|
||||||
AddStep("Hide overlay", () => failOverlay.Hide());
|
AddStep("Hide overlay", () => failOverlay.Hide());
|
||||||
|
|
||||||
AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value));
|
AddAssert("Overlay state is reset", () => failOverlay.Buttons.All(b => b.State == SelectionState.NotSelected));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -162,11 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||||
showOverlay();
|
showOverlay();
|
||||||
|
|
||||||
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
|
AddAssert("First button not selected", () => getButton(0).State == SelectionState.NotSelected);
|
||||||
|
|
||||||
AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
|
AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
|
||||||
|
|
||||||
AddAssert("First button selected", () => getButton(0).Selected.Value);
|
AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
||||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
||||||
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
|
AddAssert("First button not selected", () => getButton(0).State == SelectionState.NotSelected);
|
||||||
AddAssert("Second button selected", () => getButton(1).Selected.Value);
|
AddAssert("Second button selected", () => getButton(1).State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -196,8 +196,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
||||||
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
AddStep("Up arrow", () => InputManager.Key(Key.Up));
|
||||||
AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
|
AddAssert("Second button not selected", () => getButton(1).State == SelectionState.NotSelected);
|
||||||
AddAssert("First button selected", () => getButton(0).Selected.Value);
|
AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
||||||
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
|
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||||
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
AddStep("Down arrow", () => InputManager.Key(Key.Down));
|
||||||
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
|
AddAssert("First button selected", () => getButton(0).State == SelectionState.Selected); // Initial state condition
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
showOverlay();
|
showOverlay();
|
||||||
|
|
||||||
AddAssert("No button selected",
|
AddAssert("No button selected",
|
||||||
() => pauseOverlay.Buttons.All(button => !button.Selected.Value));
|
() => pauseOverlay.Buttons.All(button => button.State == SelectionState.NotSelected));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
|
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
|
||||||
|
@ -1,38 +1,58 @@
|
|||||||
// 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 System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Online.Solo;
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
public class TestScenePlayerScoreSubmission : OsuPlayerTestScene
|
public class TestScenePlayerScoreSubmission : PlayerTestScene
|
||||||
{
|
{
|
||||||
protected override bool AllowFail => allowFail;
|
protected override bool AllowFail => allowFail;
|
||||||
|
|
||||||
private bool allowFail;
|
private bool allowFail;
|
||||||
|
|
||||||
|
private Func<RulesetInfo, IBeatmap> createCustomBeatmap;
|
||||||
|
private Func<Ruleset> createCustomRuleset;
|
||||||
|
|
||||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
protected override bool HasCustomSteps => true;
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => createCustomBeatmap?.Invoke(ruleset) ?? createTestBeatmap(ruleset);
|
||||||
|
|
||||||
|
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset);
|
||||||
|
|
||||||
|
beatmap.HitObjects = beatmap.HitObjects.Take(10).ToList();
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNoSubmissionOnResultsWithNoToken()
|
public void TestNoSubmissionOnResultsWithNoToken()
|
||||||
{
|
{
|
||||||
prepareTokenResponse(false);
|
prepareTokenResponse(false);
|
||||||
|
|
||||||
CreateTest(() => allowFail = false);
|
createPlayerTest();
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -52,7 +72,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
prepareTokenResponse(true);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
CreateTest(() => allowFail = false);
|
createPlayerTest();
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -71,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
prepareTokenResponse(false);
|
prepareTokenResponse(false);
|
||||||
|
|
||||||
CreateTest(() => allowFail = false);
|
createPlayerTest();
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -88,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
prepareTokenResponse(true);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
CreateTest(() => allowFail = true);
|
createPlayerTest(true);
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -103,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
prepareTokenResponse(true);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
CreateTest(() => allowFail = true);
|
createPlayerTest(true);
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -120,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
prepareTokenResponse(true);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
CreateTest(() => allowFail = false);
|
createPlayerTest();
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -133,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
prepareTokenResponse(true);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
CreateTest(() => allowFail = false);
|
createPlayerTest();
|
||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
@ -143,18 +163,49 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
|
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFakeHit()
|
[Test]
|
||||||
|
public void TestNoSubmissionOnLocalBeatmap()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for first result", () => Player.Results.Count > 0);
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
AddStep("force successfuly hit", () =>
|
createPlayerTest(false, r =>
|
||||||
{
|
{
|
||||||
Player.ScoreProcessor.RevertResult(Player.Results.First());
|
var beatmap = createTestBeatmap(r);
|
||||||
Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement())
|
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
||||||
{
|
return beatmap;
|
||||||
Type = HitResult.Great,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
|
addFakeHit();
|
||||||
|
|
||||||
|
AddStep("exit", () => Player.Exit());
|
||||||
|
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoSubmissionOnCustomRuleset()
|
||||||
|
{
|
||||||
|
prepareTokenResponse(true);
|
||||||
|
|
||||||
|
createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = 10 } });
|
||||||
|
|
||||||
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
|
addFakeHit();
|
||||||
|
|
||||||
|
AddStep("exit", () => Player.Exit());
|
||||||
|
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPlayerTest(bool allowFail = false, Func<RulesetInfo, IBeatmap> createBeatmap = null, Func<Ruleset> createRuleset = null)
|
||||||
|
{
|
||||||
|
CreateTest(() => AddStep("set up requirements", () =>
|
||||||
|
{
|
||||||
|
this.allowFail = allowFail;
|
||||||
|
createCustomBeatmap = createBeatmap;
|
||||||
|
createCustomRuleset = createRuleset;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareTokenResponse(bool validToken)
|
private void prepareTokenResponse(bool validToken)
|
||||||
@ -177,5 +228,19 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addFakeHit()
|
||||||
|
{
|
||||||
|
AddUntilStep("wait for first result", () => Player.Results.Count > 0);
|
||||||
|
|
||||||
|
AddStep("force successfuly hit", () =>
|
||||||
|
{
|
||||||
|
Player.ScoreProcessor.RevertResult(Player.Results.First());
|
||||||
|
Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement())
|
||||||
|
{
|
||||||
|
Type = HitResult.Great,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
Normal file
87
osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneReplayPlayer : RateAdjustedBeatmapTestScene
|
||||||
|
{
|
||||||
|
protected TestReplayPlayer Player;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("Initialise player", () => Player = CreatePlayer(new OsuRuleset()));
|
||||||
|
AddStep("Load player", () => LoadScreen(Player));
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPause()
|
||||||
|
{
|
||||||
|
double? lastTime = null;
|
||||||
|
|
||||||
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
|
|
||||||
|
AddStep("Pause playback", () => InputManager.Key(Key.Space));
|
||||||
|
|
||||||
|
AddUntilStep("Time stopped progressing", () =>
|
||||||
|
{
|
||||||
|
double current = Player.GameplayClockContainer.CurrentTime;
|
||||||
|
bool changed = lastTime != current;
|
||||||
|
lastTime = current;
|
||||||
|
|
||||||
|
return !changed;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 10);
|
||||||
|
|
||||||
|
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSeekBackwards()
|
||||||
|
{
|
||||||
|
double? lastTime = null;
|
||||||
|
|
||||||
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
|
|
||||||
|
AddStep("Seek backwards", () =>
|
||||||
|
{
|
||||||
|
lastTime = Player.GameplayClockContainer.CurrentTime;
|
||||||
|
InputManager.Key(Key.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Jumped backwards", () => Player.GameplayClockContainer.CurrentTime - lastTime < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSeekForwards()
|
||||||
|
{
|
||||||
|
double? lastTime = null;
|
||||||
|
|
||||||
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
|
|
||||||
|
AddStep("Seek forwards", () =>
|
||||||
|
{
|
||||||
|
lastTime = Player.GameplayClockContainer.CurrentTime;
|
||||||
|
InputManager.Key(Key.Right);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TestReplayPlayer CreatePlayer(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||||
|
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
|
||||||
|
|
||||||
|
return new TestReplayPlayer(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
new ExposedSkinnableDrawable("default", _ => new DefaultBox()),
|
new ExposedSkinnableDrawable("default", _ => new DefaultBox()),
|
||||||
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.ScaleToFit),
|
new ExposedSkinnableDrawable("available", _ => new DefaultBox()),
|
||||||
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling)
|
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void Disable()
|
public void Disable()
|
||||||
{
|
{
|
||||||
allow = false;
|
allow = false;
|
||||||
OnSourceChanged();
|
TriggerSourceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SwitchableSkinProvidingContainer(ISkin skin)
|
public SwitchableSkinProvidingContainer(ISkin skin)
|
||||||
|
@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
foreach (var legacyFrame in frames.Frames)
|
foreach (var legacyFrame in frames.Frames)
|
||||||
{
|
{
|
||||||
var frame = new TestReplayFrame();
|
var frame = new TestReplayFrame();
|
||||||
frame.FromLegacy(legacyFrame, null, null);
|
frame.FromLegacy(legacyFrame, null);
|
||||||
replay.Frames.Add(frame);
|
replay.Frames.Add(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
// Send initial frames for both players. A few more for player 1.
|
// Send initial frames for both players. A few more for player 1.
|
||||||
sendFrames(PLAYER_1_ID, 20);
|
sendFrames(PLAYER_1_ID, 20);
|
||||||
sendFrames(PLAYER_2_ID, 10);
|
sendFrames(PLAYER_2_ID);
|
||||||
checkPausedInstant(PLAYER_1_ID, false);
|
checkPausedInstant(PLAYER_1_ID, false);
|
||||||
checkPausedInstant(PLAYER_2_ID, false);
|
checkPausedInstant(PLAYER_2_ID, false);
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
assertMuted(PLAYER_1_ID, true);
|
assertMuted(PLAYER_1_ID, true);
|
||||||
assertMuted(PLAYER_2_ID, true);
|
assertMuted(PLAYER_2_ID, true);
|
||||||
|
|
||||||
sendFrames(PLAYER_1_ID, 10);
|
sendFrames(PLAYER_1_ID);
|
||||||
sendFrames(PLAYER_2_ID, 20);
|
sendFrames(PLAYER_2_ID, 20);
|
||||||
checkPaused(PLAYER_1_ID, false);
|
checkPaused(PLAYER_1_ID, false);
|
||||||
assertOneNotMuted();
|
assertOneNotMuted();
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -20,6 +21,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
@ -39,10 +41,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private TestMultiplayer multiplayerScreen;
|
private TestMultiplayer multiplayerScreen;
|
||||||
private TestMultiplayerClient client;
|
private TestMultiplayerClient client;
|
||||||
|
|
||||||
public TestSceneMultiplayer()
|
[Cached(typeof(UserLookupCache))]
|
||||||
{
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
loadMultiplayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
@ -51,18 +51,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
public override void SetUpSteps()
|
||||||
public void Setup() => Schedule(() =>
|
|
||||||
{
|
{
|
||||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
base.SetUpSteps();
|
||||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
|
||||||
});
|
AddStep("import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
|
||||||
|
|
||||||
|
AddStep("load dependencies", () =>
|
||||||
|
{
|
||||||
|
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
|
||||||
|
|
||||||
|
// The screen gets suspended so it stops receiving updates.
|
||||||
|
Child = client;
|
||||||
|
|
||||||
|
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
|
||||||
|
|
||||||
|
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
|
||||||
|
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmpty()
|
||||||
|
{
|
||||||
|
// used to test the flow of multiplayer from visual tests.
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUserSetToIdleWhenBeatmapDeleted()
|
public void TestUserSetToIdleWhenBeatmapDeleted()
|
||||||
{
|
{
|
||||||
loadMultiplayer();
|
|
||||||
|
|
||||||
createRoom(() => new Room
|
createRoom(() => new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
@ -85,8 +110,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
|
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
|
||||||
{
|
{
|
||||||
loadMultiplayer();
|
|
||||||
|
|
||||||
createRoom(() => new Room
|
createRoom(() => new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
@ -123,8 +146,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable()
|
public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable()
|
||||||
{
|
{
|
||||||
loadMultiplayer();
|
|
||||||
|
|
||||||
createRoom(() => new Room
|
createRoom(() => new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
@ -164,11 +185,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen());
|
AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSubScreenExitedWhenDisconnectedFromMultiplayerServer()
|
||||||
|
{
|
||||||
|
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 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("disconnect", () => client.Disconnect());
|
||||||
|
AddUntilStep("back in lounge", () => this.ChildrenOfType<LoungeSubScreen>().FirstOrDefault()?.IsCurrentScreen() == true);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLeaveNavigation()
|
public void TestLeaveNavigation()
|
||||||
{
|
{
|
||||||
loadMultiplayer();
|
|
||||||
|
|
||||||
createRoom(() => new Room
|
createRoom(() => new Room
|
||||||
{
|
{
|
||||||
Name = { Value = "Test Room" },
|
Name = { Value = "Test Room" },
|
||||||
@ -227,26 +266,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("wait for join", () => client.Room != null);
|
AddUntilStep("wait for join", () => client.Room != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMultiplayer()
|
|
||||||
{
|
|
||||||
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
|
|
||||||
|
|
||||||
AddStep("load dependencies", () =>
|
|
||||||
{
|
|
||||||
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
|
|
||||||
|
|
||||||
// The screen gets suspended so it stops receiving updates.
|
|
||||||
Child = client;
|
|
||||||
|
|
||||||
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
|
|
||||||
|
|
||||||
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
|
|
||||||
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
|
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -113,10 +113,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
|
AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
verifyGameplayStartFlow();
|
verifyGameplayStartFlow();
|
||||||
}
|
}
|
||||||
@ -206,8 +206,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void verifyGameplayStartFlow()
|
private void verifyGameplayStartFlow()
|
||||||
{
|
{
|
||||||
|
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
AddUntilStep("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||||
|
|
||||||
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
||||||
@ -218,7 +219,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay);
|
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
AddUntilStep("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public void TestToggleWhenIdle(MultiplayerUserState initialState)
|
public void TestToggleWhenIdle(MultiplayerUserState initialState)
|
||||||
{
|
{
|
||||||
addClickSpectateButtonStep();
|
addClickSpectateButtonStep();
|
||||||
AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
|
AddUntilStep("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
addClickSpectateButtonStep();
|
addClickSpectateButtonStep();
|
||||||
AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
|
AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(MultiplayerRoomState.Closed)]
|
[TestCase(MultiplayerRoomState.Closed)]
|
||||||
@ -174,9 +174,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
private void assertSpectateButtonEnablement(bool shouldBeEnabled)
|
private void assertSpectateButtonEnablement(bool shouldBeEnabled)
|
||||||
=> AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
=> AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||||
|
|
||||||
private void assertReadyButtonEnablement(bool shouldBeEnabled)
|
private void assertReadyButtonEnablement(bool shouldBeEnabled)
|
||||||
=> AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
=> AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtSongSelectFromPlayerLoader()
|
public void TestPerformAtSongSelectFromPlayerLoader()
|
||||||
{
|
{
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
importAndWaitForSongSelect();
|
||||||
PushAndConfirm(() => new TestPlaySongSelect());
|
|
||||||
|
|
||||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||||
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||||
@ -72,8 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtMenuFromPlayerLoader()
|
public void TestPerformAtMenuFromPlayerLoader()
|
||||||
{
|
{
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
importAndWaitForSongSelect();
|
||||||
PushAndConfirm(() => new TestPlaySongSelect());
|
|
||||||
|
|
||||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||||
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||||
@ -172,6 +170,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void importAndWaitForSongSelect()
|
||||||
|
{
|
||||||
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526);
|
||||||
|
}
|
||||||
|
|
||||||
public class DialogBlockingScreen : OsuScreen
|
public class DialogBlockingScreen : OsuScreen
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Add(new DirectorySelector { RelativeSizeAxes = Axes.Both });
|
Add(new OsuDirectorySelector { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAllFiles()
|
public void TestAllFiles()
|
||||||
{
|
{
|
||||||
AddStep("create", () => Child = new FileSelector { RelativeSizeAxes = Axes.Both });
|
AddStep("create", () => Child = new OsuFileSelector { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestJpgFilesOnly()
|
public void TestJpgFilesOnly()
|
||||||
{
|
{
|
||||||
AddStep("create", () => Child = new FileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both });
|
AddStep("create", () => Child = new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -17,28 +16,65 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRestoreDefaultValueButtonVisibility()
|
public void TestRestoreDefaultValueButtonVisibility()
|
||||||
{
|
{
|
||||||
TestSettingsTextBox textBox = null;
|
SettingsTextBox textBox = null;
|
||||||
|
RestoreDefaultValueButton<string> restoreDefaultValueButton = null;
|
||||||
|
|
||||||
AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox
|
AddStep("create settings item", () =>
|
||||||
{
|
{
|
||||||
Current = new Bindable<string>
|
Child = textBox = new SettingsTextBox
|
||||||
{
|
{
|
||||||
Default = "test",
|
Current = new Bindable<string>
|
||||||
Value = "test"
|
{
|
||||||
}
|
Default = "test",
|
||||||
|
Value = "test"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
restoreDefaultValueButton = textBox.ChildrenOfType<RestoreDefaultValueButton<string>>().Single();
|
||||||
});
|
});
|
||||||
AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
|
AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
|
||||||
|
|
||||||
AddStep("change value from default", () => textBox.Current.Value = "non-default");
|
AddStep("change value from default", () => textBox.Current.Value = "non-default");
|
||||||
AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0);
|
AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0);
|
||||||
|
|
||||||
AddStep("restore default", () => textBox.Current.SetDefault());
|
AddStep("restore default", () => textBox.Current.SetDefault());
|
||||||
AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
|
AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSettingsTextBox : SettingsTextBox
|
/// <summary>
|
||||||
|
/// Ensures that the reset to default button uses the correct implementation of IsDefault to determine whether it should be shown or not.
|
||||||
|
/// Values have been chosen so that after being set, Value != Default (but they are close enough that the difference is negligible compared to Precision).
|
||||||
|
/// </summary>
|
||||||
|
[TestCase(4.2f)]
|
||||||
|
[TestCase(9.9f)]
|
||||||
|
public void TestRestoreDefaultValueButtonPrecision(float initialValue)
|
||||||
{
|
{
|
||||||
public Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton<string>>().Single();
|
BindableFloat current = null;
|
||||||
|
SettingsSlider<float> sliderBar = null;
|
||||||
|
RestoreDefaultValueButton<float> restoreDefaultValueButton = null;
|
||||||
|
|
||||||
|
AddStep("create settings item", () =>
|
||||||
|
{
|
||||||
|
Child = sliderBar = new SettingsSlider<float>
|
||||||
|
{
|
||||||
|
Current = current = new BindableFloat(initialValue)
|
||||||
|
{
|
||||||
|
MinValue = 0f,
|
||||||
|
MaxValue = 10f,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
restoreDefaultValueButton = sliderBar.ChildrenOfType<RestoreDefaultValueButton<float>>().Single();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
|
||||||
|
|
||||||
|
AddStep("change value to next closest", () => sliderBar.Current.Value += current.Precision * 0.6f);
|
||||||
|
AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0);
|
||||||
|
|
||||||
|
AddStep("restore default", () => sliderBar.Current.SetDefault());
|
||||||
|
AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
|
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLocal([Values("Beatmap", "Some long title and stuff")]
|
public void TestLocal(
|
||||||
string title,
|
[Values("Beatmap", "Some long title and stuff")]
|
||||||
[Values("Trial", "Some1's very hardest difficulty")]
|
string title,
|
||||||
string version)
|
[Values("Trial", "Some1's very hardest difficulty")]
|
||||||
|
string version)
|
||||||
{
|
{
|
||||||
showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap
|
showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneModDifficultyAdjustSettings : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private OsuModDifficultyAdjust modDifficultyAdjust;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("create control", () =>
|
||||||
|
{
|
||||||
|
modDifficultyAdjust = new OsuModDifficultyAdjust();
|
||||||
|
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
Size = new Vector2(300),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
ChildrenEnumerable = modDifficultyAdjust.CreateSettingsControls(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFollowsBeatmapDefaultsVisually()
|
||||||
|
{
|
||||||
|
setBeatmapWithDifficultyParameters(5);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 5);
|
||||||
|
checkBindableAtValue("Circle Size", null);
|
||||||
|
|
||||||
|
setBeatmapWithDifficultyParameters(8);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 8);
|
||||||
|
checkBindableAtValue("Circle Size", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOutOfRangeValueStillApplied()
|
||||||
|
{
|
||||||
|
AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 11);
|
||||||
|
checkBindableAtValue("Circle Size", 11);
|
||||||
|
|
||||||
|
// this is a no-op, just showing that it won't reset the value during deserialisation.
|
||||||
|
setExtendedLimits(false);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 11);
|
||||||
|
checkBindableAtValue("Circle Size", 11);
|
||||||
|
|
||||||
|
// setting extended limits will reset the serialisation exception.
|
||||||
|
// this should be fine as the goal is to allow, at most, the value of extended limits.
|
||||||
|
setExtendedLimits(true);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 11);
|
||||||
|
checkBindableAtValue("Circle Size", 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExtendedLimits()
|
||||||
|
{
|
||||||
|
setSliderValue("Circle Size", 99);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 10);
|
||||||
|
checkBindableAtValue("Circle Size", 10);
|
||||||
|
|
||||||
|
setExtendedLimits(true);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 10);
|
||||||
|
checkBindableAtValue("Circle Size", 10);
|
||||||
|
|
||||||
|
setSliderValue("Circle Size", 99);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 11);
|
||||||
|
checkBindableAtValue("Circle Size", 11);
|
||||||
|
|
||||||
|
setExtendedLimits(false);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 10);
|
||||||
|
checkBindableAtValue("Circle Size", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserOverrideMaintainedOnBeatmapChange()
|
||||||
|
{
|
||||||
|
setSliderValue("Circle Size", 9);
|
||||||
|
|
||||||
|
setBeatmapWithDifficultyParameters(2);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 9);
|
||||||
|
checkBindableAtValue("Circle Size", 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResetToDefault()
|
||||||
|
{
|
||||||
|
setBeatmapWithDifficultyParameters(2);
|
||||||
|
|
||||||
|
setSliderValue("Circle Size", 9);
|
||||||
|
checkSliderAtValue("Circle Size", 9);
|
||||||
|
checkBindableAtValue("Circle Size", 9);
|
||||||
|
|
||||||
|
resetToDefault("Circle Size");
|
||||||
|
checkSliderAtValue("Circle Size", 2);
|
||||||
|
checkBindableAtValue("Circle Size", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserOverrideMaintainedOnMatchingBeatmapValue()
|
||||||
|
{
|
||||||
|
setBeatmapWithDifficultyParameters(3);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 3);
|
||||||
|
checkBindableAtValue("Circle Size", null);
|
||||||
|
|
||||||
|
// need to initially change it away from the current beatmap value to trigger an override.
|
||||||
|
setSliderValue("Circle Size", 4);
|
||||||
|
setSliderValue("Circle Size", 3);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 3);
|
||||||
|
checkBindableAtValue("Circle Size", 3);
|
||||||
|
|
||||||
|
setBeatmapWithDifficultyParameters(4);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 3);
|
||||||
|
checkBindableAtValue("Circle Size", 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResetToDefaults()
|
||||||
|
{
|
||||||
|
setBeatmapWithDifficultyParameters(5);
|
||||||
|
|
||||||
|
setSliderValue("Circle Size", 3);
|
||||||
|
setExtendedLimits(true);
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 3);
|
||||||
|
checkBindableAtValue("Circle Size", 3);
|
||||||
|
|
||||||
|
AddStep("reset mod settings", () => modDifficultyAdjust.ResetSettingsToDefaults());
|
||||||
|
|
||||||
|
checkSliderAtValue("Circle Size", 5);
|
||||||
|
checkBindableAtValue("Circle Size", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetToDefault(string name)
|
||||||
|
{
|
||||||
|
AddStep($"Reset {name} to default", () =>
|
||||||
|
this.ChildrenOfType<DifficultyAdjustSettingsControl>().First(c => c.LabelText == name)
|
||||||
|
.Current.SetDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setExtendedLimits(bool status) =>
|
||||||
|
AddStep($"Set extended limits {status}", () => modDifficultyAdjust.ExtendedLimits.Value = status);
|
||||||
|
|
||||||
|
private void setSliderValue(string name, float value)
|
||||||
|
{
|
||||||
|
AddStep($"Set {name} slider to {value}", () =>
|
||||||
|
this.ChildrenOfType<DifficultyAdjustSettingsControl>().First(c => c.LabelText == name)
|
||||||
|
.ChildrenOfType<SettingsSlider<float>>().First().Current.Value = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBindableAtValue(string name, float? expectedValue)
|
||||||
|
{
|
||||||
|
AddAssert($"Bindable {name} is {(expectedValue?.ToString() ?? "null")}", () =>
|
||||||
|
this.ChildrenOfType<DifficultyAdjustSettingsControl>().First(c => c.LabelText == name)
|
||||||
|
.Current.Value == expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSliderAtValue(string name, float expectedValue)
|
||||||
|
{
|
||||||
|
AddAssert($"Slider {name} at {expectedValue}", () =>
|
||||||
|
this.ChildrenOfType<DifficultyAdjustSettingsControl>().First(c => c.LabelText == name)
|
||||||
|
.ChildrenOfType<SettingsSlider<float>>().First().Current.Value == expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBeatmapWithDifficultyParameters(float value)
|
||||||
|
{
|
||||||
|
AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
OverallDifficulty = value,
|
||||||
|
CircleSize = value,
|
||||||
|
DrainRate = value,
|
||||||
|
ApproachRate = value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
@ -50,6 +51,38 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("show", () => modSelect.Show());
|
AddStep("show", () => modSelect.Show());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure that two mod overlays are not cross polluting via central settings instances.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSettingsNotCrossPolluting()
|
||||||
|
{
|
||||||
|
Bindable<IReadOnlyList<Mod>> selectedMods2 = null;
|
||||||
|
|
||||||
|
AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
|
||||||
|
|
||||||
|
AddStep("set setting", () => modSelect.ChildrenOfType<SettingsSlider<float>>().First().Current.Value = 8);
|
||||||
|
|
||||||
|
AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
|
||||||
|
|
||||||
|
AddStep("create second bindable", () => selectedMods2 = new Bindable<IReadOnlyList<Mod>>(new Mod[] { new OsuModDifficultyAdjust() }));
|
||||||
|
|
||||||
|
AddStep("create second overlay", () =>
|
||||||
|
{
|
||||||
|
Add(modSelect = new TestModSelectOverlay().With(d =>
|
||||||
|
{
|
||||||
|
d.Origin = Anchor.TopCentre;
|
||||||
|
d.Anchor = Anchor.TopCentre;
|
||||||
|
d.SelectedMods.BindTarget = selectedMods2;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("show", () => modSelect.Show());
|
||||||
|
|
||||||
|
AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
|
||||||
|
AddAssert("ensure second is default", () => selectedMods2.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == null);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSettingsResetOnDeselection()
|
public void TestSettingsResetOnDeselection()
|
||||||
{
|
{
|
||||||
|
@ -95,6 +95,15 @@ _**italic with underscore, bold with asterisk**_";
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutoLink()
|
||||||
|
{
|
||||||
|
AddStep("Add autolink", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = "<https://discord.gg/ppy>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestInlineCode()
|
public void TestInlineCode()
|
||||||
{
|
{
|
||||||
|
103
osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs
Normal file
103
osu.Game.Tests/Visual/UserInterface/TestSceneOsuPopover.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneOsuPopover : OsuGridTestScene
|
||||||
|
{
|
||||||
|
public TestSceneOsuPopover()
|
||||||
|
: base(1, 2)
|
||||||
|
{
|
||||||
|
Cell(0, 0).Child = new PopoverContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = @"No OverlayColourProvider",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
new TriangleButtonWithPopover()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Cell(0, 1).Child = new ColourProvidingContainer(OverlayColourScheme.Orange)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new PopoverContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = @"With OverlayColourProvider (orange)",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
new TriangleButtonWithPopover()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TriangleButtonWithPopover : TriangleButton, IHasPopover
|
||||||
|
{
|
||||||
|
public TriangleButtonWithPopover()
|
||||||
|
{
|
||||||
|
Width = 100;
|
||||||
|
Height = 30;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Text = @"open";
|
||||||
|
Action = this.ShowPopover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new OsuPopover
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = @"sample text"
|
||||||
|
},
|
||||||
|
new OsuTextBox
|
||||||
|
{
|
||||||
|
Width = 150,
|
||||||
|
Height = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ColourProvidingContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider provider { get; }
|
||||||
|
|
||||||
|
public ColourProvidingContainer(OverlayColourScheme colourScheme)
|
||||||
|
{
|
||||||
|
provider = new OverlayColourProvider(colourScheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestInitialVisibility()
|
public void TestInitialVisibility()
|
||||||
{
|
{
|
||||||
AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 0));
|
AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero));
|
||||||
AddAssert("Value is 0", () => header.Current.Value == 0);
|
AddAssert("Value is 0", () => header.Current.Value == 0);
|
||||||
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Volume;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneVolumeOverlay : OsuTestScene
|
||||||
|
{
|
||||||
|
private VolumeOverlay volume;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
volume = new VolumeOverlay(),
|
||||||
|
new VolumeControlReceptor
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ActionRequested = action => volume.Adjust(action),
|
||||||
|
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("show controls", () => volume.Show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDefaultDirectory()
|
public void TestDefaultDirectory()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory)))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -139,8 +139,13 @@ namespace osu.Game.Tournament.Tests.NonVisual
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
host.Storage.Delete("tournament.ini");
|
try
|
||||||
host.Storage.DeleteDirectory("tournaments");
|
{
|
||||||
|
host.Storage.Delete("tournament.ini");
|
||||||
|
host.Storage.DeleteDirectory("tournaments");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
host.Exit();
|
host.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
|||||||
{
|
{
|
||||||
chat?.Contract();
|
chat?.Contract();
|
||||||
|
|
||||||
using (BeginDelayedSequence(300, true))
|
using (BeginDelayedSequence(300))
|
||||||
{
|
{
|
||||||
scoreDisplay.FadeIn(100);
|
scoreDisplay.FadeIn(100);
|
||||||
SongBar.Expanded = true;
|
SongBar.Expanded = true;
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MatchIPCInfo ipc { get; set; }
|
private MatchIPCInfo ipc { get; set; }
|
||||||
|
|
||||||
private DirectorySelector directorySelector;
|
private OsuDirectorySelector directorySelector;
|
||||||
private DialogOverlay overlay;
|
private DialogOverlay overlay;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -79,7 +79,7 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
directorySelector = new DirectorySelector(initialPath)
|
directorySelector = new OsuDirectorySelector(initialPath)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
}
|
}
|
||||||
|
2
osu.Game/.editorconfig
Normal file
2
osu.Game/.editorconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[*.cs]
|
||||||
|
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
|
@ -27,6 +27,30 @@ namespace osu.Game.Database
|
|||||||
[ItemCanBeNull]
|
[ItemCanBeNull]
|
||||||
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
|
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform an API lookup on the specified users, populating a <see cref="User"/> model.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userIds">The users to lookup.</param>
|
||||||
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
|
/// <returns>The populated users. May include null results for failed retrievals.</returns>
|
||||||
|
public Task<User[]> GetUsersAsync(int[] userIds, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var userLookupTasks = new List<Task<User>>();
|
||||||
|
|
||||||
|
foreach (var u in userIds)
|
||||||
|
{
|
||||||
|
userLookupTasks.Add(GetUserAsync(u, token).ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (!task.IsCompletedSuccessfully)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return task.Result;
|
||||||
|
}, token));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.WhenAll(userLookupTasks);
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
protected override async Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||||
=> await queryUser(lookup).ConfigureAwait(false);
|
=> await queryUser(lookup).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
|
using (BeginDelayedSequence(-TimeSinceLastBeat))
|
||||||
OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty);
|
OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty);
|
||||||
|
|
||||||
lastBeat = beatIndex;
|
lastBeat = beatIndex;
|
||||||
|
@ -26,6 +26,12 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
title = linkInline.Title;
|
title = linkInline.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OsuMarkdownLinkText(AutolinkInline autolinkInline)
|
||||||
|
: base(autolinkInline)
|
||||||
|
{
|
||||||
|
text = autolinkInline.Url;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,9 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
protected override void AddLinkText(string text, LinkInline linkInline)
|
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||||
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
|
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
|
||||||
|
|
||||||
|
protected override void AddAutoLink(AutolinkInline autolinkInline)
|
||||||
|
=> AddDrawable(new OsuMarkdownLinkText(autolinkInline));
|
||||||
|
|
||||||
protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline));
|
protected override void AddImage(LinkInline linkInline) => AddDrawable(new OsuMarkdownImage(linkInline));
|
||||||
|
|
||||||
// TODO : Change font to monospace
|
// TODO : Change font to monospace
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet);
|
protected virtual HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet);
|
||||||
|
|
||||||
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
||||||
{
|
{
|
||||||
@ -39,7 +39,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
content,
|
content,
|
||||||
CreateHoverClickSounds(sampleSet)
|
CreateHoverSounds(sampleSet)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Containers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A FillFlowContainer that provides functionality to cycle selection between children
|
||||||
|
/// The selection wraps around when overflowing past the first or last child.
|
||||||
|
/// </summary>
|
||||||
|
public class SelectionCycleFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable, IStateful<SelectionState>
|
||||||
|
{
|
||||||
|
public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex.Value] : null;
|
||||||
|
|
||||||
|
private int? selectedIndex;
|
||||||
|
|
||||||
|
public void SelectNext()
|
||||||
|
{
|
||||||
|
if (!selectedIndex.HasValue || selectedIndex == Count - 1)
|
||||||
|
setSelected(0);
|
||||||
|
else
|
||||||
|
setSelected(selectedIndex.Value + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectPrevious()
|
||||||
|
{
|
||||||
|
if (!selectedIndex.HasValue || selectedIndex == 0)
|
||||||
|
setSelected(Count - 1);
|
||||||
|
else
|
||||||
|
setSelected(selectedIndex.Value - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deselect() => setSelected(null);
|
||||||
|
|
||||||
|
public void Select(T item)
|
||||||
|
{
|
||||||
|
var newIndex = IndexOf(item);
|
||||||
|
|
||||||
|
if (newIndex < 0)
|
||||||
|
setSelected(null);
|
||||||
|
else
|
||||||
|
setSelected(IndexOf(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Add(T drawable)
|
||||||
|
{
|
||||||
|
base.Add(drawable);
|
||||||
|
|
||||||
|
Debug.Assert(drawable != null);
|
||||||
|
|
||||||
|
drawable.StateChanged += state => selectionChanged(drawable, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Remove(T drawable)
|
||||||
|
=> throw new NotSupportedException($"Cannot remove drawables from {nameof(SelectionCycleFillFlowContainer<T>)}");
|
||||||
|
|
||||||
|
private void setSelected(int? value)
|
||||||
|
{
|
||||||
|
if (selectedIndex == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Deselect the previously-selected button
|
||||||
|
if (selectedIndex.HasValue)
|
||||||
|
this[selectedIndex.Value].State = SelectionState.NotSelected;
|
||||||
|
|
||||||
|
selectedIndex = value;
|
||||||
|
|
||||||
|
// Select the newly-selected button
|
||||||
|
if (selectedIndex.HasValue)
|
||||||
|
this[selectedIndex.Value].State = SelectionState.Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectionChanged(T drawable, SelectionState state)
|
||||||
|
{
|
||||||
|
if (state == SelectionState.NotSelected)
|
||||||
|
Deselect();
|
||||||
|
else
|
||||||
|
Select(drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -198,8 +199,14 @@ namespace osu.Game.Graphics
|
|||||||
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
|
||||||
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
|
||||||
|
|
||||||
// in latest editor design logic, need to figure out where these sit...
|
/// <summary>
|
||||||
|
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>.
|
||||||
|
/// </summary>
|
||||||
public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66");
|
public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Equivalent to <see cref="OverlayColourProvider.Orange"/>'s <see cref="OverlayColourProvider.Colour1"/>.
|
||||||
|
/// </summary>
|
||||||
public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966");
|
public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966");
|
||||||
|
|
||||||
// Content Background
|
// Content Background
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
// 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 osu.Framework.Bindables;
|
using System;
|
||||||
using osuTK;
|
using osu.Framework;
|
||||||
using osuTK.Graphics;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Backgrounds;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class DialogButton : OsuClickableContainer
|
public class DialogButton : OsuClickableContainer, IStateful<SelectionState>
|
||||||
{
|
{
|
||||||
private const float idle_width = 0.8f;
|
private const float idle_width = 0.8f;
|
||||||
private const float hover_width = 0.9f;
|
private const float hover_width = 0.9f;
|
||||||
@ -27,7 +28,22 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private const float hover_duration = 500;
|
private const float hover_duration = 500;
|
||||||
private const float click_duration = 200;
|
private const float click_duration = 200;
|
||||||
|
|
||||||
public readonly BindableBool Selected = new BindableBool();
|
public event Action<SelectionState> StateChanged;
|
||||||
|
|
||||||
|
private SelectionState state;
|
||||||
|
|
||||||
|
public SelectionState State
|
||||||
|
{
|
||||||
|
get => state;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (state == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state = value;
|
||||||
|
StateChanged?.Invoke(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Container backgroundContainer;
|
private readonly Container backgroundContainer;
|
||||||
private readonly Container colourContainer;
|
private readonly Container colourContainer;
|
||||||
@ -153,7 +169,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
updateGlow();
|
updateGlow();
|
||||||
|
|
||||||
Selected.ValueChanged += selectionChanged;
|
StateChanged += selectionChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 buttonColour;
|
private Color4 buttonColour;
|
||||||
@ -221,7 +237,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
.OnComplete(_ =>
|
.OnComplete(_ =>
|
||||||
{
|
{
|
||||||
clickAnimating = false;
|
clickAnimating = false;
|
||||||
Selected.TriggerChange();
|
StateChanged?.Invoke(State);
|
||||||
});
|
});
|
||||||
|
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
@ -235,7 +251,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
{
|
{
|
||||||
if (Selected.Value)
|
if (State == SelectionState.Selected)
|
||||||
colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
|
colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
|
||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
@ -243,7 +259,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
base.OnHover(e);
|
base.OnHover(e);
|
||||||
Selected.Value = true;
|
State = SelectionState.Selected;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -251,15 +267,15 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
Selected.Value = false;
|
State = SelectionState.NotSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectionChanged(ValueChangedEvent<bool> args)
|
private void selectionChanged(SelectionState newState)
|
||||||
{
|
{
|
||||||
if (clickAnimating)
|
if (clickAnimating)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (args.NewValue)
|
if (newState == SelectionState.Selected)
|
||||||
{
|
{
|
||||||
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
|
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
|
||||||
colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic);
|
colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic);
|
||||||
|
@ -113,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
double delay = (current <= newValue ? Math.Max(i - current, 0) : Math.Max(current - 1 - i, 0)) * AnimationDelay;
|
double delay = (current <= newValue ? Math.Max(i - current, 0) : Math.Max(current - 1 - i, 0)) * AnimationDelay;
|
||||||
|
|
||||||
using (star.BeginDelayedSequence(delay, true))
|
using (star.BeginDelayedSequence(delay))
|
||||||
star.DisplayAt(getStarScale(i, newValue));
|
star.DisplayAt(getStarScale(i, newValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,297 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
|
||||||
{
|
|
||||||
public class DirectorySelector : CompositeDrawable
|
|
||||||
{
|
|
||||||
private FillFlowContainer directoryFlow;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private GameHost host { get; set; }
|
|
||||||
|
|
||||||
[Cached]
|
|
||||||
public readonly Bindable<DirectoryInfo> CurrentPath = new Bindable<DirectoryInfo>();
|
|
||||||
|
|
||||||
public DirectorySelector(string initialPath = null)
|
|
||||||
{
|
|
||||||
CurrentPath.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Padding = new MarginPadding(10);
|
|
||||||
|
|
||||||
InternalChild = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.Absolute, 50),
|
|
||||||
new Dimension(),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new CurrentDirectoryDisplay
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new OsuScrollContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = directoryFlow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(2),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CurrentPath.BindValueChanged(updateDisplay, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDisplay(ValueChangedEvent<DirectoryInfo> directory)
|
|
||||||
{
|
|
||||||
directoryFlow.Clear();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (directory.NewValue == null)
|
|
||||||
{
|
|
||||||
var drives = DriveInfo.GetDrives();
|
|
||||||
|
|
||||||
foreach (var drive in drives)
|
|
||||||
directoryFlow.Add(new DirectoryPiece(drive.RootDirectory));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
directoryFlow.Add(new ParentDirectoryPiece(CurrentPath.Value.Parent));
|
|
||||||
|
|
||||||
directoryFlow.AddRange(GetEntriesForPath(CurrentPath.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
CurrentPath.Value = directory.OldValue;
|
|
||||||
this.FlashColour(Color4.Red, 300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual IEnumerable<DisplayPiece> GetEntriesForPath(DirectoryInfo path)
|
|
||||||
{
|
|
||||||
foreach (var dir in path.GetDirectories().OrderBy(d => d.Name))
|
|
||||||
{
|
|
||||||
if ((dir.Attributes & FileAttributes.Hidden) == 0)
|
|
||||||
yield return new DirectoryPiece(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CurrentDirectoryDisplay : CompositeDrawable
|
|
||||||
{
|
|
||||||
[Resolved]
|
|
||||||
private Bindable<DirectoryInfo> currentDirectory { get; set; }
|
|
||||||
|
|
||||||
private FillFlowContainer flow;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
flow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Spacing = new Vector2(5),
|
|
||||||
Height = DisplayPiece.HEIGHT,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
currentDirectory.BindValueChanged(updateDisplay, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDisplay(ValueChangedEvent<DirectoryInfo> dir)
|
|
||||||
{
|
|
||||||
flow.Clear();
|
|
||||||
|
|
||||||
List<DirectoryPiece> pathPieces = new List<DirectoryPiece>();
|
|
||||||
|
|
||||||
DirectoryInfo ptr = dir.NewValue;
|
|
||||||
|
|
||||||
while (ptr != null)
|
|
||||||
{
|
|
||||||
pathPieces.Insert(0, new CurrentDisplayPiece(ptr));
|
|
||||||
ptr = ptr.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
flow.ChildrenEnumerable = new Drawable[]
|
|
||||||
{
|
|
||||||
new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DisplayPiece.HEIGHT), },
|
|
||||||
new ComputerPiece(),
|
|
||||||
}.Concat(pathPieces);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ComputerPiece : CurrentDisplayPiece
|
|
||||||
{
|
|
||||||
protected override IconUsage? Icon => null;
|
|
||||||
|
|
||||||
public ComputerPiece()
|
|
||||||
: base(null, "Computer")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CurrentDisplayPiece : DirectoryPiece
|
|
||||||
{
|
|
||||||
public CurrentDisplayPiece(DirectoryInfo directory, string displayName = null)
|
|
||||||
: base(directory, displayName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Flow.Add(new SpriteIcon
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Icon = FontAwesome.Solid.ChevronRight,
|
|
||||||
Size = new Vector2(FONT_SIZE / 2)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ParentDirectoryPiece : DirectoryPiece
|
|
||||||
{
|
|
||||||
protected override IconUsage? Icon => FontAwesome.Solid.Folder;
|
|
||||||
|
|
||||||
public ParentDirectoryPiece(DirectoryInfo directory)
|
|
||||||
: base(directory, "..")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class DirectoryPiece : DisplayPiece
|
|
||||||
{
|
|
||||||
protected readonly DirectoryInfo Directory;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Bindable<DirectoryInfo> currentDirectory { get; set; }
|
|
||||||
|
|
||||||
public DirectoryPiece(DirectoryInfo directory, string displayName = null)
|
|
||||||
: base(displayName)
|
|
||||||
{
|
|
||||||
Directory = directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
currentDirectory.Value = Directory;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string FallbackName => Directory.Name;
|
|
||||||
|
|
||||||
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar)
|
|
||||||
? FontAwesome.Solid.Database
|
|
||||||
: FontAwesome.Regular.Folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract class DisplayPiece : CompositeDrawable
|
|
||||||
{
|
|
||||||
public const float HEIGHT = 20;
|
|
||||||
|
|
||||||
protected const float FONT_SIZE = 16;
|
|
||||||
|
|
||||||
private readonly string displayName;
|
|
||||||
|
|
||||||
protected FillFlowContainer Flow;
|
|
||||||
|
|
||||||
protected DisplayPiece(string displayName = null)
|
|
||||||
{
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
Masking = true;
|
|
||||||
CornerRadius = 5;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = colours.GreySeafoamDarker,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
Flow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
Height = 20,
|
|
||||||
Margin = new MarginPadding { Vertical = 2, Horizontal = 5 },
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(5),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Icon.HasValue)
|
|
||||||
{
|
|
||||||
Flow.Add(new SpriteIcon
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Icon = Icon.Value,
|
|
||||||
Size = new Vector2(FONT_SIZE)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.Add(new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Text = displayName ?? FallbackName,
|
|
||||||
Font = OsuFont.Default.With(size: FONT_SIZE)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract string FallbackName { get; }
|
|
||||||
|
|
||||||
protected abstract IconUsage? Icon { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
|
||||||
{
|
|
||||||
public class FileSelector : DirectorySelector
|
|
||||||
{
|
|
||||||
private readonly string[] validFileExtensions;
|
|
||||||
|
|
||||||
[Cached]
|
|
||||||
public readonly Bindable<FileInfo> CurrentFile = new Bindable<FileInfo>();
|
|
||||||
|
|
||||||
public FileSelector(string initialPath = null, string[] validFileExtensions = null)
|
|
||||||
: base(initialPath)
|
|
||||||
{
|
|
||||||
this.validFileExtensions = validFileExtensions ?? Array.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<DisplayPiece> GetEntriesForPath(DirectoryInfo path)
|
|
||||||
{
|
|
||||||
foreach (var dir in base.GetEntriesForPath(path))
|
|
||||||
yield return dir;
|
|
||||||
|
|
||||||
IEnumerable<FileInfo> files = path.GetFiles();
|
|
||||||
|
|
||||||
if (validFileExtensions.Length > 0)
|
|
||||||
files = files.Where(f => validFileExtensions.Contains(f.Extension));
|
|
||||||
|
|
||||||
foreach (var file in files.OrderBy(d => d.Name))
|
|
||||||
{
|
|
||||||
if ((file.Attributes & FileAttributes.Hidden) == 0)
|
|
||||||
yield return new FilePiece(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class FilePiece : DisplayPiece
|
|
||||||
{
|
|
||||||
private readonly FileInfo file;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Bindable<FileInfo> currentFile { get; set; }
|
|
||||||
|
|
||||||
public FilePiece(FileInfo file)
|
|
||||||
{
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
currentFile.Value = file;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string FallbackName => file.Name;
|
|
||||||
|
|
||||||
protected override IconUsage? Icon
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
switch (file.Extension)
|
|
||||||
{
|
|
||||||
case ".ogg":
|
|
||||||
case ".mp3":
|
|
||||||
case ".wav":
|
|
||||||
return FontAwesome.Regular.FileAudio;
|
|
||||||
|
|
||||||
case ".jpg":
|
|
||||||
case ".jpeg":
|
|
||||||
case ".png":
|
|
||||||
return FontAwesome.Regular.FileImage;
|
|
||||||
|
|
||||||
case ".mp4":
|
|
||||||
case ".avi":
|
|
||||||
case ".mov":
|
|
||||||
case ".flv":
|
|
||||||
return FontAwesome.Regular.FileVideo;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return FontAwesome.Regular.File;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
38
osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs
Normal file
38
osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public class OsuDirectorySelector : DirectorySelector
|
||||||
|
{
|
||||||
|
public const float ITEM_HEIGHT = 20;
|
||||||
|
|
||||||
|
public OsuDirectorySelector(string initialPath = null)
|
||||||
|
: base(initialPath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
||||||
|
|
||||||
|
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||||
|
|
||||||
|
protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300);
|
||||||
|
}
|
||||||
|
}
|
@ -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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
internal class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay
|
||||||
|
{
|
||||||
|
protected override Drawable CreateCaption() => new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Current Directory: ",
|
||||||
|
Font = OsuFont.Default.With(size: OsuDirectorySelector.ITEM_HEIGHT),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer();
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName);
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Height = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory
|
||||||
|
{
|
||||||
|
protected override IconUsage? Icon => null;
|
||||||
|
|
||||||
|
public OsuBreadcrumbDisplayComputer()
|
||||||
|
: base(null, "Computer")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory
|
||||||
|
{
|
||||||
|
public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null)
|
||||||
|
: base(directory, displayName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.Add(new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(FONT_SIZE / 2)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
internal class OsuDirectorySelectorDirectory : DirectorySelectorDirectory
|
||||||
|
{
|
||||||
|
public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null)
|
||||||
|
: base(directory, displayName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.AutoSizeAxes = Axes.X;
|
||||||
|
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
|
||||||
|
|
||||||
|
AddInternal(new Background
|
||||||
|
{
|
||||||
|
Depth = 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
||||||
|
|
||||||
|
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar)
|
||||||
|
? FontAwesome.Solid.Database
|
||||||
|
: FontAwesome.Regular.Folder;
|
||||||
|
|
||||||
|
internal class Background : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
InternalChild = new Box
|
||||||
|
{
|
||||||
|
Colour = colours.GreySeafoamDarker,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
internal class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory
|
||||||
|
{
|
||||||
|
protected override IconUsage? Icon => FontAwesome.Solid.Folder;
|
||||||
|
|
||||||
|
public OsuDirectorySelectorParentDirectory(DirectoryInfo directory)
|
||||||
|
: base(directory, "..")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs
Normal file
90
osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public class OsuFileSelector : FileSelector
|
||||||
|
{
|
||||||
|
public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null)
|
||||||
|
: base(initialPath, validFileExtensions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
||||||
|
|
||||||
|
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
|
||||||
|
|
||||||
|
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
|
||||||
|
|
||||||
|
protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file);
|
||||||
|
|
||||||
|
protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300);
|
||||||
|
|
||||||
|
protected class OsuDirectoryListingFile : DirectoryListingFile
|
||||||
|
{
|
||||||
|
public OsuDirectoryListingFile(FileInfo file)
|
||||||
|
: base(file)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.AutoSizeAxes = Axes.X;
|
||||||
|
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
|
||||||
|
|
||||||
|
AddInternal(new OsuDirectorySelectorDirectory.Background
|
||||||
|
{
|
||||||
|
Depth = 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IconUsage? Icon
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (File.Extension)
|
||||||
|
{
|
||||||
|
case @".ogg":
|
||||||
|
case @".mp3":
|
||||||
|
case @".wav":
|
||||||
|
return FontAwesome.Regular.FileAudio;
|
||||||
|
|
||||||
|
case @".jpg":
|
||||||
|
case @".jpeg":
|
||||||
|
case @".png":
|
||||||
|
return FontAwesome.Regular.FileImage;
|
||||||
|
|
||||||
|
case @".mp4":
|
||||||
|
case @".avi":
|
||||||
|
case @".mov":
|
||||||
|
case @".flv":
|
||||||
|
return FontAwesome.Regular.FileVideo;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return FontAwesome.Regular.File;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
Normal file
55
osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
public class OsuPopover : Popover
|
||||||
|
{
|
||||||
|
private const float fade_duration = 250;
|
||||||
|
private const double scale_duration = 500;
|
||||||
|
|
||||||
|
public OsuPopover(bool withPadding = true)
|
||||||
|
{
|
||||||
|
Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding();
|
||||||
|
|
||||||
|
Body.Masking = true;
|
||||||
|
Body.CornerRadius = 10;
|
||||||
|
Body.Margin = new MarginPadding(10);
|
||||||
|
Body.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Offset = new Vector2(0, 2),
|
||||||
|
Radius = 5,
|
||||||
|
Colour = Colour4.Black.Opacity(0.3f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
|
{
|
||||||
|
Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeafoamDarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateArrow() => Empty();
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
this.ScaleTo(1, scale_duration, Easing.OutElasticHalf);
|
||||||
|
this.FadeIn(fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
|
||||||
|
this.FadeOut(fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -102,8 +102,15 @@ namespace osu.Game.IO
|
|||||||
|
|
||||||
protected override void ChangeTargetStorage(Storage newStorage)
|
protected override void ChangeTargetStorage(Storage newStorage)
|
||||||
{
|
{
|
||||||
|
var lastStorage = UnderlyingStorage;
|
||||||
base.ChangeTargetStorage(newStorage);
|
base.ChangeTargetStorage(newStorage);
|
||||||
Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs");
|
|
||||||
|
if (lastStorage != null)
|
||||||
|
{
|
||||||
|
// for now we assume that if there was a previous storage, this is a migration operation.
|
||||||
|
// the logger shouldn't be set during initialisation as it can cause cross-talk in tests (due to being static).
|
||||||
|
Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Migrate(Storage newStorage)
|
public override void Migrate(Storage newStorage)
|
||||||
|
@ -87,6 +87,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
|
new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
|
||||||
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
|
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
|
||||||
new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay),
|
new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay),
|
||||||
|
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
|
||||||
|
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
|
||||||
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
|
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,6 +105,9 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume),
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume),
|
||||||
new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume),
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume),
|
||||||
|
|
||||||
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.PreviousVolumeMeter),
|
||||||
|
new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.NextVolumeMeter),
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute),
|
new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute),
|
||||||
|
|
||||||
new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev),
|
new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev),
|
||||||
@ -263,5 +268,17 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[Description("Toggle skin editor")]
|
[Description("Toggle skin editor")]
|
||||||
ToggleSkinEditor,
|
ToggleSkinEditor,
|
||||||
|
|
||||||
|
[Description("Previous volume meter")]
|
||||||
|
PreviousVolumeMeter,
|
||||||
|
|
||||||
|
[Description("Next volume meter")]
|
||||||
|
NextVolumeMeter,
|
||||||
|
|
||||||
|
[Description("Seek replay forward")]
|
||||||
|
SeekReplayForward,
|
||||||
|
|
||||||
|
[Description("Seek replay backward")]
|
||||||
|
SeekReplayBackward,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
<root>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>1.3</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<data name="solo" xml:space="preserve">
|
|
||||||
<value>ソロ</value>
|
|
||||||
</data>
|
|
||||||
<data name="playlists" xml:space="preserve">
|
|
||||||
<value>プレイリスト</value>
|
|
||||||
</data>
|
|
||||||
<data name="play" xml:space="preserve">
|
|
||||||
<value>遊ぶ</value>
|
|
||||||
</data>
|
|
||||||
<data name="multi" xml:space="preserve">
|
|
||||||
<value>マルチ</value>
|
|
||||||
</data>
|
|
||||||
<data name="edit" xml:space="preserve">
|
|
||||||
<value>エディット</value>
|
|
||||||
</data>
|
|
||||||
<data name="browse" xml:space="preserve">
|
|
||||||
<value>ブラウズ</value>
|
|
||||||
</data>
|
|
||||||
<data name="exit" xml:space="preserve">
|
|
||||||
<value>閉じる</value>
|
|
||||||
</data>
|
|
||||||
<data name="settings" xml:space="preserve">
|
|
||||||
<value>設定</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
|
@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<root>
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="metadata">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="assembly">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>2.0</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<data name="solo" xml:space="preserve">
|
|
||||||
<value>solo</value>
|
|
||||||
</data>
|
|
||||||
<data name="multi" xml:space="preserve">
|
|
||||||
<value>multi</value>
|
|
||||||
</data>
|
|
||||||
<data name="playlists" xml:space="preserve">
|
|
||||||
<value>playlists</value>
|
|
||||||
</data>
|
|
||||||
<data name="play" xml:space="preserve">
|
|
||||||
<value>play</value>
|
|
||||||
</data>
|
|
||||||
<data name="edit" xml:space="preserve">
|
|
||||||
<value>edit</value>
|
|
||||||
</data>
|
|
||||||
<data name="browse" xml:space="preserve">
|
|
||||||
<value>browse</value>
|
|
||||||
</data>
|
|
||||||
<data name="settings" xml:space="preserve">
|
|
||||||
<value>settings</value>
|
|
||||||
</data>
|
|
||||||
<data name="back" xml:space="preserve">
|
|
||||||
<value>back</value>
|
|
||||||
</data>
|
|
||||||
<data name="exit" xml:space="preserve">
|
|
||||||
<value>exit</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user