mirror of
https://github.com/osukey/osukey.git
synced 2025-04-29 02:37:25 +09:00
Merge branch 'master' into skin-editor-change-handler-improvement
This commit is contained in:
commit
59ece64d25
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
|
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
|
||||||
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
|
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
|
||||||
AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1]));
|
AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1]));
|
||||||
AddAssert("default slider velocity", () => lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
AddAssert("default slider velocity", () => lastObject.SliderVelocityBindable.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
addPlacementSteps(times, positions);
|
addPlacementSteps(times, positions);
|
||||||
addPathCheckStep(times, positions);
|
addPathCheckStep(times, positions);
|
||||||
|
|
||||||
AddAssert("slider velocity changed", () => !lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
double[] times = { 100, 300 };
|
double[] times = { 100, 300 };
|
||||||
float[] positions = { 200, 300 };
|
float[] positions = { 200, 300 };
|
||||||
addBlueprintStep(times, positions);
|
addBlueprintStep(times, positions);
|
||||||
AddAssert("default slider velocity", () => hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault);
|
||||||
|
|
||||||
addDragStartStep(times[1], positions[1]);
|
addDragStartStep(times[1], positions[1]);
|
||||||
AddMouseMoveStep(times[1], 400);
|
AddMouseMoveStep(times[1], 400);
|
||||||
AddAssert("slider velocity changed", () => !hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
var xPositionData = obj as IHasXPosition;
|
var xPositionData = obj as IHasXPosition;
|
||||||
var yPositionData = obj as IHasYPosition;
|
var yPositionData = obj as IHasYPosition;
|
||||||
var comboData = obj as IHasCombo;
|
var comboData = obj as IHasCombo;
|
||||||
|
var sliderVelocityData = obj as IHasSliderVelocity;
|
||||||
|
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
@ -41,7 +42,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
|
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
|
||||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
|
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||||
|
SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
case IHasDuration endTime:
|
case IHasDuration endTime:
|
||||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
public void UpdateHitObjectFromPath(JuiceStream hitObject)
|
public void UpdateHitObjectFromPath(JuiceStream hitObject)
|
||||||
{
|
{
|
||||||
// The SV setting may need to be changed for the current path.
|
// The SV setting may need to be changed for the current path.
|
||||||
var svBindable = hitObject.DifficultyControlPoint.SliderVelocityBindable;
|
var svBindable = hitObject.SliderVelocityBindable;
|
||||||
double svToVelocityFactor = hitObject.Velocity / svBindable.Value;
|
double svToVelocityFactor = hitObject.Velocity / svBindable.Value;
|
||||||
double requiredVelocity = path.ComputeRequiredVelocity();
|
double requiredVelocity = path.ComputeRequiredVelocity();
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
||||||
|
|
||||||
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
|
private static readonly IList<HitSampleInfo> default_banana_samples = new List<HitSampleInfo> { new BananaHitSampleInfo() }.AsReadOnly();
|
||||||
|
|
||||||
public Banana()
|
public Banana()
|
||||||
{
|
{
|
||||||
Samples = samples;
|
Samples = default_banana_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
// override any external colour changes with banananana
|
// override any external colour changes with banananana
|
||||||
@ -47,13 +47,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
public class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
||||||
{
|
{
|
||||||
private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
|
private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
|
||||||
|
|
||||||
public override IEnumerable<string> LookupNames => lookup_names;
|
public override IEnumerable<string> LookupNames => lookup_names;
|
||||||
|
|
||||||
public BananaHitSampleInfo(int volume = 0)
|
public BananaHitSampleInfo(int volume = 100)
|
||||||
: base(string.Empty, volume: volume)
|
: base(string.Empty, volume: volume)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
StartTime = time,
|
StartTime = time,
|
||||||
BananaIndex = i,
|
BananaIndex = i,
|
||||||
|
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(GetSampleInfo().Volume) }
|
||||||
});
|
});
|
||||||
|
|
||||||
time += spacing;
|
time += spacing;
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -16,7 +17,7 @@ using osu.Game.Rulesets.Objects.Types;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class JuiceStream : CatchHitObject, IHasPathWithRepeats
|
public class JuiceStream : CatchHitObject, IHasPathWithRepeats, IHasSliderVelocity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Positional distance that results in a duration of one second, before any speed adjustments.
|
/// Positional distance that results in a duration of one second, before any speed adjustments.
|
||||||
@ -27,6 +28,19 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int RepeatCount { get; set; }
|
public int RepeatCount { get; set; }
|
||||||
|
|
||||||
|
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
public double SliderVelocity
|
||||||
|
{
|
||||||
|
get => SliderVelocityBindable.Value;
|
||||||
|
set => SliderVelocityBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private double velocityFactor;
|
private double velocityFactor;
|
||||||
|
|
||||||
@ -34,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
private double tickDistanceFactor;
|
private double tickDistanceFactor;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double Velocity => velocityFactor * DifficultyControlPoint.SliderVelocity;
|
public double Velocity => velocityFactor * SliderVelocity;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity;
|
public double TickDistance => tickDistanceFactor * SliderVelocity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||||
|
@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||||
@ -49,15 +48,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
Debug.Assert(distanceData != null);
|
Debug.Assert(distanceData != null);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
|
|
||||||
|
|
||||||
double beatLength;
|
double beatLength;
|
||||||
#pragma warning disable 618
|
if (hitObject.LegacyBpmMultiplier.HasValue)
|
||||||
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
|
beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value;
|
||||||
#pragma warning restore 618
|
else if (hitObject is IHasSliderVelocity hasSliderVelocity)
|
||||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
|
||||||
else
|
else
|
||||||
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
beatLength = timingPoint.BeatLength;
|
||||||
|
|
||||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||||
StartTime = (int)Math.Round(hitObject.StartTime);
|
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||||
|
@ -350,13 +350,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||||
|
|
||||||
if (HitObject.SampleControlPoint == null)
|
slidingSample.Samples = HitObject.CreateSlidingSamples().Cast<ISampleInfo>().ToArray();
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
|
||||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void StopAllSamples()
|
public override void StopAllSamples()
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
["Gameplay/soft-hitnormal"],
|
["Gameplay/soft-hitnormal"],
|
||||||
["Gameplay/drum-hitnormal"]
|
["Gameplay/drum-hitnormal"]
|
||||||
],
|
],
|
||||||
"Samples": ["Gameplay/-hitnormal"]
|
"Samples": ["Gameplay/normal-hitnormal"]
|
||||||
}, {
|
}, {
|
||||||
"StartTime": 1875.0,
|
"StartTime": 1875.0,
|
||||||
"EndTime": 2750.0,
|
"EndTime": 2750.0,
|
||||||
@ -19,7 +19,7 @@
|
|||||||
["Gameplay/soft-hitnormal"],
|
["Gameplay/soft-hitnormal"],
|
||||||
["Gameplay/drum-hitnormal"]
|
["Gameplay/drum-hitnormal"]
|
||||||
],
|
],
|
||||||
"Samples": ["Gameplay/-hitnormal"]
|
"Samples": ["Gameplay/normal-hitnormal"]
|
||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
"StartTime": 3750.0,
|
"StartTime": 3750.0,
|
||||||
|
@ -138,8 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||||
return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples)
|
return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples)
|
||||||
&& mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples)
|
&& mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples)
|
||||||
&& mergedSlider.Samples.SequenceEqual(slider1.Samples)
|
&& mergedSlider.Samples.SequenceEqual(slider1.Samples);
|
||||||
&& mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("slider end is at same completion for last slider", () =>
|
AddAssert("slider end is at same completion for last slider", () =>
|
||||||
|
@ -181,10 +181,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
if (slider is null) return;
|
if (slider is null) return;
|
||||||
|
|
||||||
slider.SampleControlPoint.SampleBank = "soft";
|
sample = new HitSampleInfo("hitwhistle", "soft", volume: 70);
|
||||||
slider.SampleControlPoint.SampleVolume = 70;
|
slider.Samples.Add(sample.With());
|
||||||
sample = new HitSampleInfo("hitwhistle");
|
|
||||||
slider.Samples.Add(sample);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select added slider", () =>
|
AddStep("select added slider", () =>
|
||||||
@ -207,9 +205,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddAssert("sliders have hitsounds", hasHitsounds);
|
AddAssert("sliders have hitsounds", hasHitsounds);
|
||||||
|
|
||||||
bool hasHitsounds() => sample is not null &&
|
bool hasHitsounds() => sample is not null &&
|
||||||
EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == "soft" &&
|
EditorBeatmap.HitObjects.All(o => o.Samples.Contains(sample));
|
||||||
o.SampleControlPoint.SampleVolume == 70 &&
|
|
||||||
o.Samples.Contains(sample));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
|
private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
|
||||||
|
@ -199,8 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
Precision.AlmostEquals(circle.StartTime, time, 1)
|
Precision.AlmostEquals(circle.StartTime, time, 1)
|
||||||
&& Precision.AlmostEquals(circle.Position, position, 0.01f)
|
&& Precision.AlmostEquals(circle.Position, position, 0.01f)
|
||||||
&& circle.NewCombo == startsNewCombo
|
&& circle.NewCombo == startsNewCombo
|
||||||
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples)
|
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples);
|
||||||
&& circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool sliderRestored(Slider slider)
|
private bool sliderRestored(Slider slider)
|
||||||
|
19
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs
Normal file
19
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneOsuModBubbles : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestOsuModBubbles() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModBubbles(),
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -8,6 +8,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -18,6 +19,7 @@ using osu.Framework.Testing.Input;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable background;
|
private Drawable background;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> ripples = new Bindable<bool>();
|
||||||
|
|
||||||
public TestSceneGameplayCursor()
|
public TestSceneGameplayCursor()
|
||||||
{
|
{
|
||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
@ -57,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddToggleStep("ripples", v => ripples.Value = v);
|
||||||
|
|
||||||
AddSliderStep("circle size", 0f, 10f, 0f, val =>
|
AddSliderStep("circle size", 0f, 10f, 0f, val =>
|
||||||
{
|
{
|
||||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||||
@ -67,6 +73,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("test cursor container", () => loadContent(false));
|
AddStep("test cursor container", () => loadContent(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var rulesetConfig = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||||
|
rulesetConfig.BindWith(OsuRulesetSetting.ShowCursorRipples, ripples);
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(1, 1)]
|
[TestCase(1, 1)]
|
||||||
[TestCase(5, 1)]
|
[TestCase(5, 1)]
|
||||||
[TestCase(10, 1)]
|
[TestCase(10, 1)]
|
||||||
|
@ -439,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public TestSlider()
|
public TestSlider()
|
||||||
{
|
{
|
||||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
SliderVelocity = 0.1f;
|
||||||
|
|
||||||
DefaultsApplied += _ =>
|
DefaultsApplied += _ =>
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,10 @@ using osuTK.Graphics;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -22,6 +26,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
@ -30,6 +35,27 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
private int depthIndex;
|
private int depthIndex;
|
||||||
|
|
||||||
|
private readonly BindableBool snakingIn = new BindableBool();
|
||||||
|
private readonly BindableBool snakingOut = new BindableBool();
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddToggleStep("toggle snaking", v =>
|
||||||
|
{
|
||||||
|
snakingIn.Value = v;
|
||||||
|
snakingOut.Value = v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||||
|
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
|
||||||
|
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestVariousSliders()
|
public void TestVariousSliders()
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -47,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
StartTime = time_slider_start,
|
StartTime = time_slider_start,
|
||||||
Position = new Vector2(0, 0),
|
Position = new Vector2(0, 0),
|
||||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity },
|
SliderVelocity = velocity,
|
||||||
Path = new SliderPath(PathType.Linear, new[]
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -350,7 +349,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
StartTime = time_slider_start,
|
StartTime = time_slider_start,
|
||||||
Position = new Vector2(0, 0),
|
Position = new Vector2(0, 0),
|
||||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f },
|
SliderVelocity = 0.1f,
|
||||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
|
@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public TestSlider()
|
public TestSlider()
|
||||||
{
|
{
|
||||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
SliderVelocity = 0.1f;
|
||||||
|
|
||||||
DefaultsApplied += _ =>
|
DefaultsApplied += _ =>
|
||||||
{
|
{
|
||||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
{
|
{
|
||||||
var positionData = original as IHasPosition;
|
var positionData = original as IHasPosition;
|
||||||
var comboData = original as IHasCombo;
|
var comboData = original as IHasCombo;
|
||||||
|
var sliderVelocityData = original as IHasSliderVelocity;
|
||||||
|
var generateTicksData = original as IHasGenerateTicks;
|
||||||
|
|
||||||
switch (original)
|
switch (original)
|
||||||
{
|
{
|
||||||
@ -47,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
||||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1
|
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
|
||||||
|
GenerateTicks = generateTicksData?.GenerateTicks ?? true,
|
||||||
|
SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1,
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
case IHasDuration endTimeData:
|
case IHasDuration endTimeData:
|
||||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
|||||||
SetDefault(OsuRulesetSetting.SnakingInSliders, true);
|
SetDefault(OsuRulesetSetting.SnakingInSliders, true);
|
||||||
SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
|
SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
|
||||||
SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
|
SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
|
||||||
|
SetDefault(OsuRulesetSetting.ShowCursorRipples, false);
|
||||||
SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
|
SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
|||||||
SnakingInSliders,
|
SnakingInSliders,
|
||||||
SnakingOutSliders,
|
SnakingOutSliders,
|
||||||
ShowCursorTrail,
|
ShowCursorTrail,
|
||||||
|
ShowCursorRipples,
|
||||||
PlayfieldBorderStyle,
|
PlayfieldBorderStyle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -83,11 +82,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
case SliderPlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
|
||||||
var nearestDifficultyPoint = editorBeatmap.HitObjects
|
double? nearestSliderVelocity = (editorBeatmap.HitObjects
|
||||||
.LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)?
|
.LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocity;
|
||||||
.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
|
|
||||||
|
|
||||||
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
HitObject.SliderVelocity = nearestSliderVelocity ?? 1;
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
|
||||||
// Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation.
|
// Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation.
|
||||||
|
@ -14,7 +14,6 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -311,17 +310,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
var splitControlPoints = controlPoints.Take(index + 1).ToList();
|
var splitControlPoints = controlPoints.Take(index + 1).ToList();
|
||||||
controlPoints.RemoveRange(0, index);
|
controlPoints.RemoveRange(0, index);
|
||||||
|
|
||||||
// Turn the control points which were split off into a new slider.
|
|
||||||
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
|
|
||||||
var difficultyPoint = (DifficultyControlPoint)HitObject.DifficultyControlPoint.DeepClone();
|
|
||||||
|
|
||||||
var newSlider = new Slider
|
var newSlider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = HitObject.StartTime,
|
StartTime = HitObject.StartTime,
|
||||||
Position = HitObject.Position + splitControlPoints[0].Position,
|
Position = HitObject.Position + splitControlPoints[0].Position,
|
||||||
NewCombo = HitObject.NewCombo,
|
NewCombo = HitObject.NewCombo,
|
||||||
SampleControlPoint = samplePoint,
|
|
||||||
DifficultyControlPoint = difficultyPoint,
|
|
||||||
LegacyLastTickOffset = HitObject.LegacyLastTickOffset,
|
LegacyLastTickOffset = HitObject.LegacyLastTickOffset,
|
||||||
Samples = HitObject.Samples.Select(s => s.With()).ToList(),
|
Samples = HitObject.Samples.Select(s => s.With()).ToList(),
|
||||||
RepeatCount = HitObject.RepeatCount,
|
RepeatCount = HitObject.RepeatCount,
|
||||||
@ -378,15 +371,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
||||||
|
|
||||||
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
|
|
||||||
samplePoint.Time = time;
|
|
||||||
|
|
||||||
editorBeatmap.Add(new HitCircle
|
editorBeatmap.Add(new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = time,
|
StartTime = time,
|
||||||
Position = position,
|
Position = position,
|
||||||
NewCombo = i == 0 && HitObject.NewCombo,
|
NewCombo = i == 0 && HitObject.NewCombo,
|
||||||
SampleControlPoint = samplePoint,
|
|
||||||
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
|
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ using osu.Framework.Extensions.EnumExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -62,7 +62,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
// Give a bit of breathing room around the playfield content.
|
// Give a bit of breathing room around the playfield content.
|
||||||
PlayfieldContentContainer.Padding = new MarginPadding(10);
|
PlayfieldContentContainer.Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 10,
|
||||||
|
Left = TOOLBOX_CONTRACTED_SIZE_LEFT + 10,
|
||||||
|
Right = TOOLBOX_CONTRACTED_SIZE_RIGHT + 10,
|
||||||
|
};
|
||||||
|
|
||||||
LayerBelowRuleset.AddRange(new Drawable[]
|
LayerBelowRuleset.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
StartTime = firstHitObject.StartTime,
|
StartTime = firstHitObject.StartTime,
|
||||||
Position = firstHitObject.Position,
|
Position = firstHitObject.Position,
|
||||||
NewCombo = firstHitObject.NewCombo,
|
NewCombo = firstHitObject.NewCombo,
|
||||||
SampleControlPoint = firstHitObject.SampleControlPoint,
|
|
||||||
Samples = firstHitObject.Samples,
|
Samples = firstHitObject.Samples,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -10,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModBarrelRoll : ModBarrelRoll<OsuHitObject>, IApplicableToDrawableHitObject
|
public class OsuModBarrelRoll : ModBarrelRoll<OsuHitObject>, IApplicableToDrawableHitObject
|
||||||
{
|
{
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModBubbles) };
|
||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject d)
|
public void ApplyToDrawableHitObject(DrawableHitObject d)
|
||||||
{
|
{
|
||||||
d.OnUpdate += _ =>
|
d.OnUpdate += _ =>
|
||||||
|
214
osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
Normal file
214
osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public partial class OsuModBubbles : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToDrawableHitObject, IApplicableToScoreProcessor
|
||||||
|
{
|
||||||
|
public override string Name => "Bubbles";
|
||||||
|
|
||||||
|
public override string Acronym => "BU";
|
||||||
|
|
||||||
|
public override LocalisableString Description => "Don't let their popping distract you!";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
|
||||||
|
// Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
|
private PlayfieldAdjustmentContainer bubbleContainer = null!;
|
||||||
|
|
||||||
|
private readonly Bindable<int> currentCombo = new BindableInt();
|
||||||
|
|
||||||
|
private float maxSize;
|
||||||
|
private float bubbleSize;
|
||||||
|
private double bubbleFade;
|
||||||
|
|
||||||
|
private readonly DrawablePool<BubbleDrawable> bubblePool = new DrawablePool<BubbleDrawable>(100);
|
||||||
|
|
||||||
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||||
|
|
||||||
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
|
{
|
||||||
|
currentCombo.BindTo(scoreProcessor.Combo);
|
||||||
|
currentCombo.BindValueChanged(combo =>
|
||||||
|
maxSize = Math.Min(1.75f, (float)(1.25 + 0.005 * combo.NewValue)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
// Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen
|
||||||
|
// Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size
|
||||||
|
bubbleSize = (float)(drawableRuleset.Beatmap.HitObjects.OfType<HitCircle>().First().Radius * 1.90f);
|
||||||
|
bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType<HitCircle>().First().TimePreempt * 2;
|
||||||
|
|
||||||
|
// We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering)
|
||||||
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
|
|
||||||
|
bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer();
|
||||||
|
|
||||||
|
drawableRuleset.Overlays.Add(bubbleContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
drawableObject.OnNewResult += (drawable, _) =>
|
||||||
|
{
|
||||||
|
if (drawable is not DrawableOsuHitObject drawableOsuHitObject) return;
|
||||||
|
|
||||||
|
switch (drawableOsuHitObject.HitObject)
|
||||||
|
{
|
||||||
|
case Slider:
|
||||||
|
case SpinnerTick:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
addBubble();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addBubble()
|
||||||
|
{
|
||||||
|
BubbleDrawable bubble = bubblePool.Get();
|
||||||
|
|
||||||
|
bubble.DrawableOsuHitObject = drawableOsuHitObject;
|
||||||
|
bubble.InitialSize = new Vector2(bubbleSize);
|
||||||
|
bubble.FadeTime = bubbleFade;
|
||||||
|
bubble.MaxSize = maxSize;
|
||||||
|
|
||||||
|
bubbleContainer.Add(bubble);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
drawableObject.OnRevertResult += (drawable, _) =>
|
||||||
|
{
|
||||||
|
if (drawable.HitObject is SpinnerTick or Slider) return;
|
||||||
|
|
||||||
|
BubbleDrawable? lastBubble = bubbleContainer.OfType<BubbleDrawable>().LastOrDefault();
|
||||||
|
|
||||||
|
lastBubble?.ClearTransforms();
|
||||||
|
lastBubble?.Expire(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Pooled Bubble drawable
|
||||||
|
|
||||||
|
private partial class BubbleDrawable : PoolableDrawable
|
||||||
|
{
|
||||||
|
public DrawableOsuHitObject? DrawableOsuHitObject { get; set; }
|
||||||
|
|
||||||
|
public Vector2 InitialSize { get; set; }
|
||||||
|
|
||||||
|
public float MaxSize { get; set; }
|
||||||
|
|
||||||
|
public double FadeTime { get; set; }
|
||||||
|
|
||||||
|
private readonly Box colourBox;
|
||||||
|
private readonly CircularContainer content;
|
||||||
|
|
||||||
|
public BubbleDrawable()
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
InternalChild = content = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
MaskingSmoothness = 2,
|
||||||
|
BorderThickness = 0,
|
||||||
|
BorderColour = Colour4.White,
|
||||||
|
Masking = true,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 3,
|
||||||
|
Colour = Colour4.Black.Opacity(0.05f),
|
||||||
|
},
|
||||||
|
Child = colourBox = new Box { RelativeSizeAxes = Axes.Both, }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareForUse()
|
||||||
|
{
|
||||||
|
Debug.Assert(DrawableOsuHitObject.IsNotNull());
|
||||||
|
|
||||||
|
Colour = DrawableOsuHitObject.IsHit ? Colour4.White : Colour4.Black;
|
||||||
|
Scale = new Vector2(1);
|
||||||
|
Position = getPosition(DrawableOsuHitObject);
|
||||||
|
Size = InitialSize;
|
||||||
|
|
||||||
|
//We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect.
|
||||||
|
ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f);
|
||||||
|
|
||||||
|
// The absolute length of the bubble's animation, can be used in fractions for animations of partial length
|
||||||
|
double duration = 1700 + Math.Pow(FadeTime, 1.07f);
|
||||||
|
|
||||||
|
// Main bubble scaling based on combo
|
||||||
|
this.FadeTo(1)
|
||||||
|
.ScaleTo(MaxSize, duration * 0.8f)
|
||||||
|
.Then()
|
||||||
|
// Pop at the end of the bubbles life time
|
||||||
|
.ScaleTo(MaxSize * 1.5f, duration * 0.2f, Easing.OutQuint)
|
||||||
|
.FadeOut(duration * 0.2f, Easing.OutCirc).Expire();
|
||||||
|
|
||||||
|
if (!DrawableOsuHitObject.IsHit) return;
|
||||||
|
|
||||||
|
content.BorderThickness = InitialSize.X / 3.5f;
|
||||||
|
content.BorderColour = Colour4.White;
|
||||||
|
|
||||||
|
colourBox.FadeColour(colourDarker);
|
||||||
|
|
||||||
|
content.TransformTo(nameof(BorderColour), colourDarker, duration * 0.3f, Easing.OutQuint);
|
||||||
|
// Ripple effect utilises the border to reduce drawable count
|
||||||
|
content.TransformTo(nameof(BorderThickness), 2f, duration * 0.3f, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
// Avoids transparency overlap issues during the bubble "pop"
|
||||||
|
.TransformTo(nameof(BorderThickness), 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 getPosition(DrawableOsuHitObject drawableObject)
|
||||||
|
{
|
||||||
|
switch (drawableObject)
|
||||||
|
{
|
||||||
|
// SliderHeads are derived from HitCircles,
|
||||||
|
// so we must handle them before to avoid them using the wrong positioning logic
|
||||||
|
case DrawableSliderHead:
|
||||||
|
return drawableObject.HitObject.Position;
|
||||||
|
|
||||||
|
// Using hitobject position will cause issues with HitCircle placement due to stack leniency.
|
||||||
|
case DrawableHitCircle:
|
||||||
|
return drawableObject.Position;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return drawableObject.HitObject.Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
||||||
public override double ScoreMultiplier => 0.5;
|
public override double ScoreMultiplier => 0.5;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles) };
|
||||||
|
|
||||||
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
||||||
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "Hit objects run away!";
|
public override LocalisableString Description => "Hit objects run away!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles) };
|
||||||
|
|
||||||
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||||
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||||
|
@ -133,14 +133,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||||
|
|
||||||
if (HitObject.SampleControlPoint == null)
|
Samples.Samples = HitObject.TailSamples.Cast<ISampleInfo>().ToArray();
|
||||||
{
|
slidingSample.Samples = HitObject.CreateSlidingSamples().Cast<ISampleInfo>().ToArray();
|
||||||
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
|
||||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
|
||||||
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void StopAllSamples()
|
public override void StopAllSamples()
|
||||||
|
@ -87,12 +87,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
|
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||||
|
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||||
|
|
||||||
animDuration = Math.Min(300, HitObject.SpanDuration);
|
animDuration = Math.Min(300, HitObject.SpanDuration);
|
||||||
|
|
||||||
this.Animate(
|
this
|
||||||
d => d.FadeIn(animDuration),
|
.FadeOut()
|
||||||
d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf)
|
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||||
);
|
.FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
|
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||||
|
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||||
|
|
||||||
|
CirclePiece
|
||||||
|
.FadeOut()
|
||||||
|
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||||
|
.FadeIn(HitObject.TimeFadeIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.LoadSamples();
|
base.LoadSamples();
|
||||||
|
|
||||||
spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
spinningSample.Samples = HitObject.CreateSpinningSamples().Cast<ISampleInfo>().ToArray();
|
||||||
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,18 +10,18 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects
|
namespace osu.Game.Rulesets.Osu.Objects
|
||||||
{
|
{
|
||||||
public class Slider : OsuHitObject, IHasPathWithRepeats
|
public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks
|
||||||
{
|
{
|
||||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||||
|
|
||||||
@ -134,6 +134,21 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool OnlyJudgeNestedObjects = true;
|
public bool OnlyJudgeNestedObjects = true;
|
||||||
|
|
||||||
|
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
public double SliderVelocity
|
||||||
|
{
|
||||||
|
get => SliderVelocityBindable.Value;
|
||||||
|
set => SliderVelocityBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GenerateTicks { get; set; } = true;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public SliderHeadCircle HeadCircle { get; protected set; }
|
public SliderHeadCircle HeadCircle { get; protected set; }
|
||||||
|
|
||||||
@ -151,15 +166,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
#pragma warning disable 618
|
|
||||||
var legacyDifficultyPoint = DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint;
|
|
||||||
#pragma warning restore 618
|
|
||||||
|
|
||||||
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity;
|
||||||
bool generateTicks = legacyDifficultyPoint?.GenerateTicks ?? true;
|
|
||||||
|
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
|
TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
@ -39,11 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// taken from osu-stable
|
|
||||||
const float first_end_circle_preempt_adjust = 2 / 3f;
|
|
||||||
|
|
||||||
// The first end circle should fade in with the slider.
|
// The first end circle should fade in with the slider.
|
||||||
TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust;
|
TimePreempt += StartTime - slider.StartTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
AddNested(i < SpinsRequired
|
AddNested(i < SpinsRequired
|
||||||
? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
|
? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
|
||||||
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration });
|
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration, Samples = new[] { GetSampleInfo("spinnerbonus") } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin")
|
referenceSample.With("spinnerspin")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
@ -11,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
public class SpinnerBonusTick : SpinnerTick
|
public class SpinnerBonusTick : SpinnerTick
|
||||||
{
|
{
|
||||||
public SpinnerBonusTick()
|
|
||||||
{
|
|
||||||
Samples.Add(new HitSampleInfo("spinnerbonus"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement();
|
public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement();
|
||||||
|
|
||||||
public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
|
public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
|
||||||
|
@ -203,7 +203,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModNoScope(),
|
new OsuModNoScope(),
|
||||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||||
new ModAdaptiveSpeed(),
|
new ModAdaptiveSpeed(),
|
||||||
new OsuModFreezeFrame()
|
new OsuModFreezeFrame(),
|
||||||
|
new OsuModBubbles()
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
Cursor,
|
Cursor,
|
||||||
CursorTrail,
|
CursorTrail,
|
||||||
CursorParticles,
|
CursorParticles,
|
||||||
|
CursorRipple,
|
||||||
SliderScorePoint,
|
SliderScorePoint,
|
||||||
ReverseArrow,
|
ReverseArrow,
|
||||||
HitCircleText,
|
HitCircleText,
|
||||||
|
@ -98,7 +98,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
|
|
||||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
{
|
{
|
||||||
this.FadeOut(duration, Easing.OutQuint);
|
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||||
|
this.FadeOut(duration / 4, Easing.OutQuint);
|
||||||
icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint);
|
icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
@ -18,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
private Drawable proxy = null!;
|
private Drawable proxy = null!;
|
||||||
|
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
private bool textureIsDefaultSkin;
|
||||||
|
|
||||||
|
private Drawable arrow = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skinSource)
|
private void load(ISkinSource skinSource)
|
||||||
{
|
{
|
||||||
@ -26,7 +34,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName;
|
string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName;
|
||||||
|
|
||||||
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
||||||
InternalChild = skin?.GetAnimation(lookupName, true, true) ?? Empty();
|
|
||||||
|
InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true) ?? Empty());
|
||||||
|
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -39,6 +49,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
drawableHitObject.HitObjectApplied += onHitObjectApplied;
|
drawableHitObject.HitObjectApplied += onHitObjectApplied;
|
||||||
onHitObjectApplied(drawableHitObject);
|
onHitObjectApplied(drawableHitObject);
|
||||||
|
|
||||||
|
accentColour = drawableHitObject.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(c =>
|
||||||
|
{
|
||||||
|
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White;
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +100,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorRipple:
|
||||||
|
if (GetTexture("cursor-ripple") != null)
|
||||||
|
{
|
||||||
|
var ripple = this.GetAnimation("cursor-ripple", false, false);
|
||||||
|
|
||||||
|
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
|
||||||
|
// If anyone complains about these not being applied, this can be uncommented.
|
||||||
|
//
|
||||||
|
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
|
||||||
|
// so we might be okay.
|
||||||
|
//
|
||||||
|
// if (ripple != null)
|
||||||
|
// {
|
||||||
|
// ripple.Scale = new Vector2(0.5f);
|
||||||
|
// ripple.Alpha = 0.2f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ripple;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.CursorParticles:
|
case OsuSkinComponents.CursorParticles:
|
||||||
if (GetTexture("star2") != null)
|
if (GetTexture("star2") != null)
|
||||||
return new LegacyCursorParticles();
|
return new LegacyCursorParticles();
|
||||||
|
105
osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs
Normal file
105
osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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.Pooling;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||||
|
{
|
||||||
|
public partial class CursorRippleVisualiser : CompositeDrawable, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private readonly Bindable<bool> showRipples = new Bindable<bool>(true);
|
||||||
|
|
||||||
|
private readonly DrawablePool<CursorRipple> ripplePool = new DrawablePool<CursorRipple>(20);
|
||||||
|
|
||||||
|
public CursorRippleVisualiser()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 CursorScale { get; set; } = Vector2.One;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(OsuRulesetConfigManager? rulesetConfig)
|
||||||
|
{
|
||||||
|
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorRipples, showRipples);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
if (showRipples.Value)
|
||||||
|
{
|
||||||
|
AddInternal(ripplePool.Get(r =>
|
||||||
|
{
|
||||||
|
r.Position = e.MousePosition;
|
||||||
|
r.Scale = CursorScale;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class CursorRipple : PoolableDrawable
|
||||||
|
{
|
||||||
|
private Drawable ripple = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChild = ripple = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple())
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareForUse()
|
||||||
|
{
|
||||||
|
base.PrepareForUse();
|
||||||
|
|
||||||
|
ClearTransforms(true);
|
||||||
|
|
||||||
|
ripple.ScaleTo(0.1f)
|
||||||
|
.ScaleTo(1, 700, Easing.Out);
|
||||||
|
|
||||||
|
this
|
||||||
|
.FadeOutFromOne(700)
|
||||||
|
.Expire(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class DefaultCursorRipple : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new RingPiece(3)
|
||||||
|
{
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2),
|
||||||
|
Alpha = 0.1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private Bindable<float> userCursorScale;
|
private Bindable<float> userCursorScale;
|
||||||
private Bindable<bool> autoCursorScale;
|
private Bindable<bool> autoCursorScale;
|
||||||
|
|
||||||
|
private readonly CursorRippleVisualiser rippleVisualiser;
|
||||||
|
|
||||||
public OsuCursorContainer()
|
public OsuCursorContainer()
|
||||||
{
|
{
|
||||||
InternalChild = fadeContainer = new Container
|
InternalChild = fadeContainer = new Container
|
||||||
@ -48,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
|
cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
|
||||||
|
rippleVisualiser = new CursorRippleVisualiser(),
|
||||||
new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
|
new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -82,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
var newScale = new Vector2(e.NewValue);
|
var newScale = new Vector2(e.NewValue);
|
||||||
|
|
||||||
ActiveCursor.Scale = newScale;
|
ActiveCursor.Scale = newScale;
|
||||||
|
rippleVisualiser.CursorScale = newScale;
|
||||||
cursorTrail.Scale = newScale;
|
cursorTrail.Scale = newScale;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
LabelText = RulesetSettingsStrings.CursorTrail,
|
LabelText = RulesetSettingsStrings.CursorTrail,
|
||||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||||
},
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = RulesetSettingsStrings.CursorRipples,
|
||||||
|
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples)
|
||||||
|
},
|
||||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||||
{
|
{
|
||||||
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||||
|
@ -64,7 +64,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
foreach (HitObject hitObject in original.HitObjects)
|
foreach (HitObject hitObject in original.HitObjects)
|
||||||
{
|
{
|
||||||
double nextScrollSpeed = hitObject.DifficultyControlPoint.SliderVelocity;
|
if (hitObject is not IHasSliderVelocity hasSliderVelocity) continue;
|
||||||
|
|
||||||
|
double nextScrollSpeed = hasSliderVelocity.SliderVelocity;
|
||||||
EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime);
|
EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime);
|
||||||
|
|
||||||
if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision))
|
if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision))
|
||||||
@ -131,7 +133,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
Duration = taikoDuration,
|
Duration = taikoDuration,
|
||||||
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4
|
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
|
||||||
|
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,15 +180,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint;
|
|
||||||
|
|
||||||
double beatLength;
|
double beatLength;
|
||||||
#pragma warning disable 618
|
if (obj.LegacyBpmMultiplier.HasValue)
|
||||||
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
|
beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value;
|
||||||
#pragma warning restore 618
|
else if (obj is IHasSliderVelocity hasSliderVelocity)
|
||||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
|
||||||
else
|
else
|
||||||
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
beatLength = timingPoint.BeatLength;
|
||||||
|
|
||||||
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
|
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
|
||||||
|
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
@ -15,7 +18,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
public class DrumRoll : TaikoStrongableHitObject, IHasPath
|
public class DrumRoll : TaikoStrongableHitObject, IHasPath, IHasSliderVelocity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
||||||
@ -35,6 +38,19 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double Velocity { get; private set; }
|
public double Velocity { get; private set; }
|
||||||
|
|
||||||
|
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
public double SliderVelocity
|
||||||
|
{
|
||||||
|
get => SliderVelocityBindable.Value;
|
||||||
|
set => SliderVelocityBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Numer of ticks per beat length.
|
/// Numer of ticks per beat length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -52,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
|
||||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
|
|
||||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||||
@ -81,7 +97,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
FirstTick = first,
|
FirstTick = first,
|
||||||
TickSpacing = tickSpacing,
|
TickSpacing = tickSpacing,
|
||||||
StartTime = t,
|
StartTime = t,
|
||||||
IsStrong = IsStrong
|
IsStrong = IsStrong,
|
||||||
|
Samples = Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToList()
|
||||||
});
|
});
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
if (IsStrongBindable.Value != strongSamples.Any())
|
if (IsStrongBindable.Value != strongSamples.Any())
|
||||||
{
|
{
|
||||||
if (IsStrongBindable.Value)
|
if (IsStrongBindable.Value)
|
||||||
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
Samples.Add(GetSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var sample in strongSamples)
|
foreach (var sample in strongSamples)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -17,12 +18,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
public void Play(HitType hitType)
|
public void Play(HitType hitType)
|
||||||
{
|
{
|
||||||
var hitObject = GetMostValidObject();
|
var hitSample = GetMostValidObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||||
|
|
||||||
if (hitObject == null)
|
if (hitSample == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) });
|
PlaySamples(new ISampleInfo[] { new HitSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL, hitSample.Bank, volume: hitSample.Volume) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead");
|
public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead");
|
||||||
|
@ -510,7 +510,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
|
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -528,7 +528,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
|
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -548,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
|
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
|
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -37,45 +37,6 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted });
|
cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNormalControlPointVolume()
|
|
||||||
{
|
|
||||||
var hitCircle = new HitCircle
|
|
||||||
{
|
|
||||||
StartTime = 0,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
|
||||||
};
|
|
||||||
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
assertOk(new List<HitObject> { hitCircle });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestLowControlPointVolume()
|
|
||||||
{
|
|
||||||
var hitCircle = new HitCircle
|
|
||||||
{
|
|
||||||
StartTime = 1000,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
|
||||||
};
|
|
||||||
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
assertLowVolume(new List<HitObject> { hitCircle });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMutedControlPointVolume()
|
|
||||||
{
|
|
||||||
var hitCircle = new HitCircle
|
|
||||||
{
|
|
||||||
StartTime = 2000,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
|
||||||
};
|
|
||||||
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
assertMuted(new List<HitObject> { hitCircle });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNormalSampleVolume()
|
public void TestNormalSampleVolume()
|
||||||
{
|
{
|
||||||
@ -122,7 +83,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
var sliderHead = new SliderHeadCircle
|
var sliderHead = new SliderHeadCircle
|
||||||
{
|
{
|
||||||
StartTime = 0,
|
StartTime = 0,
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||||
};
|
};
|
||||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
@ -135,7 +96,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
|
|
||||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
|
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
|
||||||
{
|
{
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||||
};
|
};
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
@ -155,13 +116,13 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
var sliderTick = new SliderTick
|
var sliderTick = new SliderTick
|
||||||
{
|
{
|
||||||
StartTime = 250,
|
StartTime = 250,
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick", volume: volume_regular) }
|
||||||
};
|
};
|
||||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
|
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
|
||||||
{
|
{
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail.
|
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } // Applies to the tail.
|
||||||
};
|
};
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
@ -174,14 +135,14 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
var sliderHead = new SliderHeadCircle
|
var sliderHead = new SliderHeadCircle
|
||||||
{
|
{
|
||||||
StartTime = 0,
|
StartTime = 0,
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||||
};
|
};
|
||||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
var sliderTick = new SliderTick
|
var sliderTick = new SliderTick
|
||||||
{
|
{
|
||||||
StartTime = 250,
|
StartTime = 250,
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick", volume: volume_regular) }
|
||||||
};
|
};
|
||||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
|
||||||
@ -194,59 +155,6 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
assertMutedPassive(new List<HitObject> { slider });
|
assertMutedPassive(new List<HitObject> { slider });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMutedControlPointVolumeSliderHead()
|
|
||||||
{
|
|
||||||
var sliderHead = new SliderHeadCircle
|
|
||||||
{
|
|
||||||
StartTime = 2000,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
|
||||||
};
|
|
||||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
var sliderTick = new SliderTick
|
|
||||||
{
|
|
||||||
StartTime = 2250,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
|
||||||
};
|
|
||||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500)
|
|
||||||
{
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
|
||||||
};
|
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
assertMuted(new List<HitObject> { slider });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMutedControlPointVolumeSliderTail()
|
|
||||||
{
|
|
||||||
var sliderHead = new SliderHeadCircle
|
|
||||||
{
|
|
||||||
StartTime = 0,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
|
||||||
};
|
|
||||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
var sliderTick = new SliderTick
|
|
||||||
{
|
|
||||||
StartTime = 250,
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
|
||||||
};
|
|
||||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
// Ends after the 5% control point.
|
|
||||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 2500)
|
|
||||||
{
|
|
||||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
|
||||||
};
|
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
|
||||||
|
|
||||||
assertMutedPassive(new List<HitObject> { slider });
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertOk(List<HitObject> hitObjects)
|
private void assertOk(List<HitObject> hitObjects)
|
||||||
{
|
{
|
||||||
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
|
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
@ -74,12 +75,9 @@ namespace osu.Game.Tests.Editing
|
|||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier)
|
public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier)
|
||||||
{
|
{
|
||||||
assertSnapDistance(100, new HitObject
|
assertSnapDistance(100, new Slider
|
||||||
{
|
{
|
||||||
DifficultyControlPoint = new DifficultyControlPoint
|
SliderVelocity = multiplier
|
||||||
{
|
|
||||||
SliderVelocity = multiplier
|
|
||||||
}
|
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +85,9 @@ namespace osu.Game.Tests.Editing
|
|||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
public void TestSpeedMultiplierDoesChangeDistanceSnap(float multiplier)
|
public void TestSpeedMultiplierDoesChangeDistanceSnap(float multiplier)
|
||||||
{
|
{
|
||||||
assertSnapDistance(100 * multiplier, new HitObject
|
assertSnapDistance(100 * multiplier, new Slider
|
||||||
{
|
{
|
||||||
DifficultyControlPoint = new DifficultyControlPoint
|
SliderVelocity = multiplier
|
||||||
{
|
|
||||||
SliderVelocity = multiplier
|
|
||||||
}
|
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,12 +109,9 @@ namespace osu.Game.Tests.Editing
|
|||||||
const float base_distance = 100;
|
const float base_distance = 100;
|
||||||
const float slider_velocity = 1.2f;
|
const float slider_velocity = 1.2f;
|
||||||
|
|
||||||
var referenceObject = new HitObject
|
var referenceObject = new Slider
|
||||||
{
|
{
|
||||||
DifficultyControlPoint = new DifficultyControlPoint
|
SliderVelocity = slider_velocity
|
||||||
{
|
|
||||||
SliderVelocity = slider_velocity
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assertSnapDistance(base_distance * slider_velocity, referenceObject, true);
|
assertSnapDistance(base_distance * slider_velocity, referenceObject, true);
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
#include "sh_Utils.h"
|
#define HIGH_PRECISION_VERTEX
|
||||||
|
|
||||||
varying mediump vec2 v_TexCoord;
|
#include "sh_Utils.h"
|
||||||
varying mediump vec4 v_TexRect;
|
#include "sh_Masking.h"
|
||||||
|
|
||||||
|
layout(location = 2) in highp vec2 v_TexCoord;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 o_Colour;
|
||||||
|
|
||||||
void main(void)
|
void main(void)
|
||||||
{
|
{
|
||||||
float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]);
|
highp float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]);
|
||||||
gl_FragColor = hsv2rgb(vec4(hueValue, 1, 1, 1));
|
o_Colour = getRoundedColor(hsv2rgb(vec4(hueValue, 1, 1, 1)), v_TexCoord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,31 +1,25 @@
|
|||||||
#include "sh_Utils.h"
|
layout(location = 0) in highp vec2 m_Position;
|
||||||
|
layout(location = 1) in lowp vec4 m_Colour;
|
||||||
|
layout(location = 2) in highp vec2 m_TexCoord;
|
||||||
|
layout(location = 3) in highp vec4 m_TexRect;
|
||||||
|
layout(location = 4) in mediump vec2 m_BlendRange;
|
||||||
|
|
||||||
attribute highp vec2 m_Position;
|
layout(location = 0) out highp vec2 v_MaskingPosition;
|
||||||
attribute lowp vec4 m_Colour;
|
layout(location = 1) out lowp vec4 v_Colour;
|
||||||
attribute mediump vec2 m_TexCoord;
|
layout(location = 2) out highp vec2 v_TexCoord;
|
||||||
attribute mediump vec4 m_TexRect;
|
layout(location = 3) out highp vec4 v_TexRect;
|
||||||
attribute mediump vec2 m_BlendRange;
|
layout(location = 4) out mediump vec2 v_BlendRange;
|
||||||
|
|
||||||
varying highp vec2 v_MaskingPosition;
|
|
||||||
varying lowp vec4 v_Colour;
|
|
||||||
varying mediump vec2 v_TexCoord;
|
|
||||||
varying mediump vec4 v_TexRect;
|
|
||||||
varying mediump vec2 v_BlendRange;
|
|
||||||
|
|
||||||
uniform highp mat4 g_ProjMatrix;
|
|
||||||
uniform highp mat3 g_ToMaskingSpace;
|
|
||||||
|
|
||||||
void main(void)
|
void main(void)
|
||||||
{
|
{
|
||||||
// Transform from screen space to masking space.
|
// Transform from screen space to masking space.
|
||||||
highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
|
highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
|
||||||
v_MaskingPosition = maskingPos.xy / maskingPos.z;
|
v_MaskingPosition = maskingPos.xy / maskingPos.z;
|
||||||
|
|
||||||
v_Colour = m_Colour;
|
v_Colour = m_Colour;
|
||||||
v_TexCoord = m_TexCoord;
|
v_TexCoord = m_TexCoord;
|
||||||
v_TexRect = m_TexRect;
|
v_TexRect = m_TexRect;
|
||||||
v_BlendRange = m_BlendRange;
|
v_BlendRange = m_BlendRange;
|
||||||
|
|
||||||
gl_Position = gProjMatrix * vec4(m_Position, 1.0, 1.0);
|
gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,8 @@ namespace osu.Game.Tests.Testing
|
|||||||
{
|
{
|
||||||
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestVertex.vs");
|
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestVertex.vs");
|
||||||
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestFragment.fs");
|
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestFragment.fs");
|
||||||
|
Dependencies.Get<ShaderManager>().Load(@"TestVertex", @"TestFragment");
|
||||||
|
Dependencies.Get<ShaderManager>().Load(VertexShaderDescriptor.TEXTURE_2, @"TestFragment");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -95,10 +94,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
var path = slider.Path;
|
var path = slider.Path;
|
||||||
return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints);
|
return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints);
|
||||||
});
|
});
|
||||||
|
|
||||||
// see `HitObject.control_point_leniency`.
|
|
||||||
AddAssert("sample control point has correct time", () => Precision.AlmostEquals(slider.SampleControlPoint.Time, slider.GetEndTime(), 1));
|
|
||||||
AddAssert("difficulty control point has correct time", () => slider.DifficultyControlPoint.Time == slider.StartTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -122,19 +122,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
AddAssert("Beatmap has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
||||||
|
|
||||||
// After placement these must be non-default as defaults are read-only.
|
|
||||||
AddAssert("Placed object has non-default control points", () =>
|
|
||||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) &&
|
|
||||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
|
||||||
|
|
||||||
ReloadEditorToSameBeatmap();
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
AddAssert("Beatmap still has correct timing point", () => EditorBeatmap.ControlPointInfo.TimingPoints.Single().Time == 500);
|
||||||
|
|
||||||
// After placement these must be non-default as defaults are read-only.
|
|
||||||
AddAssert("Placed object still has non-default control points", () =>
|
|
||||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) &&
|
|
||||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -108,12 +108,16 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").TriggerClick());
|
AddStep("Change to hitcircle", () => hitObjectComposer.ChildrenOfType<EditorRadioButton>().First(d => d.Button.Label == "HitCircle").TriggerClick());
|
||||||
|
|
||||||
|
ExpandingToolboxContainer toolboxContainer = null!;
|
||||||
|
|
||||||
|
AddStep("move mouse to toolbox", () => InputManager.MoveMouseTo(toolboxContainer = hitObjectComposer.ChildrenOfType<ExpandingToolboxContainer>().First()));
|
||||||
|
AddUntilStep("toolbox is expanded", () => toolboxContainer.Expanded.Value);
|
||||||
|
AddUntilStep("wait for toolbox to expand", () => toolboxContainer.LatestTransformEndTime, () => Is.EqualTo(Time.Current));
|
||||||
|
|
||||||
AddStep("move mouse to overlapping toggle button", () =>
|
AddStep("move mouse to overlapping toggle button", () =>
|
||||||
{
|
{
|
||||||
var playfield = hitObjectComposer.Playfield.ScreenSpaceDrawQuad;
|
var playfield = hitObjectComposer.Playfield.ScreenSpaceDrawQuad;
|
||||||
var button = hitObjectComposer
|
var button = toolboxContainer.ChildrenOfType<DrawableTernaryButton>().First(b => playfield.Contains(b.ScreenSpaceDrawQuad.Centre));
|
||||||
.ChildrenOfType<ExpandingToolboxContainer>().First()
|
|
||||||
.ChildrenOfType<DrawableTernaryButton>().First(b => playfield.Contains(b.ScreenSpaceDrawQuad.Centre));
|
|
||||||
|
|
||||||
InputManager.MoveMouseTo(button);
|
InputManager.MoveMouseTo(button);
|
||||||
});
|
});
|
||||||
|
@ -8,10 +8,10 @@ using Humanizer;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -61,10 +61,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
new PathControlPoint(new Vector2(100, 0))
|
new PathControlPoint(new Vector2(100, 0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DifficultyControlPoint = new DifficultyControlPoint
|
SliderVelocity = 2
|
||||||
{
|
|
||||||
SliderVelocity = 2
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,8 +97,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
AddStep("unify slider velocity", () =>
|
AddStep("unify slider velocity", () =>
|
||||||
{
|
{
|
||||||
foreach (var h in EditorBeatmap.HitObjects)
|
foreach (var h in EditorBeatmap.HitObjects.OfType<IHasSliderVelocity>())
|
||||||
h.DifficultyControlPoint.SliderVelocity = 1.5;
|
h.SliderVelocity = 1.5;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
@ -185,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () =>
|
private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () =>
|
||||||
{
|
{
|
||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
return h.DifficultyControlPoint.SliderVelocity == velocity;
|
return h is IHasSliderVelocity hasSliderVelocity && hasSliderVelocity.SliderVelocity == velocity;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -39,10 +40,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
StartTime = 0,
|
StartTime = 0,
|
||||||
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
|
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
|
||||||
SampleControlPoint = new SampleControlPoint
|
Samples = new List<HitSampleInfo>
|
||||||
{
|
{
|
||||||
SampleBank = "normal",
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 80)
|
||||||
SampleVolume = 80
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -50,10 +50,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
StartTime = 500,
|
StartTime = 500,
|
||||||
Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2,
|
Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2,
|
||||||
SampleControlPoint = new SampleControlPoint
|
Samples = new List<HitSampleInfo>
|
||||||
{
|
{
|
||||||
SampleBank = "soft",
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft", volume: 60)
|
||||||
SampleVolume = 60
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -96,7 +95,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("unify sample volume", () =>
|
AddStep("unify sample volume", () =>
|
||||||
{
|
{
|
||||||
foreach (var h in EditorBeatmap.HitObjects)
|
foreach (var h in EditorBeatmap.HitObjects)
|
||||||
h.SampleControlPoint.SampleVolume = 50;
|
{
|
||||||
|
for (int i = 0; i < h.Samples.Count; i++)
|
||||||
|
{
|
||||||
|
h.Samples[i] = h.Samples[i].With(newVolume: 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
@ -136,7 +140,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("unify sample bank", () =>
|
AddStep("unify sample bank", () =>
|
||||||
{
|
{
|
||||||
foreach (var h in EditorBeatmap.HitObjects)
|
foreach (var h in EditorBeatmap.HitObjects)
|
||||||
h.SampleControlPoint.SampleBank = "soft";
|
{
|
||||||
|
for (int i = 0; i < h.Samples.Count; i++)
|
||||||
|
{
|
||||||
|
h.Samples[i] = h.Samples[i].With(newBank: "soft");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
@ -248,7 +257,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () =>
|
private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () =>
|
||||||
{
|
{
|
||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
return h.SampleControlPoint.SampleVolume == volume;
|
return h.Samples.All(o => o.Volume == volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
|
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
|
||||||
@ -265,7 +274,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () =>
|
private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () =>
|
||||||
{
|
{
|
||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
return h.SampleControlPoint.SampleBank == bank;
|
return h.Samples.All(o => o.Bank == bank);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public partial class TestSceneLocallyModifyingOnlineBeatmaps : EditorSavingTestScene
|
||||||
|
{
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
CreateInitialBeatmap = () =>
|
||||||
|
{
|
||||||
|
var importedSet = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely();
|
||||||
|
return Game.BeatmapManager.GetWorkingBeatmap(importedSet!.Value.Beatmaps.First());
|
||||||
|
};
|
||||||
|
|
||||||
|
base.SetUpSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocallyModifyingOnlineBeatmap()
|
||||||
|
{
|
||||||
|
AddAssert("editor beatmap has online ID", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.GreaterThan(0));
|
||||||
|
|
||||||
|
AddStep("delete first hitobject", () => EditorBeatmap.RemoveAt(0));
|
||||||
|
SaveEditor();
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
AddAssert("editor beatmap online ID reset", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.EqualTo(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -77,5 +77,39 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddAssert("object has non-zero duration", () => EditorBeatmap.HitObjects.OfType<IHasDuration>().Single().Duration > 0);
|
AddAssert("object has non-zero duration", () => EditorBeatmap.HitObjects.OfType<IHasDuration>().Single().Duration > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDisallowRepeatsOnZeroDurationObjects()
|
||||||
|
{
|
||||||
|
DragArea dragArea;
|
||||||
|
|
||||||
|
AddStep("add zero length slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Clear();
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 256),
|
||||||
|
StartTime = 2700
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("hold down drag bar", () =>
|
||||||
|
{
|
||||||
|
// distinguishes between the actual drag bar and its "underlay shadow".
|
||||||
|
dragArea = this.ChildrenOfType<DragArea>().Single(bar => bar.HandlePositionalInput);
|
||||||
|
InputManager.MoveMouseTo(dragArea);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("try to extend drag bar", () =>
|
||||||
|
{
|
||||||
|
var blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().Single();
|
||||||
|
InputManager.MoveMouseTo(blueprint.SelectionQuad.TopLeft + new Vector2(100, 0));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("release button", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("object has zero repeats", () => EditorBeatmap.HitObjects.OfType<IHasRepeats>().Single().RepeatCount == 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
new HitCircle
|
new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = t += spacing,
|
StartTime = t += spacing,
|
||||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
|
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "soft") },
|
||||||
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" },
|
|
||||||
},
|
},
|
||||||
new HitCircle
|
new HitCircle
|
||||||
{
|
{
|
||||||
@ -84,8 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
StartTime = t += spacing,
|
StartTime = t += spacing,
|
||||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }),
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }),
|
||||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) },
|
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, "soft") },
|
||||||
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,6 +98,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) });
|
AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) });
|
||||||
|
|
||||||
AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0);
|
AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0);
|
||||||
|
|
||||||
|
// A previous test's mod overlay could still be fading out.
|
||||||
|
AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType<FreeModSelectOverlay>().Count() == 1);
|
||||||
|
|
||||||
assertHasFreeModButton(allowedMod, false);
|
assertHasFreeModButton(allowedMod, false);
|
||||||
assertHasFreeModButton(requiredMod, false);
|
assertHasFreeModButton(requiredMod, false);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -67,6 +68,19 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
r.Add(new ModPreset
|
||||||
|
{
|
||||||
|
Name = "Half Time 0.5x",
|
||||||
|
Description = "Very slow",
|
||||||
|
Ruleset = r.Find<RulesetInfo>(OsuRuleset.SHORT_NAME),
|
||||||
|
Mods = new[]
|
||||||
|
{
|
||||||
|
new OsuModHalfTime
|
||||||
|
{
|
||||||
|
SpeedChange = { Value = 0.5 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -566,6 +580,28 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("5 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 5);
|
AddAssert("5 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModMultiplierUpdates()
|
||||||
|
{
|
||||||
|
createScreen();
|
||||||
|
|
||||||
|
AddStep("select mod preset with half time", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ModPresetPanel>().Single(preset => preset.Preset.Value.Name == "Half Time 0.5x"));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType<DifficultyMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.5));
|
||||||
|
|
||||||
|
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
|
||||||
|
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
|
||||||
|
AddStep("force collection", GC.Collect);
|
||||||
|
|
||||||
|
AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick());
|
||||||
|
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModSettingsArea>().Single()
|
||||||
|
.ChildrenOfType<RestoreDefaultValueButton<double>>().Single().TriggerClick());
|
||||||
|
AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType<DifficultyMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.7));
|
||||||
|
}
|
||||||
|
|
||||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
||||||
() => modSelectOverlay.ChildrenOfType<ModColumn>().Any() && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
() => modSelectOverlay.ChildrenOfType<ModColumn>().Any() && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||||
|
|
||||||
|
@ -415,6 +415,13 @@ namespace osu.Game.Beatmaps
|
|||||||
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
|
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
|
||||||
beatmapContent.BeatmapInfo = beatmapInfo;
|
beatmapContent.BeatmapInfo = beatmapInfo;
|
||||||
|
|
||||||
|
// Since now this is a locally-modified beatmap, we also set all relevant flags to indicate this.
|
||||||
|
// Importantly, the `ResetOnlineInfo()` call must happen before encoding, as online ID is encoded into the `.osu` file,
|
||||||
|
// which influences the beatmap checksums.
|
||||||
|
beatmapInfo.LastLocalUpdate = DateTimeOffset.Now;
|
||||||
|
beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified;
|
||||||
|
beatmapInfo.ResetOnlineInfo();
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using (var stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
@ -438,9 +445,6 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||||
beatmapInfo.Hash = stream.ComputeSHA2Hash();
|
beatmapInfo.Hash = stream.ComputeSHA2Hash();
|
||||||
|
|
||||||
beatmapInfo.LastLocalUpdate = DateTimeOffset.Now;
|
|
||||||
beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified;
|
|
||||||
|
|
||||||
AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));
|
AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));
|
||||||
|
|
||||||
updateHashAndMarkDirty(setInfo);
|
updateHashAndMarkDirty(setInfo);
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK) { Default = DEFAULT_BANK };
|
public readonly Bindable<string> SampleBankBindable = new Bindable<string>(DEFAULT_BANK) { Default = DEFAULT_BANK };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The speed multiplier at this control point.
|
/// The default sample bank at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SampleBank
|
public string SampleBank
|
||||||
{
|
{
|
||||||
@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default sample bank at this control point.
|
/// The default sample volume at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableInt SampleVolumeBindable = new BindableInt(100)
|
public readonly BindableInt SampleVolumeBindable = new BindableInt(100)
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
#pragma warning disable 618
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -15,7 +17,9 @@ using osu.Game.Beatmaps.Legacy;
|
|||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Formats
|
namespace osu.Game.Beatmaps.Formats
|
||||||
{
|
{
|
||||||
@ -26,6 +30,11 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const int EARLY_VERSION_TIMING_OFFSET = 24;
|
public const int EARLY_VERSION_TIMING_OFFSET = 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A small adjustment to the start time of control points to account for rounding/precision errors.
|
||||||
|
/// </summary>
|
||||||
|
private const double control_point_leniency = 1;
|
||||||
|
|
||||||
internal static RulesetStore RulesetStore;
|
internal static RulesetStore RulesetStore;
|
||||||
|
|
||||||
private Beatmap beatmap;
|
private Beatmap beatmap;
|
||||||
@ -85,7 +94,45 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
|
this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
|
||||||
|
|
||||||
foreach (var hitObject in this.beatmap.HitObjects)
|
foreach (var hitObject in this.beatmap.HitObjects)
|
||||||
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty);
|
{
|
||||||
|
applyDefaults(hitObject);
|
||||||
|
applySamples(hitObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDefaults(HitObject hitObject)
|
||||||
|
{
|
||||||
|
DifficultyControlPoint difficultyControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(hitObject.StartTime) ?? DifficultyControlPoint.DEFAULT;
|
||||||
|
|
||||||
|
if (difficultyControlPoint is LegacyDifficultyControlPoint legacyDifficultyControlPoint)
|
||||||
|
{
|
||||||
|
hitObject.LegacyBpmMultiplier = legacyDifficultyControlPoint.BpmMultiplier;
|
||||||
|
if (hitObject is IHasGenerateTicks hasGenerateTicks)
|
||||||
|
hasGenerateTicks.GenerateTicks = legacyDifficultyControlPoint.GenerateTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hitObject is IHasSliderVelocity hasSliderVelocity)
|
||||||
|
hasSliderVelocity.SliderVelocity = difficultyControlPoint.SliderVelocity;
|
||||||
|
|
||||||
|
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySamples(HitObject hitObject)
|
||||||
|
{
|
||||||
|
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) ?? SampleControlPoint.DEFAULT;
|
||||||
|
|
||||||
|
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||||
|
|
||||||
|
if (hitObject is IHasRepeats hasRepeats)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
||||||
|
{
|
||||||
|
double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency;
|
||||||
|
var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT;
|
||||||
|
|
||||||
|
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -451,9 +498,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;
|
int onlineRulesetID = beatmap.BeatmapInfo.Ruleset.OnlineID;
|
||||||
|
|
||||||
#pragma warning disable 618
|
|
||||||
addControlPoint(time, new LegacyDifficultyControlPoint(onlineRulesetID, beatLength)
|
addControlPoint(time, new LegacyDifficultyControlPoint(onlineRulesetID, beatLength)
|
||||||
#pragma warning restore 618
|
|
||||||
{
|
{
|
||||||
SliderVelocity = speedMultiplier,
|
SliderVelocity = speedMultiplier,
|
||||||
}, timingChange);
|
}, timingChange);
|
||||||
|
@ -92,7 +92,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}"));
|
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank((beatmap.HitObjects.FirstOrDefault()?.SampleControlPoint ?? SampleControlPoint.DEFAULT).SampleBank)}"));
|
writer.WriteLine(FormattableString.Invariant(
|
||||||
|
$"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints?.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}"));
|
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}"));
|
writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}"));
|
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}"));
|
||||||
@ -173,9 +174,6 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
private void handleControlPoints(TextWriter writer)
|
private void handleControlPoints(TextWriter writer)
|
||||||
{
|
{
|
||||||
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var legacyControlPoints = new LegacyControlPointInfo();
|
var legacyControlPoints = new LegacyControlPointInfo();
|
||||||
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
|
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
|
||||||
legacyControlPoints.Add(point.Time, point.DeepClone());
|
legacyControlPoints.Add(point.Time, point.DeepClone());
|
||||||
@ -199,33 +197,43 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LegacyControlPointProperties lastControlPointProperties = new LegacyControlPointProperties();
|
||||||
|
|
||||||
foreach (var group in legacyControlPoints.Groups)
|
foreach (var group in legacyControlPoints.Groups)
|
||||||
{
|
{
|
||||||
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||||
|
var controlPointProperties = getLegacyControlPointProperties(group, groupTimingPoint != null);
|
||||||
|
|
||||||
// If the group contains a timing control point, it needs to be output separately.
|
// If the group contains a timing control point, it needs to be output separately.
|
||||||
if (groupTimingPoint != null)
|
if (groupTimingPoint != null)
|
||||||
{
|
{
|
||||||
writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},"));
|
writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},"));
|
||||||
writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},"));
|
writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},"));
|
||||||
outputControlPointAt(groupTimingPoint.Time, true);
|
outputControlPointAt(controlPointProperties, true);
|
||||||
|
lastControlPointProperties = controlPointProperties;
|
||||||
|
lastControlPointProperties.SliderVelocity = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (controlPointProperties.IsRedundant(lastControlPointProperties))
|
||||||
|
continue;
|
||||||
|
|
||||||
// Output any remaining effects as secondary non-timing control point.
|
// Output any remaining effects as secondary non-timing control point.
|
||||||
var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time);
|
|
||||||
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
||||||
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},"));
|
writer.Write(FormattableString.Invariant($"{-100 / controlPointProperties.SliderVelocity},"));
|
||||||
outputControlPointAt(group.Time, false);
|
outputControlPointAt(controlPointProperties, false);
|
||||||
|
lastControlPointProperties = controlPointProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
void outputControlPointAt(double time, bool isTimingPoint)
|
LegacyControlPointProperties getLegacyControlPointProperties(ControlPointGroup group, bool updateSampleBank)
|
||||||
{
|
{
|
||||||
var samplePoint = legacyControlPoints.SamplePointAt(time);
|
var timingPoint = legacyControlPoints.TimingPointAt(group.Time);
|
||||||
var effectPoint = legacyControlPoints.EffectPointAt(time);
|
var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time);
|
||||||
var timingPoint = legacyControlPoints.TimingPointAt(time);
|
var samplePoint = legacyControlPoints.SamplePointAt(group.Time);
|
||||||
|
var effectPoint = legacyControlPoints.EffectPointAt(group.Time);
|
||||||
|
|
||||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
||||||
|
int customSampleBank = toLegacyCustomSampleBank(tempHitSample);
|
||||||
|
|
||||||
// Convert effect flags to the legacy format
|
// Convert effect flags to the legacy format
|
||||||
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
||||||
@ -234,12 +242,26 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (timingPoint.OmitFirstBarLine)
|
if (timingPoint.OmitFirstBarLine)
|
||||||
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
||||||
|
|
||||||
writer.Write(FormattableString.Invariant($"{timingPoint.TimeSignature.Numerator},"));
|
return new LegacyControlPointProperties
|
||||||
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
{
|
||||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
SliderVelocity = difficultyPoint.SliderVelocity,
|
||||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
TimingSignature = timingPoint.TimeSignature.Numerator,
|
||||||
writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},"));
|
SampleBank = updateSampleBank ? (int)toLegacySampleBank(tempHitSample.Bank) : lastControlPointProperties.SampleBank,
|
||||||
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
// Inherit the previous custom sample bank if the current custom sample bank is not set
|
||||||
|
CustomSampleBank = customSampleBank >= 0 ? customSampleBank : lastControlPointProperties.CustomSampleBank,
|
||||||
|
SampleVolume = tempHitSample.Volume,
|
||||||
|
EffectFlags = effectFlags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void outputControlPointAt(LegacyControlPointProperties controlPoint, bool isTimingPoint)
|
||||||
|
{
|
||||||
|
writer.Write(FormattableString.Invariant($"{controlPoint.TimingSignature.ToString(CultureInfo.InvariantCulture)},"));
|
||||||
|
writer.Write(FormattableString.Invariant($"{controlPoint.SampleBank.ToString(CultureInfo.InvariantCulture)},"));
|
||||||
|
writer.Write(FormattableString.Invariant($"{controlPoint.CustomSampleBank.ToString(CultureInfo.InvariantCulture)},"));
|
||||||
|
writer.Write(FormattableString.Invariant($"{controlPoint.SampleVolume.ToString(CultureInfo.InvariantCulture)},"));
|
||||||
|
writer.Write(FormattableString.Invariant($"{(isTimingPoint ? "1" : "0")},"));
|
||||||
|
writer.Write(FormattableString.Invariant($"{((int)controlPoint.EffectFlags).ToString(CultureInfo.InvariantCulture)}"));
|
||||||
writer.WriteLine();
|
writer.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +271,10 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
foreach (var hitObject in hitObjects)
|
foreach (var hitObject in hitObjects)
|
||||||
yield return hitObject.DifficultyControlPoint;
|
{
|
||||||
|
if (hitObject is IHasSliderVelocity hasSliderVelocity)
|
||||||
|
yield return new DifficultyControlPoint { Time = hitObject.StartTime, SliderVelocity = hasSliderVelocity.SliderVelocity };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void extractDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
void extractDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
|
||||||
@ -268,7 +293,15 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
foreach (var hitObject in hitObjects)
|
foreach (var hitObject in hitObjects)
|
||||||
{
|
{
|
||||||
yield return hitObject.SampleControlPoint;
|
if (hitObject.Samples.Count > 0)
|
||||||
|
{
|
||||||
|
int volume = hitObject.Samples.Max(o => o.Volume);
|
||||||
|
int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo)
|
||||||
|
? hitObject.Samples.OfType<ConvertHitObjectParser.LegacyHitSampleInfo>().Max(o => o.CustomSampleBank)
|
||||||
|
: -1;
|
||||||
|
|
||||||
|
yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex };
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
|
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
|
||||||
yield return nested;
|
yield return nested;
|
||||||
@ -466,16 +499,16 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
if (curveData != null)
|
if (curveData != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
for (int i = 0; i < curveData.SpanCount() + 1; i++)
|
||||||
{
|
{
|
||||||
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}"));
|
writer.Write(FormattableString.Invariant($"{(i < curveData.NodeSamples.Count ? (int)toLegacyHitSoundType(curveData.NodeSamples[i]) : 0)}"));
|
||||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
writer.Write(i != curveData.SpanCount() ? "|" : ",");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
for (int i = 0; i < curveData.SpanCount() + 1; i++)
|
||||||
{
|
{
|
||||||
writer.Write(getSampleBank(curveData.NodeSamples[i], true));
|
writer.Write(i < curveData.NodeSamples.Count ? getSampleBank(curveData.NodeSamples[i], true) : "0:0");
|
||||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
writer.Write(i != curveData.SpanCount() ? "|" : ",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -506,10 +539,18 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
if (!banksOnly)
|
if (!banksOnly)
|
||||||
{
|
{
|
||||||
string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
|
int customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
|
||||||
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
||||||
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
||||||
|
|
||||||
|
// We want to ignore custom sample banks and volume when not encoding to the mania game mode,
|
||||||
|
// because they cause unexpected results in the editor and are already satisfied by the control points.
|
||||||
|
if (onlineRulesetID != 3)
|
||||||
|
{
|
||||||
|
customSampleBank = 0;
|
||||||
|
volume = 0;
|
||||||
|
}
|
||||||
|
|
||||||
sb.Append(':');
|
sb.Append(':');
|
||||||
sb.Append(FormattableString.Invariant($"{customSampleBank}:"));
|
sb.Append(FormattableString.Invariant($"{customSampleBank}:"));
|
||||||
sb.Append(FormattableString.Invariant($"{volume}:"));
|
sb.Append(FormattableString.Invariant($"{volume}:"));
|
||||||
@ -562,12 +603,30 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
|
private int toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
|
||||||
{
|
{
|
||||||
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
|
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
|
||||||
return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);
|
return legacy.CustomSampleBank;
|
||||||
|
|
||||||
return "0";
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct LegacyControlPointProperties
|
||||||
|
{
|
||||||
|
internal double SliderVelocity { get; set; }
|
||||||
|
internal int TimingSignature { get; init; }
|
||||||
|
internal int SampleBank { get; init; }
|
||||||
|
internal int CustomSampleBank { get; init; }
|
||||||
|
internal int SampleVolume { get; init; }
|
||||||
|
internal LegacyEffectFlags EffectFlags { get; init; }
|
||||||
|
|
||||||
|
internal bool IsRedundant(LegacyControlPointProperties other) =>
|
||||||
|
SliderVelocity == other.SliderVelocity &&
|
||||||
|
TimingSignature == other.TimingSignature &&
|
||||||
|
SampleBank == other.SampleBank &&
|
||||||
|
CustomSampleBank == other.CustomSampleBank &&
|
||||||
|
SampleVolume == other.SampleVolume &&
|
||||||
|
EffectFlags == other.EffectFlags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,6 +157,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.Scaling, ScalingMode.Off);
|
SetDefault(OsuSetting.Scaling, ScalingMode.Off);
|
||||||
SetDefault(OsuSetting.SafeAreaConsiderations, true);
|
SetDefault(OsuSetting.SafeAreaConsiderations, true);
|
||||||
|
SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f);
|
||||||
|
|
||||||
SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
|
SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
|
||||||
SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
|
SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
|
||||||
@ -364,6 +365,7 @@ namespace osu.Game.Configuration
|
|||||||
ScalingPositionY,
|
ScalingPositionY,
|
||||||
ScalingSizeX,
|
ScalingSizeX,
|
||||||
ScalingSizeY,
|
ScalingSizeY,
|
||||||
|
ScalingBackgroundDim,
|
||||||
UIScale,
|
UIScale,
|
||||||
IntroSequence,
|
IntroSequence,
|
||||||
NotifyOnUsernameMentioned,
|
NotifyOnUsernameMentioned,
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
{
|
{
|
||||||
@ -46,6 +47,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private BackgroundScreenStack backgroundStack;
|
private BackgroundScreenStack backgroundStack;
|
||||||
|
|
||||||
|
private Bindable<float> scalingMenuBackgroundDim;
|
||||||
|
|
||||||
private RectangleF? customRect;
|
private RectangleF? customRect;
|
||||||
private bool customRectIsRelativePosition;
|
private bool customRectIsRelativePosition;
|
||||||
|
|
||||||
@ -138,6 +141,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy();
|
safeAreaPadding = safeArea.SafeAreaPadding.GetBoundCopy();
|
||||||
safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize));
|
safeAreaPadding.BindValueChanged(_ => Scheduler.AddOnce(updateSize));
|
||||||
|
|
||||||
|
scalingMenuBackgroundDim = config.GetBindable<float>(OsuSetting.ScalingBackgroundDim);
|
||||||
|
scalingMenuBackgroundDim.ValueChanged += _ => Scheduler.AddOnce(updateSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -148,7 +154,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
sizableContainer.FinishTransforms();
|
sizableContainer.FinishTransforms();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1);
|
private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays)
|
||||||
|
&& (sizeX.Value != 1 || sizeY.Value != 1)
|
||||||
|
&& scalingMenuBackgroundDim.Value < 1;
|
||||||
|
|
||||||
private void updateSize()
|
private void updateSize()
|
||||||
{
|
{
|
||||||
@ -161,8 +169,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
AddInternal(backgroundStack = new BackgroundScreenStack
|
AddInternal(backgroundStack = new BackgroundScreenStack
|
||||||
{
|
{
|
||||||
Colour = OsuColour.Gray(0.1f),
|
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
|
Colour = Color4.Black,
|
||||||
Depth = float.MaxValue
|
Depth = float.MaxValue
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -170,6 +178,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
}
|
}
|
||||||
|
|
||||||
backgroundStack.FadeIn(TRANSITION_DURATION);
|
backgroundStack.FadeIn(TRANSITION_DURATION);
|
||||||
|
backgroundStack.FadeColour(OsuColour.Gray(1.0f - scalingMenuBackgroundDim.Value), TRANSITION_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
backgroundStack?.FadeOut(TRANSITION_DURATION);
|
backgroundStack?.FadeOut(TRANSITION_DURATION);
|
||||||
|
@ -97,7 +97,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
// Find the number of significant digits (we could have less than 5 after normalize())
|
// Find the number of significant digits (we could have less than 5 after normalize())
|
||||||
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
||||||
|
|
||||||
return floatValue.ToString($"N{significantDigits}");
|
string negativeSign = Math.Round(floatValue, significantDigits) < 0 ? "-" : string.Empty;
|
||||||
|
|
||||||
|
return $"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}")}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -49,11 +49,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private const float transition_length = 500;
|
private const float transition_length = 500;
|
||||||
private Sample sampleChecked;
|
private Sample sampleChecked;
|
||||||
private Sample sampleUnchecked;
|
private Sample sampleUnchecked;
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
|
||||||
public OsuTabControlCheckbox()
|
public OsuTabControlCheckbox()
|
||||||
{
|
{
|
||||||
SpriteIcon icon;
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -85,14 +84,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Current.ValueChanged += selected =>
|
|
||||||
{
|
|
||||||
icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle;
|
|
||||||
text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium);
|
|
||||||
|
|
||||||
updateFade();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -105,6 +96,19 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
|
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(selected =>
|
||||||
|
{
|
||||||
|
icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle;
|
||||||
|
text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium);
|
||||||
|
|
||||||
|
updateFade();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateFade();
|
updateFade();
|
||||||
|
@ -12,6 +12,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class TimeSlider : RoundedSliderBar<double>
|
public partial class TimeSlider : RoundedSliderBar<double>
|
||||||
{
|
{
|
||||||
public override LocalisableString TooltipText => $"{Current.Value:N0} ms";
|
public override LocalisableString TooltipText => $"{base.TooltipText} ms";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString CursorTrail => new TranslatableString(getKey(@"cursor_trail"), @"Cursor trail");
|
public static LocalisableString CursorTrail => new TranslatableString(getKey(@"cursor_trail"), @"Cursor trail");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Cursor ripples"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CursorRipples => new TranslatableString(getKey(@"cursor_ripples"), @"Cursor ripples");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Playfield border style"
|
/// "Playfield border style"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -242,17 +242,21 @@ namespace osu.Game.Overlays.Mods
|
|||||||
if (AllowCustomisation)
|
if (AllowCustomisation)
|
||||||
((IBindable<IReadOnlyList<Mod>>)modSettingsArea.SelectedMods).BindTo(SelectedMods);
|
((IBindable<IReadOnlyList<Mod>>)modSettingsArea.SelectedMods).BindTo(SelectedMods);
|
||||||
|
|
||||||
SelectedMods.BindValueChanged(val =>
|
SelectedMods.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
modSettingChangeTracker?.Dispose();
|
|
||||||
|
|
||||||
updateMultiplier();
|
updateMultiplier();
|
||||||
updateFromExternalSelection();
|
updateFromExternalSelection();
|
||||||
updateCustomisation();
|
updateCustomisation();
|
||||||
|
|
||||||
|
modSettingChangeTracker?.Dispose();
|
||||||
|
|
||||||
if (AllowCustomisation)
|
if (AllowCustomisation)
|
||||||
{
|
{
|
||||||
modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue);
|
// Importantly, use SelectedMods.Value here (and not the ValueChanged NewValue) as the latter can
|
||||||
|
// potentially be stale, due to complexities in the way change trackers work.
|
||||||
|
//
|
||||||
|
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
|
||||||
|
modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value);
|
||||||
modSettingChangeTracker.SettingChanged += _ => updateMultiplier();
|
modSettingChangeTracker.SettingChanged += _ => updateMultiplier();
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
|
var artist = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
|
||||||
|
|
||||||
titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular));
|
titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular));
|
||||||
titlePart.DrawablePartsRecreated += _ => updateSelectionState(true);
|
titlePart.DrawablePartsRecreated += _ => updateSelectionState(SelectedSet.Value, applyImmediately: true);
|
||||||
|
|
||||||
text.AddText(@" "); // to separate the title from the artist.
|
text.AddText(@" "); // to separate the title from the artist.
|
||||||
text.AddText(artist, sprite =>
|
text.AddText(artist, sprite =>
|
||||||
@ -66,27 +66,25 @@ namespace osu.Game.Overlays.Music
|
|||||||
sprite.Padding = new MarginPadding { Top = 1 };
|
sprite.Padding = new MarginPadding { Top = 1 };
|
||||||
});
|
});
|
||||||
|
|
||||||
SelectedSet.BindValueChanged(set =>
|
SelectedSet.BindValueChanged(set => updateSelectionState(set.NewValue));
|
||||||
{
|
updateSelectionState(SelectedSet.Value, applyImmediately: true);
|
||||||
bool newSelected = set.NewValue?.Equals(Model) == true;
|
|
||||||
|
|
||||||
if (newSelected == selected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
selected = newSelected;
|
|
||||||
updateSelectionState(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
updateSelectionState(true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool selected;
|
private bool selected;
|
||||||
|
|
||||||
private void updateSelectionState(bool instant)
|
private void updateSelectionState(Live<BeatmapSetInfo> selectedSet, bool applyImmediately = false)
|
||||||
{
|
{
|
||||||
|
bool wasSelected = selected;
|
||||||
|
selected = selectedSet?.Equals(Model) == true;
|
||||||
|
|
||||||
|
// Immediate updates should forcibly set correct state regardless of previous state.
|
||||||
|
// This ensures that the initial state is correctly applied.
|
||||||
|
if (wasSelected == selected && !applyImmediately)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (Drawable s in titlePart.Drawables)
|
foreach (Drawable s in titlePart.Drawables)
|
||||||
s.FadeColour(selected ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION);
|
s.FadeColour(selected ? colours.Yellow : Color4.White, applyImmediately ? 0 : FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => new DelayedLoadWrapper(text = new OsuTextFlowContainer
|
protected override Drawable CreateContent() => new DelayedLoadWrapper(text = new OsuTextFlowContainer
|
||||||
|
@ -185,6 +185,12 @@ namespace osu.Game.Overlays
|
|||||||
content.ScaleTo(1, 1000, Easing.OutElastic);
|
content.ScaleTo(1, 1000, Easing.OutElastic);
|
||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
base.OnHover(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader;
|
protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader;
|
||||||
|
|
||||||
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
||||||
|
private SettingsSlider<float> dimSlider = null!;
|
||||||
|
|
||||||
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
||||||
|
|
||||||
@ -57,6 +58,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
private Bindable<float> scalingSizeX = null!;
|
private Bindable<float> scalingSizeX = null!;
|
||||||
private Bindable<float> scalingSizeY = null!;
|
private Bindable<float> scalingSizeY = null!;
|
||||||
|
|
||||||
|
private Bindable<float> scalingBackgroundDim = null!;
|
||||||
|
|
||||||
private const int transition_duration = 400;
|
private const int transition_duration = 400;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -70,6 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
scalingSizeY = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY);
|
scalingSizeY = osuConfig.GetBindable<float>(OsuSetting.ScalingSizeY);
|
||||||
scalingPositionX = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX);
|
scalingPositionX = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX);
|
||||||
scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
|
scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
|
||||||
|
scalingBackgroundDim = osuConfig.GetBindable<float>(OsuSetting.ScalingBackgroundDim);
|
||||||
|
|
||||||
if (window != null)
|
if (window != null)
|
||||||
{
|
{
|
||||||
@ -161,6 +165,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
KeyboardStep = 0.01f,
|
KeyboardStep = 0.01f,
|
||||||
DisplayAsPercentage = true
|
DisplayAsPercentage = true
|
||||||
},
|
},
|
||||||
|
dimSlider = new SettingsSlider<float>
|
||||||
|
{
|
||||||
|
LabelText = GameplaySettingsStrings.BackgroundDim,
|
||||||
|
Current = scalingBackgroundDim,
|
||||||
|
KeyboardStep = 0.01f,
|
||||||
|
DisplayAsPercentage = true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -216,8 +227,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
scalingSettings.AutoSizeAxes = scalingMode.Value != ScalingMode.Off ? Axes.Y : Axes.None;
|
scalingSettings.AutoSizeAxes = scalingMode.Value != ScalingMode.Off ? Axes.Y : Axes.None;
|
||||||
scalingSettings.ForEach(s =>
|
scalingSettings.ForEach(s =>
|
||||||
{
|
{
|
||||||
s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything;
|
if (s == dimSlider)
|
||||||
s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off;
|
{
|
||||||
|
s.CanBeShown.Value = scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything;
|
||||||
|
s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Edit.Checks
|
|||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
// Samples that allow themselves to be overridden by control points have a volume of 0.
|
// Samples that allow themselves to be overridden by control points have a volume of 0.
|
||||||
int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume);
|
int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume);
|
||||||
double samplePlayTime = sampledHitObject.GetEndTime();
|
double samplePlayTime = sampledHitObject.GetEndTime();
|
||||||
|
|
||||||
EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime);
|
EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime);
|
||||||
|
@ -23,6 +23,7 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Overlays.OSD;
|
using osu.Game.Overlays.OSD;
|
||||||
using osu.Game.Overlays.Settings.Sections;
|
using osu.Game.Overlays.Settings.Sections;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime()
|
private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime()
|
||||||
{
|
{
|
||||||
HitObject lastBefore = Playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime <= EditorClock.CurrentTime)?.HitObject;
|
HitObject lastBefore = Playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < EditorClock.CurrentTime)?.HitObject;
|
||||||
|
|
||||||
if (lastBefore == null)
|
if (lastBefore == null)
|
||||||
return null;
|
return null;
|
||||||
@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true)
|
public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true)
|
||||||
{
|
{
|
||||||
return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1
|
return (float)(100 * (useReferenceSliderVelocity && referenceObject is IHasSliderVelocity hasSliderVelocity ? hasSliderVelocity.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1
|
||||||
/ BeatSnapProvider.BeatDivisor);
|
/ BeatSnapProvider.BeatDivisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +116,11 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
PlayfieldContentContainer = new Container
|
PlayfieldContentContainer = new Container
|
||||||
{
|
{
|
||||||
Name = "Content",
|
Name = "Content",
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Left = TOOLBOX_CONTRACTED_SIZE_LEFT,
|
||||||
|
Right = TOOLBOX_CONTRACTED_SIZE_RIGHT,
|
||||||
|
},
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -138,7 +143,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Colour = colourProvider.Background5,
|
Colour = colourProvider.Background5,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
LeftToolbox = new ExpandingToolboxContainer(60, 200)
|
LeftToolbox = new ExpandingToolboxContainer(TOOLBOX_CONTRACTED_SIZE_LEFT, 200)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -173,7 +178,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Colour = colourProvider.Background5,
|
Colour = colourProvider.Background5,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
RightToolbox = new ExpandingToolboxContainer(130, 250)
|
RightToolbox = new ExpandingToolboxContainer(TOOLBOX_CONTRACTED_SIZE_RIGHT, 250)
|
||||||
{
|
{
|
||||||
Child = new EditorToolboxGroup("inspector")
|
Child = new EditorToolboxGroup("inspector")
|
||||||
{
|
{
|
||||||
@ -450,6 +455,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
[Cached]
|
[Cached]
|
||||||
public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
|
public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
|
||||||
{
|
{
|
||||||
|
public const float TOOLBOX_CONTRACTED_SIZE_LEFT = 60;
|
||||||
|
public const float TOOLBOX_CONTRACTED_SIZE_RIGHT = 130;
|
||||||
|
|
||||||
public readonly Ruleset Ruleset;
|
public readonly Ruleset Ruleset;
|
||||||
|
|
||||||
protected HitObjectComposer(Ruleset ruleset)
|
protected HitObjectComposer(Ruleset ruleset)
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
// adding the default hit sample should be the case regardless of the ruleset.
|
// adding the default hit sample should be the case regardless of the ruleset.
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
|
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK, volume: 100));
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -74,9 +74,10 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
||||||
protected void BeginPlacement(bool commitStart = false)
|
protected void BeginPlacement(bool commitStart = false)
|
||||||
{
|
{
|
||||||
var nearestSampleControlPoint = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.SampleControlPoint?.DeepClone() as SampleControlPoint;
|
// Take the hitnormal sample of the last hit object
|
||||||
|
var lastHitNormal = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||||
HitObject.SampleControlPoint = nearestSampleControlPoint ?? new SampleControlPoint();
|
if (lastHitNormal != null)
|
||||||
|
HitObject.Samples[0] = lastHitNormal;
|
||||||
|
|
||||||
placementHandler.BeginPlacement(HitObject);
|
placementHandler.BeginPlacement(HitObject);
|
||||||
if (commitStart)
|
if (commitStart)
|
||||||
|
@ -82,8 +82,11 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
flashlight.RelativeSizeAxes = Axes.Both;
|
flashlight.RelativeSizeAxes = Axes.Both;
|
||||||
flashlight.Colour = Color4.Black;
|
flashlight.Colour = Color4.Black;
|
||||||
|
// Flashlight mods should always draw above any other mod adding overlays.
|
||||||
|
flashlight.Depth = float.MinValue;
|
||||||
|
|
||||||
flashlight.Combo.BindTo(Combo);
|
flashlight.Combo.BindTo(Combo);
|
||||||
|
|
||||||
drawableRuleset.Overlays.Add(flashlight);
|
drawableRuleset.Overlays.Add(flashlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,13 +357,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
if (samples.Length <= 0)
|
if (samples.Length <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (HitObject.SampleControlPoint == null)
|
Samples.Samples = samples.Cast<ISampleInfo>().ToArray();
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
|
||||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Samples.Samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
||||||
|
@ -16,7 +16,6 @@ using osu.Framework.Lists;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -77,8 +76,11 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual IList<HitSampleInfo> AuxiliarySamples => ImmutableList<HitSampleInfo>.Empty;
|
public virtual IList<HitSampleInfo> AuxiliarySamples => ImmutableList<HitSampleInfo>.Empty;
|
||||||
|
|
||||||
public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT;
|
/// <summary>
|
||||||
public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT;
|
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
|
||||||
|
/// DO NOT USE THIS UNLESS 100% SURE.
|
||||||
|
/// </summary>
|
||||||
|
public double? LegacyBpmMultiplier { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="HitObject"/> is in Kiai time.
|
/// Whether this <see cref="HitObject"/> is in Kiai time.
|
||||||
@ -105,25 +107,8 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default)
|
public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var legacyInfo = controlPointInfo as LegacyControlPointInfo;
|
|
||||||
|
|
||||||
if (legacyInfo != null)
|
|
||||||
DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone();
|
|
||||||
else if (ReferenceEquals(DifficultyControlPoint, DifficultyControlPoint.DEFAULT))
|
|
||||||
DifficultyControlPoint = new DifficultyControlPoint();
|
|
||||||
|
|
||||||
DifficultyControlPoint.Time = StartTime;
|
|
||||||
|
|
||||||
ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
// This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time.
|
|
||||||
if (legacyInfo != null)
|
|
||||||
SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone();
|
|
||||||
else if (ReferenceEquals(SampleControlPoint, SampleControlPoint.DEFAULT))
|
|
||||||
SampleControlPoint = new SampleControlPoint();
|
|
||||||
|
|
||||||
SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
|
|
||||||
|
|
||||||
nestedHitObjects.Clear();
|
nestedHitObjects.Clear();
|
||||||
|
|
||||||
CreateNestedHitObjects(cancellationToken);
|
CreateNestedHitObjects(cancellationToken);
|
||||||
@ -164,9 +149,6 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
foreach (var nested in nestedHitObjects)
|
foreach (var nested in nestedHitObjects)
|
||||||
nested.StartTime += offset;
|
nested.StartTime += offset;
|
||||||
|
|
||||||
DifficultyControlPoint.Time = time.NewValue;
|
|
||||||
SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +204,17 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
return slidingSamples;
|
return slidingSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a SampleInfo based on the sample settings of the hit normal sample in <see cref="Samples"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleName">The name of the sample.</param>
|
||||||
|
/// <returns>A populated <see cref="HitSampleInfo"/>.</returns>
|
||||||
|
protected HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL)
|
||||||
|
{
|
||||||
|
var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
|
||||||
|
return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HitObjectExtensions
|
public static class HitObjectExtensions
|
||||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (split.Length > 10)
|
if (split.Length > 10)
|
||||||
readCustomSampleBanks(split[10], bankInfo);
|
readCustomSampleBanks(split[10], bankInfo, true);
|
||||||
|
|
||||||
// One node for each repeat + the start and end nodes
|
// One node for each repeat + the start and end nodes
|
||||||
int nodes = repeatCount + 2;
|
int nodes = repeatCount + 2;
|
||||||
@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
|
private void readCustomSampleBanks(string str, SampleBankInfo bankInfo, bool banksOnly = false)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(str))
|
if (string.IsNullOrEmpty(str))
|
||||||
return;
|
return;
|
||||||
@ -202,6 +202,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
bankInfo.BankForNormal = stringBank;
|
bankInfo.BankForNormal = stringBank;
|
||||||
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||||
|
|
||||||
|
if (banksOnly) return;
|
||||||
|
|
||||||
if (split.Length > 2)
|
if (split.Length > 2)
|
||||||
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
||||||
|
|
||||||
|
@ -6,13 +6,14 @@
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Legacy
|
namespace osu.Game.Rulesets.Objects.Legacy
|
||||||
{
|
{
|
||||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset
|
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, IHasSliderVelocity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||||
@ -40,13 +41,21 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
public double Velocity = 1;
|
public double Velocity = 1;
|
||||||
|
|
||||||
|
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1);
|
||||||
|
|
||||||
|
public double SliderVelocity
|
||||||
|
{
|
||||||
|
get => SliderVelocityBindable.Value;
|
||||||
|
set => SliderVelocityBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
|
||||||
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * SliderVelocity;
|
||||||
|
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
}
|
}
|
||||||
|
17
osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs
Normal file
17
osu.Game/Rulesets/Objects/Types/IHasGenerateTicks.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Objects.Types
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A type of <see cref="HitObject"/> which explicitly specifies whether it should generate ticks.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasGenerateTicks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not slider ticks should be generated by this object.
|
||||||
|
/// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991).
|
||||||
|
/// </summary>
|
||||||
|
public bool GenerateTicks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
19
osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs
Normal file
19
osu.Game/Rulesets/Objects/Types/IHasSliderVelocity.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A HitObject that has a slider velocity multiplier.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasSliderVelocity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The slider velocity multiplier.
|
||||||
|
/// </summary>
|
||||||
|
double SliderVelocity { get; set; }
|
||||||
|
|
||||||
|
BindableNumber<double> SliderVelocityBindable { get; }
|
||||||
|
}
|
@ -52,7 +52,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var samples = nextObject.Samples
|
var samples = nextObject.Samples
|
||||||
.Select(s => nextObject.SampleControlPoint.ApplyTo(s))
|
|
||||||
.Cast<ISampleInfo>()
|
.Cast<ISampleInfo>()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
@ -293,10 +293,10 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
// prepare sample pools ahead of time so we're not initialising at runtime.
|
// prepare sample pools ahead of time so we're not initialising at runtime.
|
||||||
foreach (var sample in hitObject.Samples)
|
foreach (var sample in hitObject.Samples)
|
||||||
prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample));
|
prepareSamplePool(sample);
|
||||||
|
|
||||||
foreach (var sample in hitObject.AuxiliarySamples)
|
foreach (var sample in hitObject.AuxiliarySamples)
|
||||||
prepareSamplePool(hitObject.SampleControlPoint.ApplyTo(sample));
|
prepareSamplePool(sample);
|
||||||
|
|
||||||
foreach (var nestedObject in hitObject.NestedHitObjects)
|
foreach (var nestedObject in hitObject.NestedHitObjects)
|
||||||
preloadSamples(nestedObject);
|
preloadSamples(nestedObject);
|
||||||
|
@ -13,12 +13,14 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
@ -29,13 +31,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
private readonly BindableNumber<double> speedMultiplier;
|
private readonly BindableNumber<double> speedMultiplier;
|
||||||
|
|
||||||
public DifficultyPointPiece(HitObject hitObject)
|
public DifficultyPointPiece(HitObject hitObject)
|
||||||
: base(hitObject.DifficultyControlPoint)
|
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
|
speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityBindable.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -78,7 +81,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Spacing = new Vector2(0, 15),
|
Spacing = new Vector2(0, 15),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput<double>("Velocity", new DifficultyControlPoint().SliderVelocityBindable)
|
sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput<double>("Velocity", new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 10
|
||||||
|
})
|
||||||
{
|
{
|
||||||
KeyboardStep = 0.1f
|
KeyboardStep = 0.1f
|
||||||
},
|
},
|
||||||
@ -94,11 +102,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).Where(o => o is IHasSliderVelocity).ToArray();
|
||||||
var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray();
|
|
||||||
|
|
||||||
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
||||||
var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null;
|
var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1 ? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable : null;
|
||||||
|
|
||||||
if (selectedPointBindable != null)
|
if (selectedPointBindable != null)
|
||||||
{
|
{
|
||||||
@ -117,7 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
foreach (var h in relevantObjects)
|
foreach (var h in relevantObjects)
|
||||||
{
|
{
|
||||||
h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value;
|
((IHasSliderVelocity)h).SliderVelocity = val.NewValue.Value;
|
||||||
beatmap.Update(h);
|
beatmap.Update(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -16,21 +15,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
public partial class HitObjectPointPiece : CircularContainer
|
public partial class HitObjectPointPiece : CircularContainer
|
||||||
{
|
{
|
||||||
private readonly ControlPoint point;
|
|
||||||
|
|
||||||
protected OsuSpriteText Label { get; private set; }
|
protected OsuSpriteText Label { get; private set; }
|
||||||
|
|
||||||
protected HitObjectPointPiece(ControlPoint point)
|
|
||||||
{
|
|
||||||
this.point = point;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Color4 colour = point.GetRepresentingColour(colours);
|
Color4 colour = GetRepresentingColour(colours);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -61,5 +53,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual Color4 GetRepresentingColour(OsuColour colours)
|
||||||
|
{
|
||||||
|
return colours.Yellow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,13 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
@ -24,22 +26,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
public readonly HitObject HitObject;
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
private readonly Bindable<string> bank;
|
private readonly BindableList<HitSampleInfo> samplesBindable;
|
||||||
private readonly BindableNumber<int> volume;
|
|
||||||
|
|
||||||
public SamplePointPiece(HitObject hitObject)
|
public SamplePointPiece(HitObject hitObject)
|
||||||
: base(hitObject.SampleControlPoint)
|
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
|
samplesBindable = hitObject.SamplesBindable.GetBoundCopy();
|
||||||
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Color4 GetRepresentingColour(OsuColour colours) => colours.Pink;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
volume.BindValueChanged(_ => updateText());
|
samplesBindable.BindCollectionChanged((_, _) => updateText(), true);
|
||||||
bank.BindValueChanged(_ => updateText(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
@ -50,7 +50,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private void updateText()
|
private void updateText()
|
||||||
{
|
{
|
||||||
Label.Text = $"{bank.Value} {volume.Value}";
|
Label.Text = $"{GetBankValue(samplesBindable)} {GetVolumeValue(samplesBindable)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? GetBankValue(IEnumerable<HitSampleInfo> samples)
|
||||||
|
{
|
||||||
|
return samples.FirstOrDefault()?.Bank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetVolumeValue(ICollection<HitSampleInfo> samples)
|
||||||
|
{
|
||||||
|
return samples.Count == 0 ? 0 : samples.Max(o => o.Volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Popover GetPopover() => new SampleEditPopover(HitObject);
|
public Popover GetPopover() => new SampleEditPopover(HitObject);
|
||||||
@ -89,7 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
Label = "Bank Name",
|
Label = "Bank Name",
|
||||||
},
|
},
|
||||||
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new SampleControlPoint().SampleVolumeBindable)
|
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new BindableInt(100)
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 100,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -100,14 +114,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||||
var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray();
|
var relevantSamples = relevantObjects.Select(h => h.Samples).ToArray();
|
||||||
|
|
||||||
// even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value.
|
// even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value.
|
||||||
string? commonBank = getCommonBank(relevantControlPoints);
|
string? commonBank = getCommonBank(relevantSamples);
|
||||||
if (!string.IsNullOrEmpty(commonBank))
|
if (!string.IsNullOrEmpty(commonBank))
|
||||||
bank.Current.Value = commonBank;
|
bank.Current.Value = commonBank;
|
||||||
|
|
||||||
int? commonVolume = getCommonVolume(relevantControlPoints);
|
int? commonVolume = getCommonVolume(relevantSamples);
|
||||||
if (commonVolume != null)
|
if (commonVolume != null)
|
||||||
volume.Current.Value = commonVolume.Value;
|
volume.Current.Value = commonVolume.Value;
|
||||||
|
|
||||||
@ -117,9 +131,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
updateBankFor(relevantObjects, val.NewValue);
|
updateBankFor(relevantObjects, val.NewValue);
|
||||||
updateBankPlaceholderText(relevantObjects);
|
updateBankPlaceholderText(relevantObjects);
|
||||||
});
|
});
|
||||||
// on commit, ensure that the value is correct by sourcing it from the objects' control points again.
|
// on commit, ensure that the value is correct by sourcing it from the objects' samples again.
|
||||||
// this ensures that committing empty text causes a revert to the previous value.
|
// this ensures that committing empty text causes a revert to the previous value.
|
||||||
bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantControlPoints);
|
bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantSamples);
|
||||||
|
|
||||||
volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue));
|
volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue));
|
||||||
}
|
}
|
||||||
@ -130,8 +144,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume));
|
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null;
|
private static string? getCommonBank(IList<HitSampleInfo>[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null;
|
||||||
private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? relevantControlPoints.First().SampleVolume : null;
|
private static int? getCommonVolume(IList<HitSampleInfo>[] relevantSamples) => relevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(relevantSamples.First()) : null;
|
||||||
|
|
||||||
private void updateBankFor(IEnumerable<HitObject> objects, string? newBank)
|
private void updateBankFor(IEnumerable<HitObject> objects, string? newBank)
|
||||||
{
|
{
|
||||||
@ -142,7 +156,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
foreach (var h in objects)
|
foreach (var h in objects)
|
||||||
{
|
{
|
||||||
h.SampleControlPoint.SampleBank = newBank;
|
for (int i = 0; i < h.Samples.Count; i++)
|
||||||
|
{
|
||||||
|
h.Samples[i] = h.Samples[i].With(newBank: newBank);
|
||||||
|
}
|
||||||
|
|
||||||
beatmap.Update(h);
|
beatmap.Update(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private void updateBankPlaceholderText(IEnumerable<HitObject> objects)
|
private void updateBankPlaceholderText(IEnumerable<HitObject> objects)
|
||||||
{
|
{
|
||||||
string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray());
|
string? commonBank = getCommonBank(objects.Select(h => h.Samples).ToArray());
|
||||||
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty;
|
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +182,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
foreach (var h in objects)
|
foreach (var h in objects)
|
||||||
{
|
{
|
||||||
h.SampleControlPoint.SampleVolume = newVolume.Value;
|
for (int i = 0; i < h.Samples.Count; i++)
|
||||||
|
{
|
||||||
|
h.Samples[i] = h.Samples[i].With(newVolume: newVolume.Value);
|
||||||
|
}
|
||||||
|
|
||||||
beatmap.Update(h);
|
beatmap.Update(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -102,6 +101,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new SamplePointPiece(Item)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.TopCentre
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (item is IHasDuration)
|
if (item is IHasDuration)
|
||||||
@ -111,6 +115,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
OnDragHandled = e => OnDragHandled?.Invoke(e)
|
OnDragHandled = e => OnDragHandled?.Invoke(e)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is IHasSliderVelocity)
|
||||||
|
{
|
||||||
|
AddInternal(new DifficultyPointPiece(Item)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.BottomCentre
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -187,12 +200,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(averageColour);
|
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(averageColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SamplePointPiece? sampleOverrideDisplay;
|
|
||||||
private DifficultyPointPiece? difficultyOverrideDisplay;
|
|
||||||
|
|
||||||
private DifficultyControlPoint difficultyControlPoint = null!;
|
|
||||||
private SampleControlPoint sampleControlPoint = null!;
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -208,36 +215,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
if (Item is IHasRepeats repeats)
|
if (Item is IHasRepeats repeats)
|
||||||
updateRepeats(repeats);
|
updateRepeats(repeats);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ReferenceEquals(difficultyControlPoint, Item.DifficultyControlPoint))
|
|
||||||
{
|
|
||||||
difficultyControlPoint = Item.DifficultyControlPoint;
|
|
||||||
difficultyOverrideDisplay?.Expire();
|
|
||||||
|
|
||||||
if (Item.DifficultyControlPoint != null && Item is IHasDistance)
|
|
||||||
{
|
|
||||||
AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopLeft,
|
|
||||||
Origin = Anchor.BottomCentre
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ReferenceEquals(sampleControlPoint, Item.SampleControlPoint))
|
|
||||||
{
|
|
||||||
sampleControlPoint = Item.SampleControlPoint;
|
|
||||||
sampleOverrideDisplay?.Expire();
|
|
||||||
|
|
||||||
if (Item.SampleControlPoint != null)
|
|
||||||
{
|
|
||||||
AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.TopCentre
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRepeats(IHasRepeats repeats)
|
private void updateRepeats(IHasRepeats repeats)
|
||||||
@ -395,17 +372,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
case IHasRepeats repeatHitObject:
|
case IHasRepeats repeatHitObject:
|
||||||
double proposedDuration = time - hitObject.StartTime;
|
double proposedDuration = time - hitObject.StartTime;
|
||||||
|
|
||||||
if (e.CurrentState.Keyboard.ShiftPressed)
|
if (e.CurrentState.Keyboard.ShiftPressed && hitObject is IHasSliderVelocity hasSliderVelocity)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(hitObject.DifficultyControlPoint, DifficultyControlPoint.DEFAULT))
|
double newVelocity = hasSliderVelocity.SliderVelocity * (repeatHitObject.Duration / proposedDuration);
|
||||||
hitObject.DifficultyControlPoint = new DifficultyControlPoint();
|
|
||||||
|
|
||||||
double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration);
|
if (Precision.AlmostEquals(newVelocity, hasSliderVelocity.SliderVelocity))
|
||||||
|
|
||||||
if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
hitObject.DifficultyControlPoint.SliderVelocity = newVelocity;
|
hasSliderVelocity.SliderVelocity = newVelocity;
|
||||||
beatmap.Update(hitObject);
|
beatmap.Update(hitObject);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -414,7 +388,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
double lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
double lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
||||||
int proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1);
|
int proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1);
|
||||||
|
|
||||||
if (proposedCount == repeatHitObject.RepeatCount)
|
if (proposedCount == repeatHitObject.RepeatCount || lengthOfOneRepeat == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
repeatHitObject.RepeatCount = proposedCount;
|
repeatHitObject.RepeatCount = proposedCount;
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Colour = Color4.DarkBlue,
|
Colour = Color4.DarkBlue,
|
||||||
Size = new Vector2(0.96f)
|
Size = OsuLogo.SCALE_ADJUST,
|
||||||
},
|
},
|
||||||
new Circle
|
new Circle
|
||||||
{
|
{
|
||||||
|
@ -35,6 +35,12 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private const double transition_length = 300;
|
private const double transition_length = 300;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The osu! logo sprite has a shadow included in its texture.
|
||||||
|
/// This adjustment vector is used to match the precise edge of the border of the logo.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Vector2 SCALE_ADJUST = new Vector2(0.96f);
|
||||||
|
|
||||||
private readonly Sprite logo;
|
private readonly Sprite logo;
|
||||||
private readonly CircularContainer logoContainer;
|
private readonly CircularContainer logoContainer;
|
||||||
private readonly Container logoBounceContainer;
|
private readonly Container logoBounceContainer;
|
||||||
@ -150,7 +156,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Alpha = visualizer_default_alpha,
|
Alpha = visualizer_default_alpha,
|
||||||
Size = new Vector2(0.96f)
|
Size = SCALE_ADJUST
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -162,7 +168,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Scale = new Vector2(0.88f),
|
Scale = SCALE_ADJUST,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -406,7 +412,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
public void Impact()
|
public void Impact()
|
||||||
{
|
{
|
||||||
impactContainer.FadeOutFromOne(250, Easing.In);
|
impactContainer.FadeOutFromOne(250, Easing.In);
|
||||||
impactContainer.ScaleTo(0.96f);
|
impactContainer.ScaleTo(SCALE_ADJUST);
|
||||||
impactContainer.ScaleTo(1.12f, 250);
|
impactContainer.ScaleTo(1.12f, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user