mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 02:22:56 +08:00
Merge branch 'master' into autolink-md
This commit is contained in:
commit
1c69da09d3
@ -52,10 +52,10 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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 Label="Transitive Dependencies">
|
||||
<!-- 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>
|
||||
</Project>
|
||||
|
@ -68,6 +68,8 @@ namespace osu.Desktop.Updater
|
||||
return false;
|
||||
}
|
||||
|
||||
scheduleRecheck = false;
|
||||
|
||||
if (notification == null)
|
||||
{
|
||||
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)
|
||||
// try again without deltas.
|
||||
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
||||
scheduleRecheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -110,6 +111,7 @@ namespace osu.Desktop.Updater
|
||||
catch (Exception)
|
||||
{
|
||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||
scheduleRecheck = true;
|
||||
}
|
||||
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
|
||||
{
|
||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
|
||||
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
|
||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
Default = 5,
|
||||
Value = 5,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||
};
|
||||
|
||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
|
||||
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
|
||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
Default = 5,
|
||||
Value = 5,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
};
|
||||
|
||||
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
base.ApplySettings(difficulty);
|
||||
|
||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||
if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value;
|
||||
if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value;
|
||||
}
|
||||
|
||||
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -11,34 +10,26 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDifficultyAdjust : ModDifficultyAdjust
|
||||
{
|
||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
|
||||
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
|
||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Default = 5,
|
||||
Value = 5,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||
};
|
||||
|
||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
|
||||
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
|
||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Default = 5,
|
||||
Value = 5,
|
||||
ExtendedMaxValue = 11,
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
base.ApplySettings(difficulty);
|
||||
|
||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||
if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value;
|
||||
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.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -11,14 +10,13 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModDifficultyAdjust : ModDifficultyAdjust
|
||||
{
|
||||
[SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)]
|
||||
public BindableNumber<float> ScrollSpeed { get; } = new BindableFloat
|
||||
[SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable ScrollSpeed { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.05f,
|
||||
MinValue = 0.25f,
|
||||
MaxValue = 4,
|
||||
Default = 1,
|
||||
Value = 1,
|
||||
ReadCurrentFromDifficulty = _ => 1,
|
||||
};
|
||||
|
||||
public override string SettingDescription
|
||||
@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
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.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.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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
@ -10,6 +11,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
@ -17,17 +19,26 @@ using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestScenePlayerScoreSubmission : OsuPlayerTestScene
|
||||
public class TestScenePlayerScoreSubmission : PlayerTestScene
|
||||
{
|
||||
protected override bool AllowFail => allowFail;
|
||||
|
||||
private bool allowFail;
|
||||
|
||||
private Func<RulesetInfo, IBeatmap> createCustomBeatmap;
|
||||
private Func<Ruleset> createCustomRuleset;
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
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);
|
||||
|
||||
@ -36,14 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
||||
|
||||
[Test]
|
||||
public void TestNoSubmissionOnResultsWithNoToken()
|
||||
{
|
||||
prepareTokenResponse(false);
|
||||
|
||||
CreateTest(() => allowFail = false);
|
||||
createPlayerTest();
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
@ -63,7 +72,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTokenResponse(true);
|
||||
|
||||
CreateTest(() => allowFail = false);
|
||||
createPlayerTest();
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
@ -82,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTokenResponse(false);
|
||||
|
||||
CreateTest(() => allowFail = false);
|
||||
createPlayerTest();
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
@ -99,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTokenResponse(true);
|
||||
|
||||
CreateTest(() => allowFail = true);
|
||||
createPlayerTest(true);
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
@ -114,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTokenResponse(true);
|
||||
|
||||
CreateTest(() => allowFail = true);
|
||||
createPlayerTest(true);
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
@ -131,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTokenResponse(true);
|
||||
|
||||
CreateTest(() => allowFail = false);
|
||||
createPlayerTest();
|
||||
|
||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||
|
||||
@ -144,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
prepareTokenResponse(true);
|
||||
|
||||
CreateTest(() => allowFail = false);
|
||||
createPlayerTest();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement())
|
||||
{
|
||||
Type = HitResult.Great,
|
||||
});
|
||||
var beatmap = createTestBeatmap(r);
|
||||
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
||||
return beatmap;
|
||||
});
|
||||
|
||||
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)
|
||||
@ -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]
|
||||
public void TestPerformAtSongSelectFromPlayerLoader()
|
||||
{
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
importAndWaitForSongSelect();
|
||||
|
||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||
@ -72,8 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestPerformAtMenuFromPlayerLoader()
|
||||
{
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
importAndWaitForSongSelect();
|
||||
|
||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||
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
|
||||
{
|
||||
[Resolved]
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays;
|
||||
@ -17,28 +16,65 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
[Test]
|
||||
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",
|
||||
Value = "test"
|
||||
}
|
||||
Current = new Bindable<string>
|
||||
{
|
||||
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");
|
||||
AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0);
|
||||
AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0);
|
||||
|
||||
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();
|
||||
|
||||
[Test]
|
||||
public void TestLocal([Values("Beatmap", "Some long title and stuff")]
|
||||
string title,
|
||||
[Values("Trial", "Some1's very hardest difficulty")]
|
||||
string version)
|
||||
public void TestLocal(
|
||||
[Values("Beatmap", "Some long title and stuff")]
|
||||
string title,
|
||||
[Values("Trial", "Some1's very hardest difficulty")]
|
||||
string version)
|
||||
{
|
||||
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());
|
||||
|
||||
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]
|
||||
|
@ -20,15 +20,26 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
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.
|
||||
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
|
||||
{
|
||||
get => current.Current;
|
||||
set => current.Current = value;
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
current?.UnbindAll();
|
||||
current = value.GetBoundCopy();
|
||||
|
||||
current.ValueChanged += _ => UpdateState();
|
||||
current.DefaultChanged += _ => UpdateState();
|
||||
current.DisabledChanged += _ => UpdateState();
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 buttonColour;
|
||||
@ -62,18 +73,14 @@ namespace osu.Game.Overlays
|
||||
|
||||
Action += () =>
|
||||
{
|
||||
if (!current.Disabled) current.SetDefault();
|
||||
if (!current.Disabled)
|
||||
current.SetDefault();
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.ValueChanged += _ => UpdateState();
|
||||
Current.DisabledChanged += _ => UpdateState();
|
||||
Current.DefaultChanged += _ => UpdateState();
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
|
@ -101,10 +101,10 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
public event Action SettingChanged;
|
||||
|
||||
private readonly RestoreDefaultValueButton<T> restoreDefaultButton;
|
||||
|
||||
protected SettingsItem()
|
||||
{
|
||||
RestoreDefaultValueButton<T> restoreDefaultButton;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
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
|
||||
// never loaded, but requires bindable storage.
|
||||
if (controlWithCurrent != null)
|
||||
{
|
||||
controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
|
||||
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
||||
if (controlWithCurrent == null)
|
||||
throw new ArgumentException(@$"Control created via {nameof(CreateControl)} must implement {nameof(IHasCurrentValue<T>)}");
|
||||
|
||||
if (ShowsDefaultIndicator)
|
||||
restoreDefaultButton.Current = controlWithCurrent.Current;
|
||||
}
|
||||
controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
|
||||
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (ShowsDefaultIndicator)
|
||||
restoreDefaultButton.Current = controlWithCurrent.Current;
|
||||
}
|
||||
|
||||
private void updateDisabled()
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
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)
|
||||
: 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.
|
||||
// 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.Graphics.Sprites;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -33,24 +32,24 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected const int LAST_SETTING_ORDER = 2;
|
||||
|
||||
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)]
|
||||
public BindableNumber<float> DrainRate { get; } = new BindableFloatWithLimitExtension
|
||||
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable DrainRate { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Default = 5,
|
||||
Value = 5,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.DrainRate,
|
||||
};
|
||||
|
||||
[SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)]
|
||||
public BindableNumber<float> OverallDifficulty { get; } = new BindableFloatWithLimitExtension
|
||||
[SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||
public DifficultyBindable OverallDifficulty { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Default = 5,
|
||||
Value = 5,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.OverallDifficulty,
|
||||
};
|
||||
|
||||
[SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")]
|
||||
@ -58,17 +57,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected ModDifficultyAdjust()
|
||||
{
|
||||
ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
foreach (var (_, property) in this.GetOrderedSettingsSourceProperties())
|
||||
{
|
||||
if (property.GetValue(this) is DifficultyBindable diffAdjustBindable)
|
||||
diffAdjustBindable.ExtendedLimits.BindTo(ExtendedLimits);
|
||||
}
|
||||
}
|
||||
|
||||
public override string SettingDescription
|
||||
@ -86,146 +79,20 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private 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);
|
||||
|
||||
/// <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>
|
||||
/// Apply all custom settings to the provided beatmap.
|
||||
/// </summary>
|
||||
/// <param name="difficulty">The beatmap to have settings applied.</param>
|
||||
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
|
||||
{
|
||||
ApplySetting(DrainRate, dr => difficulty.DrainRate = dr);
|
||||
ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value;
|
||||
if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play
|
||||
if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId))
|
||||
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 new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash);
|
||||
|
@ -25,8 +25,11 @@ namespace osu.Game.Tests
|
||||
|
||||
protected override void SetupForRun()
|
||||
{
|
||||
base.SetupForRun();
|
||||
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
|
||||
{
|
||||
protected readonly Container HitObjectContainer;
|
||||
private PlacementBlueprint currentBlueprint;
|
||||
protected PlacementBlueprint CurrentBlueprint { get; private set; }
|
||||
|
||||
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]
|
||||
@ -63,9 +63,9 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
protected void ResetPlacement()
|
||||
{
|
||||
if (currentBlueprint != null)
|
||||
Remove(currentBlueprint);
|
||||
Add(currentBlueprint = CreateBlueprint());
|
||||
if (CurrentBlueprint != null)
|
||||
Remove(CurrentBlueprint);
|
||||
Add(CurrentBlueprint = CreateBlueprint());
|
||||
}
|
||||
|
||||
public void Delete(HitObject hitObject)
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
base.Update();
|
||||
|
||||
currentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(currentBlueprint));
|
||||
CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint));
|
||||
}
|
||||
|
||||
protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) =>
|
||||
|
@ -57,7 +57,9 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
protected void LoadPlayer()
|
||||
{
|
||||
var ruleset = Ruleset.Value.CreateInstance();
|
||||
var ruleset = CreatePlayerRuleset();
|
||||
Ruleset.Value = ruleset.RulesetInfo;
|
||||
|
||||
var beatmap = CreateBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
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 =>
|
||||
{
|
||||
|
@ -35,8 +35,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.2.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.707.0" />
|
||||
<PackageReference Include="Realm" Version="10.3.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.713.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
|
||||
<PackageReference Include="Sentry" Version="3.6.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<!-- 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.Core" Version="2.2.6" />
|
||||
<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="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<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>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user