1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 04:13:00 +08:00

Merge branch 'master' into aim-refactor-base

This commit is contained in:
smoogipoo 2021-10-17 12:00:39 +09:00
commit 30eb08d394
127 changed files with 2430 additions and 782 deletions

View File

@ -30,3 +30,5 @@ jobs:
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
path: "*.trx" path: "*.trx"
reporter: dotnet-trx reporter: dotnet-trx
list-suites: 'failed'
list-tests: 'failed'

View File

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

View File

@ -42,9 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate;

View File

@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -101,27 +102,27 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override float GetBeatSnapDistanceAt(double referenceTime) public override float GetBeatSnapDistanceAt(HitObject referenceObject)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override float DurationToDistance(double referenceTime, double duration) public override float DurationToDistance(HitObject referenceObject, double duration)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override double DistanceToDuration(double referenceTime, float distance) public override double DistanceToDuration(HitObject referenceObject, float distance)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override double GetSnappedDurationFromDistance(double referenceTime, float distance) public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View File

@ -388,7 +388,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}, },
}; };
beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
} }
AddStep("load player", () => AddStep("load player", () =>

View File

@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });

View File

@ -47,7 +47,7 @@ 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 = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
double beatLength; double beatLength;
#pragma warning disable 618 #pragma warning disable 618
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
#pragma warning restore 618 #pragma warning restore 618
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
else else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
SpanCount = repeatsData?.SpanCount() ?? 1; SpanCount = repeatsData?.SpanCount() ?? 1;
StartTime = (int)Math.Round(hitObject.StartTime); StartTime = (int)Math.Round(hitObject.StartTime);

View File

@ -13,6 +13,7 @@ SliderTickRate:1
[TimingPoints] [TimingPoints]
0,500,4,1,0,100,1,0 0,500,4,1,0,100,1,0
10000,-150,4,1,0,100,1,0
[HitObjects] [HitObjects]
51,192,500,128,0,1500:1:0:0:0: 51,192,500,128,0,1500:1:0:0:0:

View File

@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.UI
// For non-mania beatmap, speed changes should only happen through timing points // For non-mania beatmap, speed changes should only happen through timing points
if (!isForCurrentRuleset) if (!isForCurrentRuleset)
p.DifficultyPoint = new DifficultyControlPoint(); p.EffectPoint = new EffectControlPoint();
} }
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);

View File

@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
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.Rulesets.Osu.Objects;
@ -179,15 +180,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length;
public float DurationToDistance(double referenceTime, double duration) => (float)duration; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
public double DistanceToDuration(double referenceTime, float distance) => distance; public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
config.SetValue(OsuSetting.AutoCursorSize, true); config.SetValue(OsuSetting.AutoCursorSize, true);
gameplayState.Beatmap.Difficulty.CircleSize = val; gameplayState.Beatmap.Difficulty.CircleSize = val;
Scheduler.AddOnce(() => loadContent(false)); Scheduler.AddOnce(loadContent);
}); });
AddStep("test cursor container", () => loadContent(false)); AddStep("test cursor container", () => loadContent(false));
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize); AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize);
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true)); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
AddStep("load content", () => loadContent()); AddStep("load content", loadContent);
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
@ -98,7 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin()))); AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
} }
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null) private void loadContent() => loadContent(false);
private void loadContent(bool automated, Func<SkinProvidingContainer> skinProvider = null)
{ {
SetContents(_ => SetContents(_ =>
{ {

View File

@ -407,8 +407,6 @@ namespace osu.Game.Rulesets.Osu.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
SelectedMods.Value = new[] { new OsuModClassic() }; SelectedMods.Value = new[] { new OsuModClassic() };
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
@ -439,6 +437,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public TestSlider() public TestSlider()
{ {
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
DefaultsApplied += _ => DefaultsApplied += _ =>
{ {
HeadCircle.HitWindows = new TestHitWindows(); HeadCircle.HitWindows = new TestHitWindows();

View File

@ -13,6 +13,7 @@ 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.Game.Beatmaps.Legacy;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -328,10 +329,14 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); cpi.Add(0, new DifficultyControlPoint { SliderVelocity = speedMultiplier });
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); slider.ApplyDefaults(cpi, new BeatmapDifficulty
{
CircleSize = circleSize,
SliderTickRate = 3
});
var drawable = CreateDrawableSlider(slider); var drawable = CreateDrawableSlider(slider);

View File

@ -348,6 +348,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 },
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
@ -362,8 +363,6 @@ namespace osu.Game.Rulesets.Osu.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
p.OnLoadComplete += _ => p.OnLoadComplete += _ =>

View File

@ -369,8 +369,6 @@ namespace osu.Game.Rulesets.Osu.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
p.OnLoadComplete += _ => p.OnLoadComplete += _ =>
@ -399,6 +397,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public TestSlider() public TestSlider()
{ {
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
DefaultsApplied += _ => DefaultsApplied += _ =>
{ {
HeadCircle.HitWindows = new TestHitWindows(); HeadCircle.HitWindows = new TestHitWindows();

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Rulesets.Osu.Beatmaps namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
@ -44,7 +45,7 @@ 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 / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1
}.Yield(); }.Yield();
case IHasDuration endTimeData: case IHasDuration endTimeData:

View File

@ -69,6 +69,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
private void setDistances(double clockRate) private void setDistances(double clockRate)
{ {
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
if (BaseObject is Spinner || lastObject is Spinner)
return;
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
float scalingFactor = normalized_radius / (float)BaseObject.Radius; float scalingFactor = normalized_radius / (float)BaseObject.Radius;
@ -89,14 +93,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
Vector2 lastCursorPosition = getEndCursorPosition(lastObject); Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
// Don't need to jump to reach spinners JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
if (!(BaseObject is Spinner)) MovementDistance = Math.Min(JumpDistance, MovementDistance);
{
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
MovementDistance = Math.Min(JumpDistance, MovementDistance);
}
if (lastLastObject != null) if (lastLastObject != null && !(lastLastObject is Spinner))
{ {
Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject); Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject);

View File

@ -8,12 +8,14 @@ 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.Graphics; using osu.Game.Graphics;
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;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Screens.Edit;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -67,6 +69,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
inputManager = GetContainingInputManager(); inputManager = GetContainingInputManager();
} }
[Resolved]
private EditorBeatmap editorBeatmap { get; set; }
public override void UpdateTimeAndPosition(SnapResult result) public override void UpdateTimeAndPosition(SnapResult result)
{ {
base.UpdateTimeAndPosition(result); base.UpdateTimeAndPosition(result);
@ -75,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
case SliderPlacementState.Initial: case SliderPlacementState.Initial:
BeginPlacement(); BeginPlacement();
var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
break; break;
@ -212,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider() private void updateSlider()
{ {
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
bodyPiece.UpdateFrom(HitObject); bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle); headCirclePiece.UpdateFrom(HitObject.HeadCircle);

View File

@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updatePath() private void updatePath()
{ {
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
editorBeatmap?.Update(HitObject); editorBeatmap?.Update(HitObject);
} }

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{ {
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
: base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime) : base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
{ {
Masking = true; Masking = true;
} }

View File

@ -0,0 +1,76 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Mods;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
{
/// <summary>
/// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
/// </summary>
private const float min_alpha = 0.0002f;
private const float transition_duration = 100;
public override string Name => "No Scope";
public override string Acronym => "NS";
public override ModType Type => ModType.Fun;
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
public override string Description => "Where's the cursor?";
public override double ScoreMultiplier => 1;
private BindableNumber<int> currentCombo;
private float targetAlpha;
[SettingSource(
"Hidden at combo",
"The combo count at which the cursor becomes completely hidden",
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
)]
public BindableInt HiddenComboCount { get; } = new BindableInt
{
Default = 10,
Value = 10,
MinValue = 0,
MaxValue = 50,
};
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
if (HiddenComboCount.Value == 0) return;
currentCombo = scoreProcessor.Combo.GetBoundCopy();
currentCombo.BindValueChanged(combo =>
{
targetAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value);
}, true);
}
public virtual void Update(Playfield playfield)
{
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1));
}
}
public class HiddenComboSlider : OsuSliderBar<int>
{
public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
}
}

View File

@ -146,9 +146,8 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
@ -181,7 +180,6 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = e.Time, StartTime = e.Time,
Position = Position, Position = Position,
StackHeight = StackHeight, StackHeight = StackHeight,
SampleControlPoint = SampleControlPoint,
}); });
break; break;

View File

@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModBarrelRoll(), new OsuModBarrelRoll(),
new OsuModApproachDifferent(), new OsuModApproachDifferent(),
new OsuModMuted(), new OsuModMuted(),
new OsuModNoScope(),
}; };
case ModType.System: case ModType.System:

View File

@ -154,7 +154,7 @@ 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 = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint;
double beatLength; double beatLength;
#pragma warning disable 618 #pragma warning disable 618
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
#pragma warning restore 618 #pragma warning restore 618
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
else else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate; double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;

View File

@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
tickSpacing = timingPoint.BeatLength / TickRate; tickSpacing = timingPoint.BeatLength / TickRate;

View File

@ -192,15 +192,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
var difficultyPoint = controlPoints.DifficultyPointAt(0); var difficultyPoint = controlPoints.DifficultyPointAt(0);
Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
difficultyPoint = controlPoints.DifficultyPointAt(48428); difficultyPoint = controlPoints.DifficultyPointAt(48428);
Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
difficultyPoint = controlPoints.DifficultyPointAt(116999); difficultyPoint = controlPoints.DifficultyPointAt(116999);
Assert.AreEqual(116999, difficultyPoint.Time); Assert.AreEqual(116999, difficultyPoint.Time);
Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1); Assert.AreEqual(0.75, difficultyPoint.SliderVelocity, 0.1);
var soundPoint = controlPoints.SamplePointAt(0); var soundPoint = controlPoints.SamplePointAt(0);
Assert.AreEqual(956, soundPoint.Time); Assert.AreEqual(956, soundPoint.Time);
@ -227,7 +227,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(effectPoint.KiaiMode); Assert.IsTrue(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
effectPoint = controlPoints.EffectPointAt(119637); effectPoint = controlPoints.EffectPointAt(116637);
Assert.AreEqual(95901, effectPoint.Time); Assert.AreEqual(95901, effectPoint.Time);
Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
@ -249,10 +249,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3)); Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3));
Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3)); Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3));
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(1500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(2500).SliderVelocity, Is.EqualTo(0.75).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(3500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True); Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True);
Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True); Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True);
@ -279,10 +279,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu")) using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
{ {
var controlPoints = decoder.Decode(stream).ControlPointInfo; var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(0).SliderVelocity, Is.EqualTo(0.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1).Within(0.1));
} }
} }
@ -394,12 +394,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu")) using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
{ {
var controlPointInfo = decoder.Decode(stream).ControlPointInfo; var controlPointInfo = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPointInfo.DifficultyPointAt(5).SpeedMultiplier, Is.EqualTo(1)); Assert.That(controlPointInfo.DifficultyPointAt(5).SliderVelocity, Is.EqualTo(1));
Assert.That(controlPointInfo.DifficultyPointAt(1000).SpeedMultiplier, Is.EqualTo(10)); Assert.That(controlPointInfo.DifficultyPointAt(1000).SliderVelocity, Is.EqualTo(10));
Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d)); Assert.That(controlPointInfo.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1.8518518518518519d));
Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5)); Assert.That(controlPointInfo.DifficultyPointAt(3000).SliderVelocity, Is.EqualTo(0.5));
} }
} }

View File

@ -46,8 +46,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
sort(decoded.beatmap); sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap); sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); compareBeatmaps(decoded, decodedAfterEncode);
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
} }
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
@ -62,8 +61,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
sort(decoded.beatmap); sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap); sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); compareBeatmaps(decoded, decodedAfterEncode);
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
} }
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
@ -77,12 +75,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
// in this process, we may lose some detail in the control points section. compareBeatmaps(decoded, decodedAfterEncode);
// let's focus on only the hitobjects.
var originalHitObjects = decoded.beatmap.HitObjects.Serialize();
var newHitObjects = decodedAfterEncode.beatmap.HitObjects.Serialize();
Assert.That(newHitObjects, Is.EqualTo(originalHitObjects));
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
{ {
@ -97,7 +90,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
// completely ignore "legacy" types, which have been moved to HitObjects. // completely ignore "legacy" types, which have been moved to HitObjects.
// even though these would mostly be ignored by the Add call, they will still be available in groups, // even though these would mostly be ignored by the Add call, they will still be available in groups,
// which isn't what we want to be testing here. // which isn't what we want to be testing here.
if (point is SampleControlPoint) if (point is SampleControlPoint || point is DifficultyControlPoint)
continue; continue;
newControlPoints.Add(point.Time, point.DeepClone()); newControlPoints.Add(point.Time, point.DeepClone());
@ -107,6 +100,19 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private void compareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual)
{
// Check all control points that are still considered to be at a global level.
Assert.That(expected.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.TimingPoints.Serialize()));
Assert.That(expected.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.EffectPoints.Serialize()));
// Check all hitobjects.
Assert.That(expected.beatmap.HitObjects.Serialize(), Is.EqualTo(actual.beatmap.HitObjects.Serialize()));
// Check skin.
Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
}
[Test] [Test]
public void TestEncodeMultiSegmentSliderWithFloatingPointError() public void TestEncodeMultiSegmentSliderWithFloatingPointError()
{ {
@ -156,7 +162,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name) private (IBeatmap beatmap, TestLegacySkin skin) decodeFromLegacy(Stream stream, string name)
{ {
using (var reader = new LineBufferedReader(stream)) using (var reader = new LineBufferedReader(stream))
{ {
@ -174,7 +180,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap) private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin skin) fullBeatmap)
{ {
var (beatmap, beatmapSkin) = fullBeatmap; var (beatmap, beatmapSkin) = fullBeatmap;
var stream = new MemoryStream(); var stream = new MemoryStream();

View File

@ -0,0 +1,54 @@
// 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.Game.Models;
using osu.Game.Stores;
namespace osu.Game.Tests.Database
{
public class RulesetStoreTests : RealmTest
{
[Test]
public void TestCreateStore()
{
RunTestWithRealm((realmFactory, storage) =>
{
var rulesets = new RealmRulesetStore(realmFactory, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
});
}
[Test]
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
{
RunTestWithRealm((realmFactory, storage) =>
{
var rulesets = new RealmRulesetStore(realmFactory, storage);
var rulesets2 = new RealmRulesetStore(realmFactory, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
});
}
[Test]
public void TestRetrievedRulesetsAreDetached()
{
RunTestWithRealm((realmFactory, storage) =>
{
var rulesets = new RealmRulesetStore(realmFactory, storage);
Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
});
}
}
}

View File

@ -0,0 +1,104 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckAudioInVideoTest
{
private CheckAudioInVideo check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckAudioInVideo();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo
{
Filename = "abc123.mp4",
FileInfo = new FileInfo { Hash = "abcdef" }
}
})
}
}
};
}
[Test]
public void TestRegularVideoFile()
{
using (var resourceStream = TestResources.OpenResource("Videos/test-video.mp4"))
Assert.IsEmpty(check.Run(getContext(resourceStream)));
}
[Test]
public void TestVideoFileWithAudio()
{
using (var resourceStream = TestResources.OpenResource("Videos/test-video-with-audio.mp4"))
{
var issues = check.Run(getContext(resourceStream)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateHasAudioTrack);
}
}
[Test]
public void TestVideoFileWithTrackButNoAudio()
{
using (var resourceStream = TestResources.OpenResource("Videos/test-video-with-track-but-no-audio.mp4"))
{
var issues = check.Run(getContext(resourceStream)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateHasAudioTrack);
}
}
[Test]
public void TestMissingFile()
{
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
var issues = check.Run(getContext(null)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioInVideo.IssueTemplateMissingFile);
}
private BeatmapVerifierContext getContext(Stream resourceStream)
{
var storyboard = new Storyboard();
var layer = storyboard.GetLayer("Video");
layer.Add(new StoryboardVideo("abc123.mp4", 0));
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
mockWorkingBeatmap.As<IWorkingBeatmap>().SetupGet(w => w.Storyboard).Returns(storyboard);
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
}
}
}

View File

@ -0,0 +1,128 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ManagedBass;
using Moq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osuTK.Audio;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckTooShortAudioFilesTest
{
private CheckTooShortAudioFiles check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckTooShortAudioFiles();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo
{
Filename = "abc123.wav",
FileInfo = new FileInfo { Hash = "abcdef" }
}
})
}
}
};
// 0 = No output device. This still allows decoding.
if (!Bass.Init(0) && Bass.LastError != Errors.Already)
throw new AudioException("Could not initialize Bass.");
}
[Test]
public void TestDifferentExtension()
{
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
beatmap.BeatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo
{
Filename = "abc123.jpg",
FileInfo = new FileInfo { Hash = "abcdef" }
});
// Should fail to load, but not produce an error due to the extension not being expected to load.
Assert.IsEmpty(check.Run(getContext(null, allowMissing: true)));
}
[Test]
public void TestRegularAudioFile()
{
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.mp3"))
{
Assert.IsEmpty(check.Run(getContext(resourceStream)));
}
}
[Test]
public void TestBlankAudioFile()
{
using (var resourceStream = TestResources.OpenResource("Samples/blank.wav"))
{
// This is a 0 ms duration audio file, commonly used to silence sliderslides/ticks, and so should be fine.
Assert.IsEmpty(check.Run(getContext(resourceStream)));
}
}
[Test]
public void TestTooShortAudioFile()
{
using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3"))
{
var issues = check.Run(getContext(resourceStream)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateTooShort);
}
}
[Test]
public void TestMissingAudioFile()
{
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
{
Assert.IsEmpty(check.Run(getContext(resourceStream, allowMissing: true)));
}
}
[Test]
public void TestCorruptAudioFile()
{
using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
{
var issues = check.Run(getContext(resourceStream)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateBadFormat);
}
}
private BeatmapVerifierContext getContext(Stream resourceStream, bool allowMissing = false)
{
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
}
}
}

View File

@ -0,0 +1,86 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckZeroByteFilesTest
{
private CheckZeroByteFiles check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckZeroByteFiles();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo
{
Filename = "abc123.jpg",
FileInfo = new FileInfo { Hash = "abcdef" }
}
})
}
}
};
}
[Test]
public void TestNonZeroBytes()
{
Assert.IsEmpty(check.Run(getContext(byteLength: 44)));
}
[Test]
public void TestZeroBytes()
{
var issues = check.Run(getContext(byteLength: 0)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckZeroByteFiles.IssueTemplateZeroBytes);
}
[Test]
public void TestMissing()
{
Assert.IsEmpty(check.Run(getContextMissing()));
}
private BeatmapVerifierContext getContext(long byteLength)
{
var mockStream = new Mock<Stream>();
mockStream.Setup(s => s.Length).Returns(byteLength);
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(mockStream.Object);
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
}
private BeatmapVerifierContext getContextMissing()
{
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns((Stream)null);
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
}
}
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
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;
@ -55,8 +56,6 @@ namespace osu.Game.Tests.Editing
composer.EditorBeatmap.Difficulty.SliderMultiplier = 1; composer.EditorBeatmap.Difficulty.SliderMultiplier = 1;
composer.EditorBeatmap.ControlPointInfo.Clear(); composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
}); });
@ -73,13 +72,13 @@ namespace osu.Game.Tests.Editing
[TestCase(2)] [TestCase(2)]
public void TestSpeedMultiplier(float multiplier) public void TestSpeedMultiplier(float multiplier)
{ {
AddStep($"set multiplier = {multiplier}", () => assertSnapDistance(100 * multiplier, new HitObject
{ {
composer.EditorBeatmap.ControlPointInfo.Clear(); DifficultyControlPoint = new DifficultyControlPoint
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier }); {
SliderVelocity = multiplier
}
}); });
assertSnapDistance(100 * multiplier);
} }
[TestCase(1)] [TestCase(1)]
@ -197,20 +196,20 @@ namespace osu.Game.Tests.Editing
assertSnappedDistance(400, 400); assertSnappedDistance(400, 400);
} }
private void assertSnapDistance(float expectedDistance) private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance); => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()) == expectedDistance);
private void assertDurationToDistance(double duration, float expectedDistance) private void assertDurationToDistance(double duration, float expectedDistance)
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(0, duration) == expectedDistance); => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
private void assertDistanceToDuration(float distance, double expectedDuration) private void assertDistanceToDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(0, distance) == expectedDuration); => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(new HitObject(), distance) == expectedDuration);
private void assertSnappedDuration(float distance, double expectedDuration) private void assertSnappedDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(0, distance) == expectedDuration); => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(new HitObject(), distance) == expectedDuration);
private void assertSnappedDistance(float distance, float expectedDistance) private void assertSnappedDistance(float distance, float expectedDistance)
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(0, distance) == expectedDistance); => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(new HitObject(), distance) == expectedDistance);
private class TestHitObjectComposer : OsuHitObjectComposer private class TestHitObjectComposer : OsuHitObjectComposer
{ {

View File

@ -46,7 +46,7 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestAddRedundantDifficulty() public void TestAddRedundantDifficulty()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new DifficultyControlPoint()); // is redundant cpi.Add(0, new DifficultyControlPoint()); // is redundant
cpi.Add(1000, new DifficultyControlPoint()); // is redundant cpi.Add(1000, new DifficultyControlPoint()); // is redundant
@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant cpi.Add(1000, new DifficultyControlPoint { SliderVelocity = 2 }); // is not redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
@ -159,7 +159,7 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestAddControlPointToGroup() public void TestAddControlPointToGroup()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
var group = cpi.GroupAt(1000, true); var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
@ -174,23 +174,23 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestAddDuplicateControlPointToGroup() public void TestAddDuplicateControlPointToGroup()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
var group = cpi.GroupAt(1000, true); var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
group.Add(new DifficultyControlPoint()); group.Add(new DifficultyControlPoint());
group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 }); group.Add(new DifficultyControlPoint { SliderVelocity = 2 });
Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2)); Assert.That(cpi.DifficultyPoints.First().SliderVelocity, Is.EqualTo(2));
} }
[Test] [Test]
public void TestRemoveControlPointFromGroup() public void TestRemoveControlPointFromGroup()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
var group = cpi.GroupAt(1000, true); var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
@ -208,14 +208,14 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestOrdering() public void TestOrdering()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new TimingControlPoint()); cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
@ -230,14 +230,14 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestClear() public void TestClear()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new TimingControlPoint()); cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Binary file not shown.

View File

@ -163,5 +163,11 @@ namespace osu.Game.Tests.Visual.Audio
} }
private void waitTrackPlay() => AddWaitStep("Let track play", 10); private void waitTrackPlay() => AddWaitStep("Let track play", 10);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
track?.Dispose();
}
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing
public new float DistanceSpacing => base.DistanceSpacing; public new float DistanceSpacing => base.DistanceSpacing;
public TestDistanceSnapGrid(double? endTime = null) public TestDistanceSnapGrid(double? endTime = null)
: base(grid_position, 0, endTime) : base(new HitObject(), grid_position, 0, endTime)
{ {
} }
@ -158,15 +159,15 @@ namespace osu.Game.Tests.Visual.Editing
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => 10; public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10;
public float DurationToDistance(double referenceTime, double duration) => (float)duration; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
public double DistanceToDuration(double referenceTime, float distance) => distance; public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osuTK.Input; using osuTK.Input;
@ -30,23 +31,35 @@ namespace osu.Game.Tests.Visual.Editing
PushAndConfirm(() => new EditorLoader()); PushAndConfirm(() => new EditorLoader());
AddUntilStep("wait for editor load", () => editor != null); AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7); AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType<MetadataSection>().FirstOrDefault()?.IsLoaded == true);
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true); AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
AddStep("Set artist and title", () =>
{
editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
editorBeatmap.BeatmapInfo.Metadata.Title = "title";
});
AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty");
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); checkMutations();
AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
checkMutations();
AddStep("Exit", () => InputManager.Key(Key.Escape)); AddStep("Exit", () => InputManager.Key(Key.Escape));
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
@ -58,8 +71,16 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Enter editor", () => InputManager.Key(Key.Number5)); AddStep("Enter editor", () => InputManager.Key(Key.Number5));
AddUntilStep("Wait for editor load", () => editor != null); AddUntilStep("Wait for editor load", () => editor != null);
checkMutations();
}
private void checkMutations()
{
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty");
} }
} }
} }

View File

@ -93,9 +93,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint> private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
{ {
new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } }, new MultiplierControlPoint(time_range) { EffectPoint = { ScrollSpeed = 1.25 } },
new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } }, new MultiplierControlPoint(1.5 * time_range) { EffectPoint = { ScrollSpeed = 1 } },
new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } } new MultiplierControlPoint(2 * time_range) { EffectPoint = { ScrollSpeed = 1.5 } }
}; };
[Test] [Test]

View File

@ -564,11 +564,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
AddRepeatStep("click spectate button", () => AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click ready button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single()); InputManager.MoveMouseTo(readyButton);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}, 2); });
AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
@ -582,6 +589,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen); AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
} }
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
private void createRoom(Func<Room> room) private void createRoom(Func<Room> room)
{ {
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true); AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);

View File

@ -4,7 +4,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Overlays.Notifications;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Navigation namespace osu.Game.Tests.Visual.Navigation
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
[Test] [Test]
public void TestImportCreatedNotification() public void TestImportCreatedNotification()
{ {
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ImportProgressNotification>().Count() == 1); AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ProgressCompletionNotification>().Count() == 1);
} }
} }
} }

View File

@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<FillFlowContainer>().OfType<IHasText>().First().Text == collectionName))); () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName)));
private IconButton getAddOrRemoveButton(int index) private IconButton getAddOrRemoveButton(int index)
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single(); => getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();

View File

@ -1,11 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
@ -19,28 +23,62 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
AddStep("create component", () => AddStep("create component", () =>
{ {
LabelledSliderBar<double> component; FillFlowContainer flow;
Child = new Container Child = flow = new FillFlowContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Width = 500, Width = 500,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Child = component = new LabelledSliderBar<double> Direction = FillDirection.Vertical,
Children = new Drawable[]
{ {
Current = new BindableDouble(5) new LabelledSliderBar<double>
{ {
MinValue = 0, Current = new BindableDouble(5)
MaxValue = 10, {
Precision = 1, MinValue = 0,
} MaxValue = 10,
} Precision = 1,
},
Label = "a sample component",
Description = hasDescription ? "this text describes the component" : string.Empty,
},
},
}; };
component.Label = "a sample component"; foreach (var colour in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
component.Description = hasDescription ? "this text describes the component" : string.Empty; {
flow.Add(new OverlayColourContainer(colour)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new LabelledSliderBar<double>
{
Current = new BindableDouble(5)
{
MinValue = 0,
MaxValue = 10,
Precision = 1,
},
Label = "a sample component",
Description = hasDescription ? "this text describes the component" : string.Empty,
}
});
}
}); });
} }
private class OverlayColourContainer : Container
{
[Cached]
private OverlayColourProvider colourProvider;
public OverlayColourContainer(OverlayColourScheme scheme)
{
colourProvider = new OverlayColourProvider(scheme);
}
}
} }
} }

View File

@ -0,0 +1,68 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
using osuTK;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneSettingsCheckbox : OsuTestScene
{
[TestCase]
public void TestCheckbox()
{
AddStep("create component", () =>
{
FillFlowContainer flow;
Child = flow = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = "a sample component",
},
},
};
foreach (var colour1 in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
{
flow.Add(new OverlayColourContainer(colour1)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new SettingsCheckbox
{
LabelText = "a sample component",
}
});
}
});
}
private class OverlayColourContainer : Container
{
[Cached]
private OverlayColourProvider colourProvider;
public OverlayColourContainer(OverlayColourScheme scheme)
{
colourProvider = new OverlayColourProvider(scheme);
}
}
}
}

View File

@ -176,11 +176,6 @@ namespace osu.Game.Beatmaps
} }
} }
/// <summary>
/// Fired when the user requests to view the resulting import.
/// </summary>
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PresentImport { set => beatmapModelManager.PostImport = value; }
/// <summary> /// <summary>
/// Delete a beatmap difficulty. /// Delete a beatmap difficulty.
/// </summary> /// </summary>
@ -338,5 +333,14 @@ namespace osu.Game.Beatmaps
} }
#endregion #endregion
#region Implementation of IPostImports<out BeatmapSetInfo>
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PostImport
{
set => beatmapModelManager.PostImport = value;
}
#endregion
} }
} }

View File

@ -192,6 +192,13 @@ namespace osu.Game.Beatmaps
{ {
var setInfo = beatmapInfo.BeatmapSet; var setInfo = beatmapInfo.BeatmapSet;
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`.
// This should hopefully be temporary, assuming said clone is eventually removed.
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
// All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding.
beatmapContent.BeatmapInfo = beatmapInfo;
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))
@ -202,7 +209,6 @@ namespace osu.Game.Beatmaps
using (ContextFactory.GetForWrite()) using (ContextFactory.GetForWrite())
{ {
beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID); beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;

View File

@ -15,11 +15,9 @@ namespace osu.Game.Beatmaps.ControlPoints
/// The time at which the control point takes effect. /// The time at which the control point takes effect.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public double Time => controlPointGroup?.Time ?? 0; public double Time { get; set; }
private ControlPointGroup controlPointGroup; public void AttachGroup(ControlPointGroup pointGroup) => Time = pointGroup.Time;
public void AttachGroup(ControlPointGroup pointGroup) => controlPointGroup = pointGroup;
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
@ -46,6 +44,7 @@ namespace osu.Game.Beatmaps.ControlPoints
public virtual void CopyFrom(ControlPoint other) public virtual void CopyFrom(ControlPoint other)
{ {
Time = other.Time;
} }
} }
} }

View File

@ -33,14 +33,6 @@ namespace osu.Game.Beatmaps.ControlPoints
private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default); private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
/// <summary>
/// All difficulty points.
/// </summary>
[JsonProperty]
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
/// <summary> /// <summary>
/// All effect points. /// All effect points.
/// </summary> /// </summary>
@ -55,14 +47,6 @@ namespace osu.Game.Beatmaps.ControlPoints
[JsonIgnore] [JsonIgnore]
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray(); public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
/// <summary>
/// Finds the difficulty control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the difficulty control point at.</param>
/// <returns>The difficulty control point.</returns>
[NotNull]
public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
/// <summary> /// <summary>
/// Finds the effect control point that is active at <paramref name="time"/>. /// Finds the effect control point that is active at <paramref name="time"/>.
/// </summary> /// </summary>
@ -100,7 +84,6 @@ namespace osu.Game.Beatmaps.ControlPoints
{ {
groups.Clear(); groups.Clear();
timingPoints.Clear(); timingPoints.Clear();
difficultyPoints.Clear();
effectPoints.Clear(); effectPoints.Clear();
} }
@ -277,10 +260,6 @@ namespace osu.Game.Beatmaps.ControlPoints
case EffectControlPoint _: case EffectControlPoint _:
existing = EffectPointAt(time); existing = EffectPointAt(time);
break; break;
case DifficultyControlPoint _:
existing = DifficultyPointAt(time);
break;
} }
return newPoint?.IsRedundant(existing) == true; return newPoint?.IsRedundant(existing) == true;
@ -298,9 +277,8 @@ namespace osu.Game.Beatmaps.ControlPoints
effectPoints.Add(typed); effectPoints.Add(typed);
break; break;
case DifficultyControlPoint typed: default:
difficultyPoints.Add(typed); throw new ArgumentException($"A control point of unexpected type {controlPoint.GetType()} was added to this {nameof(ControlPointInfo)}");
break;
} }
} }
@ -315,10 +293,6 @@ namespace osu.Game.Beatmaps.ControlPoints
case EffectControlPoint typed: case EffectControlPoint typed:
effectPoints.Remove(typed); effectPoints.Remove(typed);
break; break;
case DifficultyControlPoint typed:
difficultyPoints.Remove(typed);
break;
} }
} }

View File

@ -7,17 +7,20 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
/// <remarks>
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
/// </remarks>
public class DifficultyControlPoint : ControlPoint public class DifficultyControlPoint : ControlPoint
{ {
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
{ {
SpeedMultiplierBindable = { Disabled = true }, SliderVelocityBindable = { Disabled = true },
}; };
/// <summary> /// <summary>
/// The speed multiplier at this control point. /// The slider velocity at this control point.
/// </summary> /// </summary>
public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
{ {
Precision = 0.01, Precision = 0.01,
Default = 1, Default = 1,
@ -28,21 +31,21 @@ namespace osu.Game.Beatmaps.ControlPoints
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
/// <summary> /// <summary>
/// The speed multiplier at this control point. /// The slider velocity at this control point.
/// </summary> /// </summary>
public double SpeedMultiplier public double SliderVelocity
{ {
get => SpeedMultiplierBindable.Value; get => SliderVelocityBindable.Value;
set => SpeedMultiplierBindable.Value = value; set => SliderVelocityBindable.Value = value;
} }
public override bool IsRedundant(ControlPoint existing) public override bool IsRedundant(ControlPoint existing)
=> existing is DifficultyControlPoint existingDifficulty => existing is DifficultyControlPoint existingDifficulty
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier; && SliderVelocity == existingDifficulty.SliderVelocity;
public override void CopyFrom(ControlPoint other) public override void CopyFrom(ControlPoint other)
{ {
SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier; SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity;
base.CopyFrom(other); base.CopyFrom(other);
} }

View File

@ -12,7 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
{ {
KiaiModeBindable = { Disabled = true }, KiaiModeBindable = { Disabled = true },
OmitFirstBarLineBindable = { Disabled = true } OmitFirstBarLineBindable = { Disabled = true },
ScrollSpeedBindable = { Disabled = true }
}; };
/// <summary> /// <summary>
@ -20,6 +21,26 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary> /// </summary>
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
/// <summary>
/// The relative scroll speed at this control point.
/// </summary>
public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1)
{
Precision = 0.01,
Default = 1,
MinValue = 0.01,
MaxValue = 10
};
/// <summary>
/// The relative scroll speed.
/// </summary>
public double ScrollSpeed
{
get => ScrollSpeedBindable.Value;
set => ScrollSpeedBindable.Value = value;
}
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
/// <summary> /// <summary>
@ -49,12 +70,14 @@ namespace osu.Game.Beatmaps.ControlPoints
=> !OmitFirstBarLine => !OmitFirstBarLine
&& existing is EffectControlPoint existingEffect && existing is EffectControlPoint existingEffect
&& KiaiMode == existingEffect.KiaiMode && KiaiMode == existingEffect.KiaiMode
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine; && OmitFirstBarLine == existingEffect.OmitFirstBarLine
&& ScrollSpeed == existingEffect.ScrollSpeed;
public override void CopyFrom(ControlPoint other) public override void CopyFrom(ControlPoint other)
{ {
KiaiMode = ((EffectControlPoint)other).KiaiMode; KiaiMode = ((EffectControlPoint)other).KiaiMode;
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine; OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
ScrollSpeed = ((EffectControlPoint)other).ScrollSpeed;
base.CopyFrom(other); base.CopyFrom(other);
} }

View File

@ -8,6 +8,9 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
/// <remarks>
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
/// </remarks>
public class SampleControlPoint : ControlPoint public class SampleControlPoint : ControlPoint
{ {
public const string DEFAULT_BANK = "normal"; public const string DEFAULT_BANK = "normal";

View File

@ -384,14 +384,21 @@ namespace osu.Game.Beatmaps.Formats
addControlPoint(time, new LegacyDifficultyControlPoint(beatLength) addControlPoint(time, new LegacyDifficultyControlPoint(beatLength)
#pragma warning restore 618 #pragma warning restore 618
{ {
SpeedMultiplier = speedMultiplier, SliderVelocity = speedMultiplier,
}, timingChange); }, timingChange);
addControlPoint(time, new EffectControlPoint var effectPoint = new EffectControlPoint
{ {
KiaiMode = kiaiMode, KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature, OmitFirstBarLine = omitFirstBarSignature,
}, timingChange); };
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
// scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments.
if (!isOsuRuleset)
effectPoint.ScrollSpeed = speedMultiplier;
addControlPoint(time, effectPoint, timingChange);
addControlPoint(time, new LegacySampleControlPoint addControlPoint(time, new LegacySampleControlPoint
{ {

View File

@ -170,33 +170,30 @@ namespace osu.Game.Beatmaps.Formats
if (beatmap.ControlPointInfo.Groups.Count == 0) if (beatmap.ControlPointInfo.Groups.Count == 0)
return; return;
var legacyControlPoints = new LegacyControlPointInfo();
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
legacyControlPoints.Add(point.Time, point.DeepClone());
writer.WriteLine("[TimingPoints]"); writer.WriteLine("[TimingPoints]");
if (!(beatmap.ControlPointInfo is LegacyControlPointInfo)) SampleControlPoint lastRelevantSamplePoint = null;
DifficultyControlPoint lastRelevantDifficultyPoint = null;
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
// iterate over hitobjects and pull out all required sample and difficulty changes
extractDifficultyControlPoints(beatmap.HitObjects);
extractSampleControlPoints(beatmap.HitObjects);
// handle scroll speed, which is stored as "slider velocity" in legacy formats.
// this is relevant for scrolling ruleset beatmaps.
if (!isOsuRuleset)
{ {
var legacyControlPoints = new LegacyControlPointInfo(); foreach (var point in legacyControlPoints.EffectPoints)
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
legacyControlPoints.Add(point.Time, point.DeepClone());
beatmap.ControlPointInfo = legacyControlPoints;
SampleControlPoint lastRelevantSamplePoint = null;
// iterate over hitobjects and pull out all required sample changes
foreach (var h in beatmap.HitObjects)
{
var hSamplePoint = h.SampleControlPoint;
if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint))
{
legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint);
lastRelevantSamplePoint = hSamplePoint;
}
}
} }
foreach (var group in beatmap.ControlPointInfo.Groups) foreach (var group in legacyControlPoints.Groups)
{ {
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault(); var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
@ -209,16 +206,16 @@ namespace osu.Game.Beatmaps.Formats
} }
// Output any remaining effects as secondary non-timing control point. // Output any remaining effects as secondary non-timing control point.
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time); var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time);
writer.Write(FormattableString.Invariant($"{group.Time},")); writer.Write(FormattableString.Invariant($"{group.Time},"));
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},")); writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},"));
outputControlPointAt(group.Time, false); outputControlPointAt(group.Time, false);
} }
void outputControlPointAt(double time, bool isTimingPoint) void outputControlPointAt(double time, bool isTimingPoint)
{ {
var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time); var samplePoint = legacyControlPoints.SamplePointAt(time);
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); var effectPoint = legacyControlPoints.EffectPointAt(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));
@ -230,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats
if (effectPoint.OmitFirstBarLine) if (effectPoint.OmitFirstBarLine)
effectFlags |= LegacyEffectFlags.OmitFirstBarLine; effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},")); writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},"));
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
@ -238,6 +235,55 @@ namespace osu.Game.Beatmaps.Formats
writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
writer.WriteLine(); writer.WriteLine();
} }
IEnumerable<DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
{
if (!isOsuRuleset)
yield break;
foreach (var hitObject in hitObjects)
{
yield return hitObject.DifficultyControlPoint;
foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects))
yield return nested;
}
}
void extractDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
{
foreach (var hDifficultyPoint in collectDifficultyControlPoints(hitObjects).OrderBy(dp => dp.Time))
{
if (!hDifficultyPoint.IsRedundant(lastRelevantDifficultyPoint))
{
legacyControlPoints.Add(hDifficultyPoint.Time, hDifficultyPoint);
lastRelevantDifficultyPoint = hDifficultyPoint;
}
}
}
IEnumerable<SampleControlPoint> collectSampleControlPoints(IEnumerable<HitObject> hitObjects)
{
foreach (var hitObject in hitObjects)
{
yield return hitObject.SampleControlPoint;
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
yield return nested;
}
}
void extractSampleControlPoints(IEnumerable<HitObject> hitObject)
{
foreach (var hSamplePoint in collectSampleControlPoints(hitObject).OrderBy(sp => sp.Time))
{
if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint))
{
legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint);
lastRelevantSamplePoint = hSamplePoint;
}
}
}
} }
private void handleColours(TextWriter writer) private void handleColours(TextWriter writer)

View File

@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps.Formats
public LegacyDifficultyControlPoint() public LegacyDifficultyControlPoint()
{ {
SpeedMultiplierBindable.Precision = double.Epsilon; SliderVelocityBindable.Precision = double.Epsilon;
} }
public override void CopyFrom(ControlPoint other) public override void CopyFrom(ControlPoint other)

View File

@ -1,9 +1,10 @@
// 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 JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables; using osu.Framework.Lists;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps.Legacy namespace osu.Game.Beatmaps.Legacy
@ -14,9 +15,9 @@ namespace osu.Game.Beatmaps.Legacy
/// All sound points. /// All sound points.
/// </summary> /// </summary>
[JsonProperty] [JsonProperty]
public IBindableList<SampleControlPoint> SamplePoints => samplePoints; public IReadOnlyList<SampleControlPoint> SamplePoints => samplePoints;
private readonly BindableList<SampleControlPoint> samplePoints = new BindableList<SampleControlPoint>(); private readonly SortedList<SampleControlPoint> samplePoints = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default);
/// <summary> /// <summary>
/// Finds the sound control point that is active at <paramref name="time"/>. /// Finds the sound control point that is active at <paramref name="time"/>.
@ -26,35 +27,76 @@ namespace osu.Game.Beatmaps.Legacy
[NotNull] [NotNull]
public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT);
/// <summary>
/// All difficulty points.
/// </summary>
[JsonProperty]
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
/// <summary>
/// Finds the difficulty control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the difficulty control point at.</param>
/// <returns>The difficulty control point.</returns>
[NotNull]
public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
public override void Clear() public override void Clear()
{ {
base.Clear(); base.Clear();
samplePoints.Clear(); samplePoints.Clear();
difficultyPoints.Clear();
} }
protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint)
{ {
if (newPoint is SampleControlPoint) switch (newPoint)
{ {
var existing = BinarySearch(SamplePoints, time); case SampleControlPoint _:
return newPoint.IsRedundant(existing); // intentionally don't use SamplePointAt (we always need to consider the first sample point).
} var existing = BinarySearch(SamplePoints, time);
return newPoint.IsRedundant(existing);
return base.CheckAlreadyExisting(time, newPoint); case DifficultyControlPoint _:
return newPoint.IsRedundant(DifficultyPointAt(time));
default:
return base.CheckAlreadyExisting(time, newPoint);
}
} }
protected override void GroupItemAdded(ControlPoint controlPoint) protected override void GroupItemAdded(ControlPoint controlPoint)
{ {
if (controlPoint is SampleControlPoint typed) switch (controlPoint)
samplePoints.Add(typed); {
case SampleControlPoint typed:
samplePoints.Add(typed);
return;
base.GroupItemAdded(controlPoint); case DifficultyControlPoint typed:
difficultyPoints.Add(typed);
return;
default:
base.GroupItemAdded(controlPoint);
break;
}
} }
protected override void GroupItemRemoved(ControlPoint controlPoint) protected override void GroupItemRemoved(ControlPoint controlPoint)
{ {
if (controlPoint is SampleControlPoint typed) switch (controlPoint)
samplePoints.Remove(typed); {
case SampleControlPoint typed:
samplePoints.Remove(typed);
break;
case DifficultyControlPoint typed:
difficultyPoints.Remove(typed);
break;
}
base.GroupItemRemoved(controlPoint); base.GroupItemRemoved(controlPoint);
} }

View File

@ -189,11 +189,14 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public void CancelAsyncLoad() public void CancelAsyncLoad()
{ {
loadCancellation?.Cancel(); lock (beatmapFetchLock)
loadCancellation = new CancellationTokenSource(); {
loadCancellation?.Cancel();
loadCancellation = new CancellationTokenSource();
if (beatmapLoadTask?.IsCompleted != true) if (beatmapLoadTask?.IsCompleted != true)
beatmapLoadTask = null; beatmapLoadTask = null;
}
} }
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout) private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
@ -205,19 +208,27 @@ namespace osu.Game.Beatmaps
return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10)); return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10));
} }
private Task<IBeatmap> loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() => private readonly object beatmapFetchLock = new object();
private Task<IBeatmap> loadBeatmapAsync()
{ {
// Todo: Handle cancellation during beatmap parsing lock (beatmapFetchLock)
var b = GetBeatmap() ?? new Beatmap(); {
return beatmapLoadTask ??= Task.Factory.StartNew(() =>
{
// Todo: Handle cancellation during beatmap parsing
var b = GetBeatmap() ?? new Beatmap();
// The original beatmap version needs to be preserved as the database doesn't contain it // The original beatmap version needs to be preserved as the database doesn't contain it
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion; BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc) // Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
b.BeatmapInfo = BeatmapInfo; b.BeatmapInfo = BeatmapInfo;
return b; return b;
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); }, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
public override string ToString() => BeatmapInfo.ToString(); public override string ToString() => BeatmapInfo.ToString();

View File

@ -66,8 +66,12 @@ namespace osu.Game.Beatmaps
lock (workingCache) lock (workingCache)
{ {
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
if (working != null) if (working != null)
{
Logger.Log($"Invalidating working beatmap cache for {info}");
workingCache.Remove(working); workingCache.Remove(working);
}
} }
} }
@ -86,6 +90,7 @@ namespace osu.Game.Beatmaps
lock (workingCache) lock (workingCache)
{ {
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
if (working != null) if (working != null)
return working; return working;

View File

@ -30,7 +30,7 @@ namespace osu.Game.Database
/// </summary> /// </summary>
/// <typeparam name="TModel">The model type.</typeparam> /// <typeparam name="TModel">The model type.</typeparam>
/// <typeparam name="TFileModel">The associated file join type.</typeparam> /// <typeparam name="TFileModel">The associated file join type.</typeparam>
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>, IPostImports<TModel> public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : class, INamedFileInfo, new() where TFileModel : class, INamedFileInfo, new()
{ {

View File

@ -13,7 +13,7 @@ namespace osu.Game.Database
/// A class which handles importing of associated models to the game store. /// A class which handles importing of associated models to the game store.
/// </summary> /// </summary>
/// <typeparam name="TModel">The model type.</typeparam> /// <typeparam name="TModel">The model type.</typeparam>
public interface IModelImporter<TModel> : IPostNotifications public interface IModelImporter<TModel> : IPostNotifications, IPostImports<TModel>
where TModel : class where TModel : class
{ {
/// <summary> /// <summary>

View File

@ -5,7 +5,6 @@ using System;
using System.Threading; using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Development; using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Statistics; using osu.Framework.Statistics;
@ -18,7 +17,7 @@ namespace osu.Game.Database
/// <summary> /// <summary>
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
/// </summary> /// </summary>
public class RealmContextFactory : Component, IRealmFactory public class RealmContextFactory : IDisposable, IRealmFactory
{ {
private readonly Storage storage; private readonly Storage storage;
@ -79,10 +78,11 @@ namespace osu.Game.Database
/// <returns></returns> /// <returns></returns>
public bool Compact() => Realm.Compact(getConfiguration()); public bool Compact() => Realm.Compact(getConfiguration());
protected override void Update() /// <summary>
/// Perform a blocking refresh on the main realm context.
/// </summary>
public void Refresh()
{ {
base.Update();
lock (contextLock) lock (contextLock)
{ {
if (context?.Refresh() == true) if (context?.Refresh() == true)
@ -92,7 +92,7 @@ namespace osu.Game.Database
public Realm CreateContext() public Realm CreateContext()
{ {
if (IsDisposed) if (isDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory)); throw new ObjectDisposedException(nameof(RealmContextFactory));
try try
@ -132,7 +132,7 @@ namespace osu.Game.Database
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns> /// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
public IDisposable BlockAllOperations() public IDisposable BlockAllOperations()
{ {
if (IsDisposed) if (isDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory)); throw new ObjectDisposedException(nameof(RealmContextFactory));
if (!ThreadSafety.IsUpdateThread) if (!ThreadSafety.IsUpdateThread)
@ -176,21 +176,23 @@ namespace osu.Game.Database
}); });
} }
protected override void Dispose(bool isDisposing) private bool isDisposed;
public void Dispose()
{ {
lock (contextLock) lock (contextLock)
{ {
context?.Dispose(); context?.Dispose();
} }
if (!IsDisposed) if (!isDisposed)
{ {
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
contextCreationLock.Wait(); contextCreationLock.Wait();
contextCreationLock.Dispose(); contextCreationLock.Dispose();
}
base.Dispose(isDisposing); isDisposed = true;
}
} }
} }
} }

View File

@ -1,11 +1,14 @@
// 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.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -141,12 +144,12 @@ namespace osu.Game.Graphics.Containers
Child = box = new Box { RelativeSizeAxes = Axes.Both }; Child = box = new Box { RelativeSizeAxes = Axes.Both };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
Colour = defaultColour = colours.Gray8; Colour = defaultColour = colours.Gray8;
hoverColour = colours.GrayF; hoverColour = colours.GrayF;
highlightColour = colours.Green; highlightColour = colourProvider?.Highlight1 ?? colours.Green;
} }
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None) public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using JetBrains.Annotations;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -12,63 +13,74 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class Nub : CircularContainer, IHasCurrentValue<bool>, IHasAccentColour public class Nub : CompositeDrawable, IHasCurrentValue<bool>, IHasAccentColour
{ {
public const float COLLAPSED_SIZE = 20; public const float HEIGHT = 15;
public const float EXPANDED_SIZE = 40;
public const float EXPANDED_SIZE = 50;
private const float border_width = 3; private const float border_width = 3;
private const double animate_in_duration = 150; private const double animate_in_duration = 200;
private const double animate_out_duration = 500; private const double animate_out_duration = 500;
private readonly Box fill;
private readonly Container main;
public Nub() public Nub()
{ {
Box fill; Size = new Vector2(EXPANDED_SIZE, HEIGHT);
Size = new Vector2(COLLAPSED_SIZE, 12); InternalChildren = new[]
BorderColour = Color4.White;
BorderThickness = border_width;
Masking = true;
Children = new[]
{ {
fill = new Box main = new CircularContainer
{ {
BorderColour = Color4.White,
BorderThickness = border_width,
Masking = true,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0, Anchor = Anchor.TopCentre,
AlwaysPresent = true, Origin = Anchor.TopCentre,
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
}
}, },
}; };
Current.ValueChanged += filled =>
{
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
};
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
{ {
AccentColour = colours.Pink; AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
GlowingAccentColour = colours.PinkLighter; GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.2f) ?? colours.PinkLighter;
GlowColour = colours.PinkDarker; GlowColour = colourProvider?.Highlight1 ?? colours.PinkLighter;
EdgeEffect = new EdgeEffectParameters main.EdgeEffect = new EdgeEffectParameters
{ {
Colour = GlowColour.Opacity(0), Colour = GlowColour.Opacity(0),
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 10, Radius = 8,
Roundness = 8, Roundness = 5,
}; };
} }
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(onCurrentValueChanged, true);
}
private bool glowing; private bool glowing;
public bool Glowing public bool Glowing
@ -80,28 +92,17 @@ namespace osu.Game.Graphics.UserInterface
if (value) if (value)
{ {
this.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint); main.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
FadeEdgeEffectTo(1, animate_in_duration, Easing.OutQuint); main.FadeEdgeEffectTo(0.2f, animate_in_duration, Easing.OutQuint);
} }
else else
{ {
FadeEdgeEffectTo(0, animate_out_duration); main.FadeEdgeEffectTo(0, animate_out_duration, Easing.OutQuint);
this.FadeColour(AccentColour, animate_out_duration); main.FadeColour(AccentColour, animate_out_duration, Easing.OutQuint);
} }
} }
} }
public bool Expanded
{
set
{
if (value)
this.ResizeTo(new Vector2(EXPANDED_SIZE, 12), animate_in_duration, Easing.OutQuint);
else
this.ResizeTo(new Vector2(COLLAPSED_SIZE, 12), animate_out_duration, Easing.OutQuint);
}
}
private readonly Bindable<bool> current = new Bindable<bool>(); private readonly Bindable<bool> current = new Bindable<bool>();
public Bindable<bool> Current public Bindable<bool> Current
@ -126,7 +127,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
accentColour = value; accentColour = value;
if (!Glowing) if (!Glowing)
Colour = value; main.Colour = value;
} }
} }
@ -139,7 +140,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
glowingAccentColour = value; glowingAccentColour = value;
if (Glowing) if (Glowing)
Colour = value; main.Colour = value;
} }
} }
@ -152,10 +153,22 @@ namespace osu.Game.Graphics.UserInterface
{ {
glowColour = value; glowColour = value;
var effect = EdgeEffect; var effect = main.EdgeEffect;
effect.Colour = Glowing ? value : value.Opacity(0); effect.Colour = Glowing ? value : value.Opacity(0);
EdgeEffect = effect; main.EdgeEffect = effect;
} }
} }
private void onCurrentValueChanged(ValueChangedEvent<bool> filled)
{
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
if (filled.NewValue)
main.ResizeWidthTo(1, animate_in_duration, Easing.OutElasticHalf);
else
main.ResizeWidthTo(0.9f, animate_out_duration, Easing.OutElastic);
main.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
}
} }
} }

View File

@ -9,16 +9,11 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuCheckbox : Checkbox public class OsuCheckbox : Checkbox
{ {
public Color4 CheckedColor { get; set; } = Color4.Cyan;
public Color4 UncheckedColor { get; set; } = Color4.White;
public int FadeDuration { get; set; }
/// <summary> /// <summary>
/// Whether to play sounds when the state changes as a result of user interaction. /// Whether to play sounds when the state changes as a result of user interaction.
/// </summary> /// </summary>
@ -104,14 +99,12 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
Nub.Glowing = true; Nub.Glowing = true;
Nub.Expanded = true;
return base.OnHover(e); return base.OnHover(e);
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
Nub.Glowing = false; Nub.Glowing = false;
Nub.Expanded = false;
base.OnHoverLost(e); base.OnHoverLost(e);
} }

View File

@ -1,8 +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.
#nullable enable
using System.Linq; using System.Linq;
using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -14,13 +15,15 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
{ {
private const float corner_radius = 4; private const float corner_radius = 5;
private Color4 accentColour; private Color4 accentColour;
@ -34,11 +37,11 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
if (accentColour == default) if (accentColour == default)
accentColour = colours.PinkDarker; accentColour = colourProvider?.Light4 ?? colours.PinkDarker;
updateAccentColour(); updateAccentColour();
} }
@ -59,14 +62,13 @@ namespace osu.Game.Graphics.UserInterface
{ {
public override bool HandleNonPositionalInput => State == MenuState.Open; public override bool HandleNonPositionalInput => State == MenuState.Open;
private Sample sampleOpen; private Sample? sampleOpen;
private Sample sampleClose; private Sample? sampleClose;
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
public OsuDropdownMenu() public OsuDropdownMenu()
{ {
CornerRadius = corner_radius; CornerRadius = corner_radius;
BackgroundColour = Color4.Black.Opacity(0.5f);
MaskingContainer.CornerRadius = corner_radius; MaskingContainer.CornerRadius = corner_radius;
Alpha = 0; Alpha = 0;
@ -75,9 +77,11 @@ namespace osu.Game.Graphics.UserInterface
ItemsContainer.Padding = new MarginPadding(5); ItemsContainer.Padding = new MarginPadding(5);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio) private void load(OverlayColourProvider? colourProvider, AudioManager audio)
{ {
BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close"); sampleClose = audio.Samples.Get(@"UI/dropdown-close");
} }
@ -159,6 +163,8 @@ namespace osu.Game.Graphics.UserInterface
{ {
BackgroundColourHover = accentColour ?? nonAccentHoverColour; BackgroundColourHover = accentColour ?? nonAccentHoverColour;
BackgroundColourSelected = accentColour ?? nonAccentSelectedColour; BackgroundColourSelected = accentColour ?? nonAccentSelectedColour;
BackgroundColour = BackgroundColourHover.Opacity(0);
UpdateBackgroundColour(); UpdateBackgroundColour();
UpdateForegroundColour(); UpdateForegroundColour();
} }
@ -178,8 +184,6 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BackgroundColour = Color4.Transparent;
nonAccentHoverColour = colours.PinkDarker; nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f); nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours(); updateColours();
@ -187,16 +191,29 @@ namespace osu.Game.Graphics.UserInterface
AddInternal(new HoverSounds()); AddInternal(new HoverSounds());
} }
protected override void UpdateBackgroundColour()
{
if (!IsPreSelected && !IsSelected)
{
Background.FadeOut(600, Easing.OutQuint);
return;
}
Background.FadeIn(100, Easing.OutQuint);
Background.FadeColour(IsPreSelected ? BackgroundColourHover : BackgroundColourSelected, 100, Easing.OutQuint);
}
protected override void UpdateForegroundColour() protected override void UpdateForegroundColour()
{ {
base.UpdateForegroundColour(); base.UpdateForegroundColour();
if (Foreground.Children.FirstOrDefault() is Content content) content.Chevron.Alpha = IsHovered ? 1 : 0; if (Foreground.Children.FirstOrDefault() is Content content)
content.Hovering = IsHovered;
} }
protected override Drawable CreateContent() => new Content(); protected override Drawable CreateContent() => new Content();
protected new class Content : FillFlowContainer, IHasText protected new class Content : CompositeDrawable, IHasText
{ {
public LocalisableString Text public LocalisableString Text
{ {
@ -207,32 +224,64 @@ namespace osu.Game.Graphics.UserInterface
public readonly OsuSpriteText Label; public readonly OsuSpriteText Label;
public readonly SpriteIcon Chevron; public readonly SpriteIcon Chevron;
private const float chevron_offset = -3;
public Content() public Content()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Direction = FillDirection.Horizontal;
Children = new Drawable[] InternalChildren = new Drawable[]
{ {
Chevron = new SpriteIcon Chevron = new SpriteIcon
{ {
AlwaysPresent = true,
Icon = FontAwesome.Solid.ChevronRight, Icon = FontAwesome.Solid.ChevronRight,
Colour = Color4.Black,
Alpha = 0.5f,
Size = new Vector2(8), Size = new Vector2(8),
Alpha = 0,
X = chevron_offset,
Margin = new MarginPadding { Left = 3, Right = 3 }, Margin = new MarginPadding { Left = 3, Right = 3 },
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
Label = new OsuSpriteText Label = new OsuSpriteText
{ {
X = 15,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
}; };
} }
[BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider? colourProvider)
{
Chevron.Colour = colourProvider?.Background5 ?? Color4.Black;
}
private bool hovering;
public bool Hovering
{
get => hovering;
set
{
if (value == hovering)
return;
hovering = value;
if (hovering)
{
Chevron.FadeIn(400, Easing.OutQuint);
Chevron.MoveToX(0, 400, Easing.OutQuint);
}
else
{
Chevron.FadeOut(200);
Chevron.MoveToX(chevron_offset, 200, Easing.In);
}
}
}
} }
} }
@ -267,7 +316,7 @@ namespace osu.Game.Graphics.UserInterface
public OsuDropdownHeader() public OsuDropdownHeader()
{ {
Foreground.Padding = new MarginPadding(4); Foreground.Padding = new MarginPadding(10);
AutoSizeAxes = Axes.None; AutoSizeAxes = Axes.None;
Margin = new MarginPadding { Bottom = 4 }; Margin = new MarginPadding { Bottom = 4 };
@ -303,8 +352,7 @@ namespace osu.Game.Graphics.UserInterface
Icon = FontAwesome.Solid.ChevronDown, Icon = FontAwesome.Solid.ChevronDown,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Margin = new MarginPadding { Horizontal = 5 }, Size = new Vector2(16),
Size = new Vector2(12),
}, },
} }
} }
@ -313,11 +361,11 @@ namespace osu.Game.Graphics.UserInterface
AddInternal(new HoverClickSounds()); AddInternal(new HoverClickSounds());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
BackgroundColour = Color4.Black.Opacity(0.5f); BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
BackgroundColourHover = colours.PinkDarker; BackgroundColourHover = colourProvider?.Light4 ?? colours.PinkDarker;
} }
} }
} }

View File

@ -3,11 +3,13 @@
using System; using System;
using System.Globalization; using System.Globalization;
using JetBrains.Annotations;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
@ -16,6 +18,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -52,34 +55,63 @@ namespace osu.Game.Graphics.UserInterface
{ {
accentColour = value; accentColour = value;
leftBox.Colour = value; leftBox.Colour = value;
}
}
private Colour4 backgroundColour;
public Color4 BackgroundColour
{
get => backgroundColour;
set
{
backgroundColour = value;
rightBox.Colour = value; rightBox.Colour = value;
} }
} }
public OsuSliderBar() public OsuSliderBar()
{ {
Height = 12; Height = Nub.HEIGHT;
RangePadding = 20; RangePadding = Nub.EXPANDED_SIZE / 2;
Children = new Drawable[] Children = new Drawable[]
{ {
leftBox = new Box new Container
{ {
Height = 2, RelativeSizeAxes = Axes.X,
EdgeSmoothness = new Vector2(0, 0.5f), AutoSizeAxes = Axes.Y,
Position = new Vector2(2, 0),
RelativeSizeAxes = Axes.None,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
}, Padding = new MarginPadding { Horizontal = 2 },
rightBox = new Box Child = new CircularContainer
{ {
Height = 2, RelativeSizeAxes = Axes.X,
EdgeSmoothness = new Vector2(0, 0.5f), AutoSizeAxes = Axes.Y,
Position = new Vector2(-2, 0), Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.None, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreRight, Masking = true,
Origin = Anchor.CentreRight, CornerRadius = 5f,
Alpha = 0.5f, Children = new Drawable[]
{
leftBox = new Box
{
Height = 5,
EdgeSmoothness = new Vector2(0, 0.5f),
RelativeSizeAxes = Axes.None,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
rightBox = new Box
{
Height = 5,
EdgeSmoothness = new Vector2(0, 0.5f),
RelativeSizeAxes = Axes.None,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Alpha = 0.5f,
},
},
},
}, },
nubContainer = new Container nubContainer = new Container
{ {
@ -88,7 +120,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
RelativePositionAxes = Axes.X, RelativePositionAxes = Axes.X,
Expanded = true, Current = { Value = true }
}, },
}, },
new HoverClickSounds() new HoverClickSounds()
@ -97,11 +129,12 @@ namespace osu.Game.Graphics.UserInterface
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; }; Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuColour colours) private void load(AudioManager audio, [CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
{ {
sample = audio.Samples.Get(@"UI/notch-tick"); sample = audio.Samples.Get(@"UI/notch-tick");
AccentColour = colours.Pink; AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
BackgroundColour = colourProvider?.Background5 ?? colours.Pink.Opacity(0.5f);
} }
protected override void Update() protected override void Update()
@ -119,26 +152,25 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
Nub.Glowing = true; updateGlow();
return base.OnHover(e); return base.OnHover(e);
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
Nub.Glowing = false; updateGlow();
base.OnHoverLost(e); base.OnHoverLost(e);
} }
protected override bool OnMouseDown(MouseDownEvent e) protected override void OnDragEnd(DragEndEvent e)
{ {
Nub.Current.Value = true; updateGlow();
return base.OnMouseDown(e); base.OnDragEnd(e);
} }
protected override void OnMouseUp(MouseUpEvent e) private void updateGlow()
{ {
Nub.Current.Value = false; Nub.Glowing = IsHovered || IsDragged;
base.OnMouseUp(e);
} }
protected override void OnUserChange(T value) protected override void OnUserChange(T value)

View File

@ -2,11 +2,8 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -15,30 +12,13 @@ namespace osu.Game.Graphics.UserInterface
{ {
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
protected override DropdownMenu CreateMenu() => new SlimMenu();
private class SlimDropdownHeader : OsuDropdownHeader private class SlimDropdownHeader : OsuDropdownHeader
{ {
public SlimDropdownHeader() public SlimDropdownHeader()
{ {
Height = 25; Height = 25;
Icon.Size = new Vector2(16);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
} }
protected override void LoadComplete()
{
base.LoadComplete();
BackgroundColour = Color4.Black.Opacity(0.25f);
}
}
private class SlimMenu : OsuDropdownMenu
{
public SlimMenu()
{
BackgroundColour = Color4.Black.Opacity(0.7f);
}
} }
} }
} }

View File

@ -1,12 +1,15 @@
// 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.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
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.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osuTK; using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
@ -44,6 +47,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// </summary> /// </summary>
protected readonly T Component; protected readonly T Component;
private readonly Box background;
private readonly GridContainer grid; private readonly GridContainer grid;
private readonly OsuTextFlowContainer labelText; private readonly OsuTextFlowContainer labelText;
private readonly OsuTextFlowContainer descriptionText; private readonly OsuTextFlowContainer descriptionText;
@ -62,10 +66,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("1c2125"),
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -146,9 +149,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour osuColour) private void load(OverlayColourProvider? colourProvider, OsuColour osuColour)
{ {
background.Colour = colourProvider?.Background4 ?? Color4Extensions.FromHex(@"1c2125");
descriptionText.Colour = osuColour.Yellow; descriptionText.Colour = osuColour.Yellow;
} }

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
@ -23,10 +25,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
{ {
BackgroundColour = colours.Blue3; BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -1,6 +1,8 @@
// 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.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -10,6 +12,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -66,11 +69,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
enabledColour = colours.BlueDark; enabledColour = colourProvider?.Highlight1 ?? colours.BlueDark;
disabledColour = colours.Gray3; disabledColour = colourProvider?.Background3 ?? colours.Gray3;
switchContainer.Colour = enabledColour; switchContainer.Colour = enabledColour;
fill.Colour = disabledColour; fill.Colour = disabledColour;

View File

@ -0,0 +1,30 @@
// 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.IO;
namespace osu.Game.IO.FileAbstraction
{
public class StreamFileAbstraction : TagLib.File.IFileAbstraction
{
public StreamFileAbstraction(string filename, Stream fileStream)
{
ReadStream = fileStream;
Name = filename;
}
public string Name { get; }
public Stream ReadStream { get; }
public Stream WriteStream => ReadStream;
public void CloseStream(Stream stream)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
stream.Close();
}
}
}

View File

@ -374,7 +374,7 @@ namespace osu.Game.Online.Multiplayer
UserJoined?.Invoke(user); UserJoined?.Invoke(user);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
}, false); });
} }
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) => Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>

View File

@ -554,6 +554,7 @@ namespace osu.Game
{ {
beatmap.OldValue?.CancelAsyncLoad(); beatmap.OldValue?.CancelAsyncLoad();
beatmap.NewValue?.BeginAsyncLoad(); beatmap.NewValue?.BeginAsyncLoad();
Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}");
} }
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods) private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
@ -642,7 +643,7 @@ namespace osu.Game
SkinManager.PostNotification = n => Notifications.Post(n); SkinManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value); BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value);
ScoreManager.PostNotification = n => Notifications.Post(n); ScoreManager.PostNotification = n => Notifications.Post(n);
ScoreManager.PostImport = items => PresentScore(items.First().Value); ScoreManager.PostImport = items => PresentScore(items.First().Value);

View File

@ -187,8 +187,6 @@ namespace osu.Game
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client")); dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
AddInternal(realmFactory);
dependencies.CacheAs(Storage); dependencies.CacheAs(Storage);
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"))); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
@ -529,6 +527,7 @@ namespace osu.Game
LocalConfig?.Dispose(); LocalConfig?.Dispose();
contextFactory?.FlushConnections(); contextFactory?.FlushConnections();
realmFactory?.Dispose();
} }
} }
} }

View File

@ -31,10 +31,12 @@ namespace osu.Game.Overlays.Notifications
set set
{ {
progress = value; progress = value;
Scheduler.AddOnce(() => progressBar.Progress = progress); Scheduler.AddOnce(updateProgress, progress);
} }
} }
private void updateProgress(float progress) => progressBar.Progress = progress;
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
@ -27,6 +28,11 @@ namespace osu.Game.Overlays.Settings
public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString())); public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString()));
public SettingsDropdown()
{
FlowContent.Spacing = new Vector2(0, 10);
}
protected sealed override Drawable CreateControl() => CreateDropdown(); protected sealed override Drawable CreateControl() => CreateDropdown();
protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl(); protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl();
@ -35,7 +41,6 @@ namespace osu.Game.Overlays.Settings
{ {
public DropdownControl() public DropdownControl()
{ {
Margin = new MarginPadding { Top = 5 };
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
} }

View File

@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Settings
{ {
public DropdownControl() public DropdownControl()
{ {
Margin = new MarginPadding { Top = 5 };
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings
{ {
protected override Drawable CreateControl() => new TSlider protected override Drawable CreateControl() => new TSlider
{ {
Margin = new MarginPadding { Top = 5, Bottom = 5 }, Margin = new MarginPadding { Vertical = 10 },
RelativeSizeAxes = Axes.X RelativeSizeAxes = Axes.X
}; };

View File

@ -47,9 +47,34 @@ namespace osu.Game.Rulesets.Configuration
} }
} }
private readonly HashSet<TLookup> pendingWrites = new HashSet<TLookup>();
protected override bool PerformSave() protected override bool PerformSave()
{ {
// do nothing, realm saves immediately TLookup[] changed;
lock (pendingWrites)
{
changed = pendingWrites.ToArray();
pendingWrites.Clear();
}
if (realmFactory == null)
return true;
using (var context = realmFactory.CreateContext())
{
context.Write(realm =>
{
foreach (var c in changed)
{
var setting = realm.All<RealmRulesetSetting>().First(s => s.RulesetID == rulesetId && s.Variant == variant && s.Key == c.ToString());
setting.Value = ConfigStore[c].ToString();
}
});
}
return true; return true;
} }
@ -80,7 +105,8 @@ namespace osu.Game.Rulesets.Configuration
bindable.ValueChanged += b => bindable.ValueChanged += b =>
{ {
realmFactory?.Context.Write(() => setting.Value = b.NewValue.ToString()); lock (pendingWrites)
pendingWrites.Add(lookup);
}; };
} }
} }

View File

@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Edit
new CheckAudioQuality(), new CheckAudioQuality(),
new CheckMutedObjects(), new CheckMutedObjects(),
new CheckFewHitsounds(), new CheckFewHitsounds(),
new CheckTooShortAudioFiles(),
new CheckAudioInVideo(),
// Files
new CheckZeroByteFiles(),
// Compose // Compose
new CheckUnsnappedObjects(), new CheckUnsnappedObjects(),

View File

@ -0,0 +1,112 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using osu.Game.IO.FileAbstraction;
using osu.Game.Rulesets.Edit.Checks.Components;
using osu.Game.Storyboards;
using TagLib;
using File = TagLib.File;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckAudioInVideo : ICheck
{
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Audio track in video files");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateHasAudioTrack(this),
new IssueTemplateMissingFile(this),
new IssueTemplateFileError(this)
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
var videoPaths = new List<string>();
foreach (var layer in context.WorkingBeatmap.Storyboard.Layers)
{
foreach (var element in layer.Elements)
{
if (!(element is StoryboardVideo video))
continue;
// Ensures we don't check the same video file multiple times in case of multiple elements using it.
if (!videoPaths.Contains(video.Path))
videoPaths.Add(video.Path);
}
}
foreach (var filename in videoPaths)
{
string storagePath = beatmapSet.GetPathForFile(filename);
if (storagePath == null)
{
// There's an element in the storyboard that requires this resource, so it being missing is worth warning about.
yield return new IssueTemplateMissingFile(this).Create(filename);
continue;
}
Issue issue;
try
{
// We use TagLib here for platform invariance; BASS cannot detect audio presence on Linux.
using (Stream data = context.WorkingBeatmap.GetStream(storagePath))
using (File tagFile = File.Create(new StreamFileAbstraction(filename, data)))
{
if (tagFile.Properties.AudioChannels == 0)
continue;
}
issue = new IssueTemplateHasAudioTrack(this).Create(filename);
}
catch (CorruptFileException)
{
issue = new IssueTemplateFileError(this).Create(filename, "Corrupt file");
}
catch (UnsupportedFormatException)
{
issue = new IssueTemplateFileError(this).Create(filename, "Unsupported format");
}
yield return issue;
}
}
public class IssueTemplateHasAudioTrack : IssueTemplate
{
public IssueTemplateHasAudioTrack(ICheck check)
: base(check, IssueType.Problem, "\"{0}\" has an audio track.")
{
}
public Issue Create(string filename) => new Issue(this, filename);
}
public class IssueTemplateFileError : IssueTemplate
{
public IssueTemplateFileError(ICheck check)
: base(check, IssueType.Error, "Could not check whether \"{0}\" has an audio track ({1}).")
{
}
public Issue Create(string filename, string errorReason) => new Issue(this, filename, errorReason);
}
public class IssueTemplateMissingFile : IssueTemplate
{
public IssueTemplateMissingFile(ICheck check)
: base(check, IssueType.Warning, "Could not check whether \"{0}\" has an audio track, because it is missing.")
{
}
public Issue Create(string filename) => new Issue(this, filename);
}
}
}

View File

@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ManagedBass;
using osu.Framework.Audio.Callbacks;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckTooShortAudioFiles : ICheck
{
private const int ms_threshold = 25;
private const int min_bytes_threshold = 100;
private readonly string[] audioExtensions = { "mp3", "ogg", "wav" };
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Too short audio files");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateTooShort(this),
new IssueTemplateBadFormat(this)
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
foreach (var file in beatmapSet.Files)
{
using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.StoragePath))
{
if (data == null)
continue;
var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data));
int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle);
if (decodeStream == 0)
{
// If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it.
// Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check.
if (hasAudioExtension(file.Filename) && probablyHasAudioData(data))
yield return new IssueTemplateBadFormat(this).Create(file.Filename);
continue;
}
long length = Bass.ChannelGetLength(decodeStream);
double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000;
// Extremely short audio files do not play on some soundcards, resulting in nothing being heard in-game for some users.
if (ms > 0 && ms < ms_threshold)
yield return new IssueTemplateTooShort(this).Create(file.Filename, ms);
}
}
}
private bool hasAudioExtension(string filename) => audioExtensions.Any(filename.ToLower().EndsWith);
private bool probablyHasAudioData(Stream data) => data.Length > min_bytes_threshold;
public class IssueTemplateTooShort : IssueTemplate
{
public IssueTemplateTooShort(ICheck check)
: base(check, IssueType.Problem, "\"{0}\" is too short ({1:0} ms), should be at least {2:0} ms.")
{
}
public Issue Create(string filename, double ms) => new Issue(this, filename, ms, ms_threshold);
}
public class IssueTemplateBadFormat : IssueTemplate
{
public IssueTemplateBadFormat(ICheck check)
: base(check, IssueType.Error, "Could not check whether \"{0}\" is too short (code \"{1}\").")
{
}
public Issue Create(string filename) => new Issue(this, filename, Bass.LastError);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckZeroByteFiles : ICheck
{
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Files, "Zero-byte files");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateZeroBytes(this)
};
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
foreach (var file in beatmapSet.Files)
{
using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.StoragePath))
{
if (data?.Length == 0)
yield return new IssueTemplateZeroBytes(this).Create(file.Filename);
}
}
}
public class IssueTemplateZeroBytes : IssueTemplate
{
public IssueTemplateZeroBytes(ICheck check)
: base(check, IssueType.Problem, "\"{0}\" is a 0-byte file.")
{
}
public Issue Create(string filename) => new Issue(this, filename);
}
}
}

View File

@ -13,7 +13,6 @@ using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -389,41 +388,42 @@ namespace osu.Game.Rulesets.Edit
return new SnapResult(screenSpacePosition, targetTime, playfield); return new SnapResult(screenSpacePosition, targetTime, playfield);
} }
public override float GetBeatSnapDistanceAt(double referenceTime) public override float GetBeatSnapDistanceAt(HitObject referenceObject)
{ {
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor);
return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
} }
public override float DurationToDistance(double referenceTime, double duration) public override float DurationToDistance(HitObject referenceObject, double duration)
{ {
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceObject));
} }
public override double DistanceToDuration(double referenceTime, float distance) public override double DistanceToDuration(HitObject referenceObject, float distance)
{ {
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength;
} }
public override double GetSnappedDurationFromDistance(double referenceTime, float distance) public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
=> BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime; => BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime;
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
{ {
double actualDuration = referenceTime + DistanceToDuration(referenceTime, distance); double startTime = referenceObject.StartTime;
double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); double actualDuration = startTime + DistanceToDuration(referenceObject, distance);
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, startTime);
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(startTime);
// we don't want to exceed the actual duration and snap to a point in the future. // we don't want to exceed the actual duration and snap to a point in the future.
// as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it.
if (snappedEndTime > actualDuration + 1) if (snappedEndTime > actualDuration + 1)
snappedEndTime -= beatLength; snappedEndTime -= beatLength;
return DurationToDistance(referenceTime, snappedEndTime - referenceTime); return DurationToDistance(referenceObject, snappedEndTime - startTime);
} }
#endregion #endregion
@ -466,15 +466,15 @@ namespace osu.Game.Rulesets.Edit
public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null); new SnapResult(screenSpacePosition, null);
public abstract float GetBeatSnapDistanceAt(double referenceTime); public abstract float GetBeatSnapDistanceAt(HitObject referenceObject);
public abstract float DurationToDistance(double referenceTime, double duration); public abstract float DurationToDistance(HitObject referenceObject, double duration);
public abstract double DistanceToDuration(double referenceTime, float distance); public abstract double DistanceToDuration(HitObject referenceObject, float distance);
public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); public abstract double GetSnappedDurationFromDistance(HitObject referenceObject, float distance);
public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); public abstract float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance);
#endregion #endregion
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
@ -27,41 +28,41 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// Retrieves the distance between two points within a timing point that are one beat length apart. /// Retrieves the distance between two points within a timing point that are one beat length apart.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns> /// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns>
float GetBeatSnapDistanceAt(double referenceTime); float GetBeatSnapDistanceAt(HitObject referenceObject);
/// <summary> /// <summary>
/// Converts a duration to a distance. /// Converts a duration to a distance.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="duration"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="duration">The duration to convert.</param> /// <param name="duration">The duration to convert.</param>
/// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns> /// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns>
float DurationToDistance(double referenceTime, double duration); float DurationToDistance(HitObject referenceObject, double duration);
/// <summary> /// <summary>
/// Converts a distance to a duration. /// Converts a distance to a duration.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns>
double DistanceToDuration(double referenceTime, float distance); double DistanceToDuration(HitObject referenceObject, float distance);
/// <summary> /// <summary>
/// Converts a distance to a snapped duration. /// Converts a distance to a snapped duration.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns>
double GetSnappedDurationFromDistance(double referenceTime, float distance); double GetSnappedDurationFromDistance(HitObject referenceObject, float distance);
/// <summary> /// <summary>
/// Converts an unsnapped distance to a snapped distance. /// Converts an unsnapped distance to a snapped distance.
/// The returned distance will always be floored (as to never exceed the provided <paramref name="distance"/>. /// The returned distance will always be floored (as to never exceed the provided <paramref name="distance"/>.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns>
float GetSnappedDistanceFromDistance(double referenceTime, float distance); float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance);
} }
} }

View File

@ -67,7 +67,8 @@ namespace osu.Game.Rulesets.Objects
} }
} }
public SampleControlPoint SampleControlPoint; public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT;
public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT;
/// <summary> /// <summary>
/// Whether this <see cref="HitObject"/> is in Kiai time. /// Whether this <see cref="HitObject"/> is in Kiai time.
@ -94,6 +95,12 @@ namespace osu.Game.Rulesets.Objects
foreach (var nested in nestedHitObjects) foreach (var nested in nestedHitObjects)
nested.StartTime += offset; nested.StartTime += offset;
if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT)
DifficultyControlPoint.Time = time.NewValue;
if (SampleControlPoint != SampleControlPoint.DEFAULT)
SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
}; };
} }
@ -105,16 +112,21 @@ 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();
DifficultyControlPoint.Time = StartTime;
}
ApplyDefaultsToSelf(controlPointInfo, difficulty); ApplyDefaultsToSelf(controlPointInfo, difficulty);
if (controlPointInfo is LegacyControlPointInfo legacyInfo) // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time.
if (legacyInfo != null)
{ {
// This is done here since ApplyDefaultsToSelf may be used to determine the end time SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone();
SampleControlPoint = legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
}
else
{
SampleControlPoint ??= SampleControlPoint.DEFAULT;
} }
nestedHitObjects.Clear(); nestedHitObjects.Clear();

View File

@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
} }

View File

@ -7,7 +7,7 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Timing namespace osu.Game.Rulesets.Timing
{ {
/// <summary> /// <summary>
/// A control point which adds an aggregated multiplier based on the provided <see cref="TimingPoint"/>'s BeatLength and <see cref="DifficultyPoint"/>'s SpeedMultiplier. /// A control point which adds an aggregated multiplier based on the provided <see cref="TimingPoint"/>'s BeatLength and <see cref="EffectPoint"/>'s SpeedMultiplier.
/// </summary> /// </summary>
public class MultiplierControlPoint : IComparable<MultiplierControlPoint> public class MultiplierControlPoint : IComparable<MultiplierControlPoint>
{ {
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Timing
/// <summary> /// <summary>
/// The aggregate multiplier which this <see cref="MultiplierControlPoint"/> provides. /// The aggregate multiplier which this <see cref="MultiplierControlPoint"/> provides.
/// </summary> /// </summary>
public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength; public double Multiplier => Velocity * EffectPoint.ScrollSpeed * BaseBeatLength / TimingPoint.BeatLength;
/// <summary> /// <summary>
/// The base beat length to scale the <see cref="TimingPoint"/> provided multiplier relative to. /// The base beat length to scale the <see cref="TimingPoint"/> provided multiplier relative to.
@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Timing
public TimingControlPoint TimingPoint = new TimingControlPoint(); public TimingControlPoint TimingPoint = new TimingControlPoint();
/// <summary> /// <summary>
/// The <see cref="DifficultyControlPoint"/> that provides additional difficulty information for this <see cref="MultiplierControlPoint"/>. /// The <see cref="EffectControlPoint"/> that provides additional difficulty information for this <see cref="MultiplierControlPoint"/>.
/// </summary> /// </summary>
public DifficultyControlPoint DifficultyPoint = new DifficultyControlPoint(); public EffectControlPoint EffectPoint = new EffectControlPoint();
/// <summary> /// <summary>
/// Creates a <see cref="MultiplierControlPoint"/>. This is required for JSON serialization /// Creates a <see cref="MultiplierControlPoint"/>. This is required for JSON serialization

View File

@ -140,25 +140,32 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point
var lastTimingPoint = new TimingControlPoint(); var lastTimingPoint = new TimingControlPoint();
var lastDifficultyPoint = new DifficultyControlPoint(); var lastEffectPoint = new EffectControlPoint();
var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default); var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); allPoints.AddRange(Beatmap.ControlPointInfo.EffectPoints);
// Generate the timing points, making non-timing changes use the previous timing change and vice-versa // Generate the timing points, making non-timing changes use the previous timing change and vice-versa
var timingChanges = allPoints.Select(c => var timingChanges = allPoints.Select(c =>
{ {
if (c is TimingControlPoint timingPoint) switch (c)
lastTimingPoint = timingPoint; {
else if (c is DifficultyControlPoint difficultyPoint) case TimingControlPoint timingPoint:
lastDifficultyPoint = difficultyPoint; lastTimingPoint = timingPoint;
break;
case EffectControlPoint difficultyPoint:
lastEffectPoint = difficultyPoint;
break;
}
return new MultiplierControlPoint(c.Time) return new MultiplierControlPoint(c.Time)
{ {
Velocity = Beatmap.Difficulty.SliderMultiplier, Velocity = Beatmap.Difficulty.SliderMultiplier,
BaseBeatLength = baseBeatLength, BaseBeatLength = baseBeatLength,
TimingPoint = lastTimingPoint, TimingPoint = lastTimingPoint,
DifficultyPoint = lastDifficultyPoint EffectPoint = lastEffectPoint
}; };
}); });

View File

@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring namespace osu.Game.Scoring
{ {
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles, IPostImports<ScoreInfo> public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles
{ {
private readonly Scheduler scheduler; private readonly Scheduler scheduler;
private readonly Func<BeatmapDifficultyCache> difficulties; private readonly Func<BeatmapDifficultyCache> difficulties;

View File

@ -5,14 +5,15 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
{ {
protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
: base(startPosition, startTime, endTime) : base(referenceObject, startPosition, startTime, endTime)
{ {
} }
@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 normalisedDirection = direction * new Vector2(1f / distance);
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius; Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length)); return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(ReferenceObject, (snappedPosition - StartPosition).Length));
} }
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
@ -54,15 +55,20 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
private readonly double? endTime; private readonly double? endTime;
protected readonly HitObject ReferenceObject;
/// <summary> /// <summary>
/// Creates a new <see cref="DistanceSnapGrid"/>. /// Creates a new <see cref="DistanceSnapGrid"/>.
/// </summary> /// </summary>
/// <param name="referenceObject">A reference object to gather relevant difficulty values from.</param>
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param> /// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param> /// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param> /// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) protected DistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
{ {
ReferenceObject = referenceObject;
this.endTime = endTime; this.endTime = endTime;
StartPosition = startPosition; StartPosition = startPosition;
StartTime = startTime; StartTime = startTime;
@ -80,7 +86,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updateSpacing() private void updateSpacing()
{ {
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject);
if (endTime == null) if (endTime == null)
MaxIntervals = int.MaxValue; MaxIntervals = int.MaxValue;
@ -88,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
// +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors
double maxDuration = endTime.Value - StartTime + 1; double maxDuration = endTime.Value - StartTime + 1;
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(ReferenceObject, DistanceSpacing));
} }
gridCache.Invalidate(); gridCache.Invalidate();

View File

@ -1,27 +1,106 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public class DifficultyPointPiece : TopPointPiece public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
{ {
private readonly HitObject hitObject;
private readonly BindableNumber<double> speedMultiplier; private readonly BindableNumber<double> speedMultiplier;
public DifficultyPointPiece(DifficultyControlPoint point) public DifficultyPointPiece(HitObject hitObject)
: base(point) : base(hitObject.DifficultyControlPoint)
{ {
speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy(); this.hitObject = hitObject;
Y = Height; speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true); speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true);
} }
protected override bool OnClick(ClickEvent e)
{
this.ShowPopover();
return true;
}
public Popover GetPopover() => new DifficultyEditPopover(hitObject);
public class DifficultyEditPopover : OsuPopover
{
private readonly HitObject hitObject;
private readonly DifficultyControlPoint point;
private SliderWithTextBoxInput<double> sliderVelocitySlider;
[Resolved(canBeNull: true)]
private EditorBeatmap beatmap { get; set; }
public DifficultyEditPopover(HitObject hitObject)
{
this.hitObject = hitObject;
point = hitObject.DifficultyControlPoint;
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new FillFlowContainer
{
Width = 200,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
sliderVelocitySlider = new SliderWithTextBoxInput<double>("Velocity")
{
Current = new DifficultyControlPoint().SliderVelocityBindable,
KeyboardStep = 0.1f
},
new OsuTextFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
}
}
}
};
var selectedPointBindable = point.SliderVelocityBindable;
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
// generally that level of precision could only be set by externally editing the .osu file, so at the point
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision;
if (selectedPointBindable.Precision < expectedPrecision)
selectedPointBindable.Precision = expectedPrecision;
sliderVelocitySlider.Current = selectedPointBindable;
sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject));
}
}
} }
} }

View File

@ -0,0 +1,63 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public class HitObjectPointPiece : CircularContainer
{
private readonly ControlPoint point;
protected OsuSpriteText Label { get; private set; }
protected HitObjectPointPiece(ControlPoint point)
{
this.point = point;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AutoSizeAxes = Axes.Both;
Color4 colour = point.GetRepresentingColour(colours);
InternalChildren = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.X,
Height = 16,
Masking = true,
CornerRadius = 8,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Children = new Drawable[]
{
new Box
{
Colour = colour,
RelativeSizeAxes = Axes.Both,
},
Label = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(5),
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
Colour = colours.B5,
}
}
},
};
}
}
}

View File

@ -3,88 +3,102 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
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.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2;
using osuTK.Graphics; using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public class SamplePointPiece : CompositeDrawable public class SamplePointPiece : HitObjectPointPiece, IHasPopover
{ {
private readonly SampleControlPoint samplePoint; private readonly HitObject hitObject;
private readonly Bindable<string> bank; private readonly Bindable<string> bank;
private readonly BindableNumber<int> volume; private readonly BindableNumber<int> volume;
private OsuSpriteText text; public SamplePointPiece(HitObject hitObject)
private Container volumeBox; : base(hitObject.SampleControlPoint)
private const int max_volume_height = 22;
public SamplePointPiece(SampleControlPoint samplePoint)
{ {
this.samplePoint = samplePoint; this.hitObject = hitObject;
volume = samplePoint.SampleVolumeBindable.GetBoundCopy(); volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
bank = samplePoint.SampleBankBindable.GetBoundCopy(); bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Margin = new MarginPadding { Vertical = 5 }; volume.BindValueChanged(volume => updateText());
bank.BindValueChanged(bank => updateText(), true);
}
Origin = Anchor.BottomCentre; protected override bool OnClick(ClickEvent e)
Anchor = Anchor.BottomCentre; {
this.ShowPopover();
return true;
}
AutoSizeAxes = Axes.X; private void updateText()
RelativeSizeAxes = Axes.Y; {
Label.Text = $"{bank.Value} {volume.Value}";
}
Color4 colour = samplePoint.GetRepresentingColour(colours); public Popover GetPopover() => new SampleEditPopover(hitObject);
InternalChildren = new Drawable[] public class SampleEditPopover : OsuPopover
{
private readonly HitObject hitObject;
private readonly SampleControlPoint point;
private LabelledTextBox bank;
private SliderWithTextBoxInput<int> volume;
[Resolved(canBeNull: true)]
private EditorBeatmap beatmap { get; set; }
public SampleEditPopover(HitObject hitObject)
{ {
volumeBox = new Circle this.hitObject = hitObject;
point = hitObject.SampleControlPoint;
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{ {
CornerRadius = 5, new FillFlowContainer
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Y = -20,
Width = 10,
Colour = colour,
},
new Container
{
AutoSizeAxes = Axes.X,
Height = 16,
Masking = true,
CornerRadius = 8,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Children = new Drawable[]
{ {
new Box Width = 200,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{ {
Colour = colour, bank = new LabelledTextBox
RelativeSizeAxes = Axes.Both, {
}, Label = "Bank Name",
text = new OsuSpriteText },
{ volume = new SliderWithTextBoxInput<int>("Volume")
Anchor = Anchor.Centre, {
Origin = Anchor.Centre, Current = new SampleControlPoint().SampleVolumeBindable,
Padding = new MarginPadding(5), }
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
Colour = colours.B5,
} }
} }
}, };
};
volume.BindValueChanged(volume => volumeBox.Height = max_volume_height * volume.NewValue / 100f, true); bank.Current = point.SampleBankBindable;
bank.BindValueChanged(bank => text.Text = bank.NewValue, true); bank.Current.BindValueChanged(_ => beatmap.Update(hitObject));
volume.Current = point.SampleVolumeBindable;
volume.Current.BindValueChanged(_ => beatmap.Update(hitObject));
}
} }
} }
} }

View File

@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
@ -58,7 +59,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private Track track; private Track track;
private const float timeline_height = 72; private const float timeline_height = 72;
private const float timeline_expanded_height = 156; private const float timeline_expanded_height = 94;
public Timeline(Drawable userContent) public Timeline(Drawable userContent)
{ {
@ -158,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (visible.NewValue) if (visible.NewValue)
{ {
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
mainContent.MoveToY(36, 200, Easing.OutQuint); mainContent.MoveToY(20, 200, Easing.OutQuint);
// delay the fade in else masking looks weird. // delay the fade in else masking looks weird.
controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); controlPoints.Delay(180).FadeIn(400, Easing.OutQuint);
@ -298,14 +299,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private double getTimeFromPosition(Vector2 localPosition) => private double getTimeFromPosition(Vector2 localPosition) =>
(localPosition.X / Content.DrawWidth) * track.Length; (localPosition.X / Content.DrawWidth) * track.Length;
public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException();
public float DurationToDistance(double referenceTime, double duration) => throw new NotImplementedException(); public float DurationToDistance(HitObject referenceObject, double duration) => throw new NotImplementedException();
public double DistanceToDuration(double referenceTime, float distance) => throw new NotImplementedException(); public double DistanceToDuration(HitObject referenceObject, float distance) => throw new NotImplementedException();
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => throw new NotImplementedException();
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => throw new NotImplementedException();
} }
} }

View File

@ -45,17 +45,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
switch (point) switch (point)
{ {
case DifficultyControlPoint difficultyPoint:
AddInternal(new DifficultyPointPiece(difficultyPoint) { Depth = -2 });
break;
case TimingControlPoint timingPoint: case TimingControlPoint timingPoint:
AddInternal(new TimingPointPiece(timingPoint)); AddInternal(new TimingPointPiece(timingPoint));
break; break;
case SampleControlPoint samplePoint:
AddInternal(new SamplePointPiece(samplePoint) { Depth = -1 });
break;
} }
} }
}, true); }, true);

Some files were not shown because too many files have changed in this diff Show More