mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 20:13:22 +08:00
Merge branch 'master' into autolink-md
This commit is contained in:
commit
1c69da09d3
@ -52,10 +52,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.707.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.713.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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -10,6 +11,7 @@ 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;
|
||||||
@ -17,17 +19,26 @@ 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 IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
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);
|
var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset);
|
||||||
|
|
||||||
@ -36,14 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
|
||||||
|
|
||||||
[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);
|
||||||
|
|
||||||
@ -63,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);
|
||||||
|
|
||||||
@ -82,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);
|
||||||
|
|
||||||
@ -99,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);
|
||||||
|
|
||||||
@ -114,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);
|
||||||
|
|
||||||
@ -131,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);
|
||||||
|
|
||||||
@ -144,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);
|
||||||
|
|
||||||
@ -154,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)
|
||||||
@ -188,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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("show", () => modSelect.Show());
|
AddStep("show", () => modSelect.Show());
|
||||||
|
|
||||||
AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
|
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 == 5);
|
AddAssert("ensure second is default", () => selectedMods2.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -20,15 +20,26 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||||
|
|
||||||
private readonly BindableWithCurrent<T> current = new BindableWithCurrent<T>();
|
|
||||||
|
|
||||||
// this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button.
|
// this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button.
|
||||||
public override bool AcceptsFocus => true;
|
public override bool AcceptsFocus => true;
|
||||||
|
|
||||||
|
// this is intentionally not using BindableWithCurrent, as it can use the wrong IsDefault implementation when passed a BindableNumber.
|
||||||
|
// using GetBoundCopy() ensures that the received bindable is of the exact same type as the source bindable and uses the proper IsDefault implementation.
|
||||||
|
private Bindable<T> current;
|
||||||
|
|
||||||
public Bindable<T> Current
|
public Bindable<T> Current
|
||||||
{
|
{
|
||||||
get => current.Current;
|
get => current;
|
||||||
set => current.Current = value;
|
set
|
||||||
|
{
|
||||||
|
current?.UnbindAll();
|
||||||
|
current = value.GetBoundCopy();
|
||||||
|
|
||||||
|
current.ValueChanged += _ => UpdateState();
|
||||||
|
current.DefaultChanged += _ => UpdateState();
|
||||||
|
current.DisabledChanged += _ => UpdateState();
|
||||||
|
UpdateState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 buttonColour;
|
private Color4 buttonColour;
|
||||||
@ -62,18 +73,14 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
Action += () =>
|
Action += () =>
|
||||||
{
|
{
|
||||||
if (!current.Disabled) current.SetDefault();
|
if (!current.Disabled)
|
||||||
|
current.SetDefault();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Current.ValueChanged += _ => UpdateState();
|
|
||||||
Current.DisabledChanged += _ => UpdateState();
|
|
||||||
Current.DefaultChanged += _ => UpdateState();
|
|
||||||
|
|
||||||
UpdateState();
|
UpdateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,10 +101,10 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
public event Action SettingChanged;
|
public event Action SettingChanged;
|
||||||
|
|
||||||
|
private readonly RestoreDefaultValueButton<T> restoreDefaultButton;
|
||||||
|
|
||||||
protected SettingsItem()
|
protected SettingsItem()
|
||||||
{
|
{
|
||||||
RestoreDefaultValueButton<T> restoreDefaultButton;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS };
|
Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS };
|
||||||
@ -126,14 +126,19 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
// all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is
|
// all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is
|
||||||
// never loaded, but requires bindable storage.
|
// never loaded, but requires bindable storage.
|
||||||
if (controlWithCurrent != null)
|
if (controlWithCurrent == null)
|
||||||
{
|
throw new ArgumentException(@$"Control created via {nameof(CreateControl)} must implement {nameof(IHasCurrentValue<T>)}");
|
||||||
controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
|
|
||||||
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
|
||||||
|
|
||||||
if (ShowsDefaultIndicator)
|
controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
|
||||||
restoreDefaultButton.Current = controlWithCurrent.Current;
|
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (ShowsDefaultIndicator)
|
||||||
|
restoreDefaultButton.Current = controlWithCurrent.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisabled()
|
private void updateDisabled()
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual bool AlwaysShowWhenSelected => false;
|
protected virtual bool AlwaysShowWhenSelected => false;
|
||||||
|
|
||||||
protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
protected override bool ShouldBeAlive => (DrawableObject?.IsAlive == true && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
||||||
|
|
||||||
protected HitObjectSelectionBlueprint(HitObject hitObject)
|
protected HitObjectSelectionBlueprint(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
|
112
osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
Normal file
112
osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mods
|
||||||
|
{
|
||||||
|
public class DifficultyAdjustSettingsControl : SettingsItem<float?>
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to track the display value on the setting slider.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When the mod is overriding a default, this will match the value of <see cref="Current"/>.
|
||||||
|
/// When there is no override (ie. <see cref="Current"/> is null), this value will match the beatmap provided default via <see cref="updateCurrentFromSlider"/>.
|
||||||
|
/// </remarks>
|
||||||
|
private readonly BindableNumber<float> sliderDisplayCurrent = new BindableNumber<float>();
|
||||||
|
|
||||||
|
protected override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Guards against beatmap values displayed on slider bars being transferred to user override.
|
||||||
|
/// </summary>
|
||||||
|
private bool isInternalChange;
|
||||||
|
|
||||||
|
private DifficultyBindable difficultyBindable;
|
||||||
|
|
||||||
|
public override Bindable<float?> Current
|
||||||
|
{
|
||||||
|
get => base.Current;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// Intercept and extract the internal number bindable from DifficultyBindable.
|
||||||
|
// This will provide bounds and precision specifications for the slider bar.
|
||||||
|
difficultyBindable = ((DifficultyBindable)value).GetBoundCopy();
|
||||||
|
sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber);
|
||||||
|
|
||||||
|
base.Current = difficultyBindable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(current => updateCurrentFromSlider());
|
||||||
|
beatmap.BindValueChanged(b => updateCurrentFromSlider(), true);
|
||||||
|
|
||||||
|
sliderDisplayCurrent.BindValueChanged(number =>
|
||||||
|
{
|
||||||
|
// this handles the transfer of the slider value to the main bindable.
|
||||||
|
// as such, should be skipped if the slider is being updated via updateFromDifficulty().
|
||||||
|
if (!isInternalChange)
|
||||||
|
Current.Value = number.NewValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCurrentFromSlider()
|
||||||
|
{
|
||||||
|
if (Current.Value != null)
|
||||||
|
{
|
||||||
|
// a user override has been added or updated.
|
||||||
|
sliderDisplayCurrent.Value = Current.Value.Value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty;
|
||||||
|
|
||||||
|
if (difficulty == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// generally should always be implemented, else the slider will have a zero default.
|
||||||
|
if (difficultyBindable.ReadCurrentFromDifficulty == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isInternalChange = true;
|
||||||
|
sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty);
|
||||||
|
isInternalChange = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SliderControl : CompositeDrawable, IHasCurrentValue<float?>
|
||||||
|
{
|
||||||
|
// This is required as SettingsItem relies heavily on this bindable for internal use.
|
||||||
|
// The actual update flow is done via the bindable provided in the constructor.
|
||||||
|
public Bindable<float?> Current { get; set; } = new Bindable<float?>();
|
||||||
|
|
||||||
|
public SliderControl(BindableNumber<float> currentNumber)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsSlider<float>
|
||||||
|
{
|
||||||
|
ShowsDefaultIndicator = false,
|
||||||
|
Current = currentNumber,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
osu.Game/Rulesets/Mods/DifficultyBindable.cs
Normal file
133
osu.Game/Rulesets/Mods/DifficultyBindable.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// 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 osu.Framework.Bindables;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mods
|
||||||
|
{
|
||||||
|
public class DifficultyBindable : Bindable<float?>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the extended limits should be applied to this bindable.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableBool ExtendedLimits = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An internal numeric bindable to hold and propagate min/max/precision.
|
||||||
|
/// The value of this bindable should not be set.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly BindableFloat CurrentNumber = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function that can extract the current value of this setting from a beatmap difficulty for display purposes.
|
||||||
|
/// </summary>
|
||||||
|
public Func<BeatmapDifficulty, float> ReadCurrentFromDifficulty;
|
||||||
|
|
||||||
|
public float Precision
|
||||||
|
{
|
||||||
|
set => CurrentNumber.Precision = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MinValue
|
||||||
|
{
|
||||||
|
set => CurrentNumber.MinValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float maxValue;
|
||||||
|
|
||||||
|
public float MaxValue
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == maxValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
maxValue = value;
|
||||||
|
updateMaxValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float? extendedMaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum value to be used when extended limits are applied.
|
||||||
|
/// </summary>
|
||||||
|
public float? ExtendedMaxValue
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == extendedMaxValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
extendedMaxValue = value;
|
||||||
|
updateMaxValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DifficultyBindable()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DifficultyBindable(float? defaultValue = null)
|
||||||
|
: base(defaultValue)
|
||||||
|
{
|
||||||
|
ExtendedLimits.BindValueChanged(_ => updateMaxValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float? Value
|
||||||
|
{
|
||||||
|
get => base.Value;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated.
|
||||||
|
if (value != null)
|
||||||
|
CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value);
|
||||||
|
|
||||||
|
base.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMaxValue()
|
||||||
|
{
|
||||||
|
CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BindTo(Bindable<float?> them)
|
||||||
|
{
|
||||||
|
if (!(them is DifficultyBindable otherDifficultyBindable))
|
||||||
|
throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}.");
|
||||||
|
|
||||||
|
ReadCurrentFromDifficulty = otherDifficultyBindable.ReadCurrentFromDifficulty;
|
||||||
|
|
||||||
|
// the following max value copies are only safe as long as these values are effectively constants.
|
||||||
|
MaxValue = otherDifficultyBindable.maxValue;
|
||||||
|
ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue;
|
||||||
|
|
||||||
|
ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits;
|
||||||
|
|
||||||
|
// the actual values need to be copied after the max value constraints.
|
||||||
|
CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber;
|
||||||
|
base.BindTo(them);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UnbindFrom(IUnbindable them)
|
||||||
|
{
|
||||||
|
if (!(them is DifficultyBindable otherDifficultyBindable))
|
||||||
|
throw new InvalidOperationException($"Cannot unbind from a non-{nameof(DifficultyBindable)}.");
|
||||||
|
|
||||||
|
base.UnbindFrom(them);
|
||||||
|
|
||||||
|
CurrentNumber.UnbindFrom(otherDifficultyBindable.CurrentNumber);
|
||||||
|
ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this };
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +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 osu.Game.Beatmaps;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using System;
|
using osu.Game.Beatmaps;
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
@ -33,24 +32,24 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
protected const int LAST_SETTING_ORDER = 2;
|
protected const int LAST_SETTING_ORDER = 2;
|
||||||
|
|
||||||
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)]
|
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> DrainRate { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable DrainRate { 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.DrainRate,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)]
|
[SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> OverallDifficulty { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable OverallDifficulty { 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.OverallDifficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")]
|
[SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")]
|
||||||
@ -58,17 +57,11 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
protected ModDifficultyAdjust()
|
protected ModDifficultyAdjust()
|
||||||
{
|
{
|
||||||
ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue));
|
foreach (var (_, property) in this.GetOrderedSettingsSourceProperties())
|
||||||
}
|
{
|
||||||
|
if (property.GetValue(this) is DifficultyBindable diffAdjustBindable)
|
||||||
/// <summary>
|
diffAdjustBindable.ExtendedLimits.BindTo(ExtendedLimits);
|
||||||
/// Changes the difficulty adjustment limits. Occurs when the value of <see cref="ExtendedLimits"/> is changed.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="extended">Whether limits should extend beyond sane ranges.</param>
|
|
||||||
protected virtual void ApplyLimits(bool extended)
|
|
||||||
{
|
|
||||||
DrainRate.MaxValue = extended ? 11 : 10;
|
|
||||||
OverallDifficulty.MaxValue = extended ? 11 : 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string SettingDescription
|
public override string SettingDescription
|
||||||
@ -86,146 +79,20 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapDifficulty difficulty;
|
|
||||||
|
|
||||||
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
|
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
if (this.difficulty == null || this.difficulty.ID != difficulty.ID)
|
|
||||||
{
|
|
||||||
TransferSettings(difficulty);
|
|
||||||
this.difficulty = difficulty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty) => ApplySettings(difficulty);
|
public void ApplyToDifficulty(BeatmapDifficulty difficulty) => ApplySettings(difficulty);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transfer initial settings from the beatmap to settings.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="difficulty">The beatmap's initial values.</param>
|
|
||||||
protected virtual void TransferSettings(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
TransferSetting(DrainRate, difficulty.DrainRate);
|
|
||||||
TransferSetting(OverallDifficulty, difficulty.OverallDifficulty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<IBindable, bool> userChangedSettings = new Dictionary<IBindable, bool>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transfer a setting from <see cref="BeatmapDifficulty"/> to a configuration bindable.
|
|
||||||
/// Only performs the transfer if the user is not currently overriding.
|
|
||||||
/// </summary>
|
|
||||||
protected void TransferSetting<T>(BindableNumber<T> bindable, T beatmapDefault)
|
|
||||||
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
|
|
||||||
{
|
|
||||||
bindable.UnbindEvents();
|
|
||||||
|
|
||||||
userChangedSettings.TryAdd(bindable, false);
|
|
||||||
|
|
||||||
bindable.Default = beatmapDefault;
|
|
||||||
|
|
||||||
// users generally choose a difficulty setting and want it to stick across multiple beatmap changes.
|
|
||||||
// we only want to value transfer if the user hasn't changed the value previously.
|
|
||||||
if (!userChangedSettings[bindable])
|
|
||||||
bindable.Value = beatmapDefault;
|
|
||||||
|
|
||||||
bindable.ValueChanged += _ => userChangedSettings[bindable] = !bindable.IsDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void CopyAdjustedSetting(IBindable target, object source)
|
|
||||||
{
|
|
||||||
// if the value is non-bindable, it's presumably coming from an external source (like the API) - therefore presume it is not default.
|
|
||||||
// if the value is bindable, defer to the source's IsDefault to be able to tell.
|
|
||||||
userChangedSettings[target] = !(source is IBindable bindableSource) || !bindableSource.IsDefault;
|
|
||||||
base.CopyAdjustedSetting(target, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies a setting from a configuration bindable using <paramref name="applyFunc"/>, if it has been changed by the user.
|
|
||||||
/// </summary>
|
|
||||||
protected void ApplySetting<T>(BindableNumber<T> setting, Action<T> applyFunc)
|
|
||||||
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
|
|
||||||
{
|
|
||||||
if (userChangedSettings.TryGetValue(setting, out bool userChangedSetting) && userChangedSetting)
|
|
||||||
applyFunc.Invoke(setting.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply all custom settings to the provided beatmap.
|
/// Apply all custom settings to the provided beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="difficulty">The beatmap to have settings applied.</param>
|
/// <param name="difficulty">The beatmap to have settings applied.</param>
|
||||||
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
|
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
ApplySetting(DrainRate, dr => difficulty.DrainRate = dr);
|
if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value;
|
||||||
ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od);
|
if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value;
|
||||||
}
|
|
||||||
|
|
||||||
public override void ResetSettingsToDefaults()
|
|
||||||
{
|
|
||||||
base.ResetSettingsToDefaults();
|
|
||||||
|
|
||||||
if (difficulty != null)
|
|
||||||
{
|
|
||||||
// base implementation potentially overwrite modified defaults that came from a beatmap selection.
|
|
||||||
TransferSettings(difficulty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="BindableDouble"/> that extends its min/max values to support any assigned value.
|
|
||||||
/// </summary>
|
|
||||||
protected class BindableDoubleWithLimitExtension : BindableDouble
|
|
||||||
{
|
|
||||||
public override double Value
|
|
||||||
{
|
|
||||||
get => base.Value;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value < MinValue)
|
|
||||||
MinValue = value;
|
|
||||||
if (value > MaxValue)
|
|
||||||
MaxValue = value;
|
|
||||||
base.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="BindableFloat"/> that extends its min/max values to support any assigned value.
|
|
||||||
/// </summary>
|
|
||||||
protected class BindableFloatWithLimitExtension : BindableFloat
|
|
||||||
{
|
|
||||||
public override float Value
|
|
||||||
{
|
|
||||||
get => base.Value;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value < MinValue)
|
|
||||||
MinValue = value;
|
|
||||||
if (value > MaxValue)
|
|
||||||
MaxValue = value;
|
|
||||||
base.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="BindableInt"/> that extends its min/max values to support any assigned value.
|
|
||||||
/// </summary>
|
|
||||||
protected class BindableIntWithLimitExtension : BindableInt
|
|
||||||
{
|
|
||||||
public override int Value
|
|
||||||
{
|
|
||||||
get => base.Value;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value < MinValue)
|
|
||||||
MinValue = value;
|
|
||||||
if (value > MaxValue)
|
|
||||||
MaxValue = value;
|
|
||||||
base.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
|||||||
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.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId))
|
if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (!(Ruleset.Value.ID is int rulesetId))
|
if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash);
|
return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash);
|
||||||
|
@ -25,8 +25,11 @@ namespace osu.Game.Tests
|
|||||||
|
|
||||||
protected override void SetupForRun()
|
protected override void SetupForRun()
|
||||||
{
|
{
|
||||||
base.SetupForRun();
|
|
||||||
Storage.DeleteDirectory(string.Empty);
|
Storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
|
// base call needs to be run *after* storage is emptied, as it updates the (static) logger's storage and may start writing
|
||||||
|
// log entries from another source if a unit test host is shared over multiple tests, causing a file access denied exception.
|
||||||
|
base.SetupForRun();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler
|
public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler
|
||||||
{
|
{
|
||||||
protected readonly Container HitObjectContainer;
|
protected readonly Container HitObjectContainer;
|
||||||
private PlacementBlueprint currentBlueprint;
|
protected PlacementBlueprint CurrentBlueprint { get; private set; }
|
||||||
|
|
||||||
protected PlacementBlueprintTestScene()
|
protected PlacementBlueprintTestScene()
|
||||||
{
|
{
|
||||||
Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock())));
|
base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock())));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -63,9 +63,9 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected void ResetPlacement()
|
protected void ResetPlacement()
|
||||||
{
|
{
|
||||||
if (currentBlueprint != null)
|
if (CurrentBlueprint != null)
|
||||||
Remove(currentBlueprint);
|
Remove(CurrentBlueprint);
|
||||||
Add(currentBlueprint = CreateBlueprint());
|
Add(CurrentBlueprint = CreateBlueprint());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(HitObject hitObject)
|
public void Delete(HitObject hitObject)
|
||||||
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
currentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(currentBlueprint));
|
CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) =>
|
protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) =>
|
||||||
|
@ -57,7 +57,9 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected void LoadPlayer()
|
protected void LoadPlayer()
|
||||||
{
|
{
|
||||||
var ruleset = Ruleset.Value.CreateInstance();
|
var ruleset = CreatePlayerRuleset();
|
||||||
|
Ruleset.Value = ruleset.RulesetInfo;
|
||||||
|
|
||||||
var beatmap = CreateBeatmap(ruleset.RulesetInfo);
|
var beatmap = CreateBeatmap(ruleset.RulesetInfo);
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
@ -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.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject)
|
protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, [CanBeNull] DrawableHitObject drawableObject = null)
|
||||||
{
|
{
|
||||||
Add(blueprint.With(d =>
|
Add(blueprint.With(d =>
|
||||||
{
|
{
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.2.1" />
|
<PackageReference Include="Realm" Version="10.3.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.707.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.713.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.6.0" />
|
<PackageReference Include="Sentry" Version="3.6.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.707.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.713.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -93,12 +93,12 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.707.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.713.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2021.115.0" ExcludeAssets="all" />
|
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2021.115.0" ExcludeAssets="all" />
|
||||||
<PackageReference Include="Realm" Version="10.2.1" />
|
<PackageReference Include="Realm" Version="10.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user