mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 14:32:55 +08:00
Merge branch 'master' into aim-refactor-base
This commit is contained in:
commit
30eb08d394
2
.github/workflows/report-nunit.yml
vendored
2
.github/workflows/report-nunit.yml
vendored
@ -30,3 +30,5 @@ jobs:
|
||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||
path: "*.trx"
|
||||
reporter: dotnet-trx
|
||||
list-suites: 'failed'
|
||||
list-tests: 'failed'
|
||||
|
@ -51,8 +51,8 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1013.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1015.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1014.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -42,9 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
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;
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -101,27 +102,27 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||
public override float GetBeatSnapDistanceAt(HitObject referenceObject)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float DurationToDistance(double referenceTime, double duration)
|
||||
public override float DurationToDistance(HitObject referenceObject, double duration)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override double DistanceToDuration(double referenceTime, float distance)
|
||||
public override double DistanceToDuration(HitObject referenceObject, float distance)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
||||
public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
||||
public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
@ -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", () =>
|
||||
|
@ -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 } });
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
Debug.Assert(distanceData != null);
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
|
||||
|
||||
double beatLength;
|
||||
#pragma warning disable 618
|
||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
#pragma warning restore 618
|
||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||
else
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
||||
|
||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||
|
@ -13,6 +13,7 @@ SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
0,500,4,1,0,100,1,0
|
||||
10000,-150,4,1,0,100,1,0
|
||||
|
||||
[HitObjects]
|
||||
51,192,500,128,0,1500:1:0:0:0:
|
||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
// For non-mania beatmap, speed changes should only happen through timing points
|
||||
if (!isForCurrentRuleset)
|
||||
p.DifficultyPoint = new DifficultyControlPoint();
|
||||
p.EffectPoint = new EffectControlPoint();
|
||||
}
|
||||
|
||||
BarLines.ForEach(Playfield.Add);
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||
gameplayState.Beatmap.Difficulty.CircleSize = val;
|
||||
Scheduler.AddOnce(() => loadContent(false));
|
||||
Scheduler.AddOnce(loadContent);
|
||||
});
|
||||
|
||||
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("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);
|
||||
|
||||
@ -98,7 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
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(_ =>
|
||||
{
|
||||
|
@ -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() };
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
@ -439,6 +437,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public TestSlider()
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
||||
|
||||
DefaultsApplied += _ =>
|
||||
{
|
||||
HeadCircle.HitWindows = new TestHitWindows();
|
||||
|
@ -13,6 +13,7 @@ using osuTK.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@ -328,10 +329,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
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);
|
||||
|
||||
|
@ -348,6 +348,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
StartTime = time_slider_start,
|
||||
Position = new Vector2(0, 0),
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f },
|
||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||
{
|
||||
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 } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
|
@ -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 } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
@ -399,6 +397,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public TestSlider()
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
||||
|
||||
DefaultsApplied += _ =>
|
||||
{
|
||||
HeadCircle.HitWindows = new TestHitWindows();
|
||||
|
@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
||||
// 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.
|
||||
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();
|
||||
|
||||
case IHasDuration endTimeData:
|
||||
|
@ -69,6 +69,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
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.
|
||||
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
|
||||
|
||||
@ -89,14 +93,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||
|
||||
// Don't need to jump to reach spinners
|
||||
if (!(BaseObject is Spinner))
|
||||
{
|
||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
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);
|
||||
|
||||
|
@ -8,12 +8,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -67,6 +69,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; }
|
||||
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
base.UpdateTimeAndPosition(result);
|
||||
@ -75,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
case SliderPlacementState.Initial:
|
||||
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);
|
||||
break;
|
||||
|
||||
@ -212,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
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);
|
||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||
|
@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -146,9 +146,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
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;
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||
@ -181,7 +180,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
StartTime = e.Time,
|
||||
Position = Position,
|
||||
StackHeight = StackHeight,
|
||||
SampleControlPoint = SampleControlPoint,
|
||||
});
|
||||
break;
|
||||
|
||||
|
@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModBarrelRoll(),
|
||||
new OsuModApproachDifferent(),
|
||||
new OsuModMuted(),
|
||||
new OsuModNoScope(),
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint;
|
||||
|
||||
double beatLength;
|
||||
#pragma warning disable 618
|
||||
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
#pragma warning restore 618
|
||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||
else
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
||||
|
||||
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
|
||||
|
||||
|
@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
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;
|
||||
|
||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||
|
@ -192,15 +192,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
||||
Assert.AreEqual(0, difficultyPoint.Time);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||
|
||||
difficultyPoint = controlPoints.DifficultyPointAt(48428);
|
||||
Assert.AreEqual(0, difficultyPoint.Time);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||
|
||||
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
||||
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);
|
||||
Assert.AreEqual(956, soundPoint.Time);
|
||||
@ -227,7 +227,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsTrue(effectPoint.KiaiMode);
|
||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||
|
||||
effectPoint = controlPoints.EffectPointAt(119637);
|
||||
effectPoint = controlPoints.EffectPointAt(116637);
|
||||
Assert.AreEqual(95901, effectPoint.Time);
|
||||
Assert.IsFalse(effectPoint.KiaiMode);
|
||||
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.SamplePoints.Count, Is.EqualTo(3));
|
||||
|
||||
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(3500).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).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(2500).SliderVelocity, Is.EqualTo(0.75).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(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 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(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(0).SliderVelocity, Is.EqualTo(0.5).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 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(1000).SpeedMultiplier, Is.EqualTo(10));
|
||||
Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d));
|
||||
Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5));
|
||||
Assert.That(controlPointInfo.DifficultyPointAt(5).SliderVelocity, Is.EqualTo(1));
|
||||
Assert.That(controlPointInfo.DifficultyPointAt(1000).SliderVelocity, Is.EqualTo(10));
|
||||
Assert.That(controlPointInfo.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1.8518518518518519d));
|
||||
Assert.That(controlPointInfo.DifficultyPointAt(3000).SliderVelocity, Is.EqualTo(0.5));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
sort(decoded.beatmap);
|
||||
sort(decodedAfterEncode.beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
|
||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
||||
compareBeatmaps(decoded, decodedAfterEncode);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(allBeatmaps))]
|
||||
@ -62,8 +61,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
sort(decoded.beatmap);
|
||||
sort(decodedAfterEncode.beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
|
||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
||||
compareBeatmaps(decoded, decodedAfterEncode);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(allBeatmaps))]
|
||||
@ -77,12 +75,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
|
||||
|
||||
// in this process, we may lose some detail in the control points section.
|
||||
// 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));
|
||||
compareBeatmaps(decoded, decodedAfterEncode);
|
||||
|
||||
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
|
||||
{
|
||||
@ -97,7 +90,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
// 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,
|
||||
// which isn't what we want to be testing here.
|
||||
if (point is SampleControlPoint)
|
||||
if (point is SampleControlPoint || point is DifficultyControlPoint)
|
||||
continue;
|
||||
|
||||
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]
|
||||
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))
|
||||
{
|
||||
@ -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 stream = new MemoryStream();
|
||||
|
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
104
osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
Normal file
104
osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
128
osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
Normal file
128
osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
86
osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs
Normal file
86
osu.Game.Tests/Editing/Checks/CheckZeroByteFilesTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
@ -55,8 +56,6 @@ namespace osu.Game.Tests.Editing
|
||||
|
||||
composer.EditorBeatmap.Difficulty.SliderMultiplier = 1;
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
});
|
||||
|
||||
@ -73,13 +72,13 @@ namespace osu.Game.Tests.Editing
|
||||
[TestCase(2)]
|
||||
public void TestSpeedMultiplier(float multiplier)
|
||||
{
|
||||
AddStep($"set multiplier = {multiplier}", () =>
|
||||
assertSnapDistance(100 * multiplier, new HitObject
|
||||
{
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier });
|
||||
DifficultyControlPoint = new DifficultyControlPoint
|
||||
{
|
||||
SliderVelocity = multiplier
|
||||
}
|
||||
});
|
||||
|
||||
assertSnapDistance(100 * multiplier);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
@ -197,20 +196,20 @@ namespace osu.Game.Tests.Editing
|
||||
assertSnappedDistance(400, 400);
|
||||
}
|
||||
|
||||
private void assertSnapDistance(float expectedDistance)
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance);
|
||||
private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()) == 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)
|
||||
=> 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)
|
||||
=> 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)
|
||||
=> 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
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestAddRedundantDifficulty()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
|
||||
cpi.Add(0, 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.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.DifficultyPoints.Count, Is.EqualTo(1));
|
||||
@ -159,7 +159,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestAddControlPointToGroup()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
|
||||
var group = cpi.GroupAt(1000, true);
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
@ -174,23 +174,23 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestAddDuplicateControlPointToGroup()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
|
||||
var group = cpi.GroupAt(1000, true);
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
|
||||
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(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]
|
||||
public void TestRemoveControlPointFromGroup()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
|
||||
var group = cpi.GroupAt(1000, true);
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
@ -208,14 +208,14 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestOrdering()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
|
||||
cpi.Add(0, new TimingControlPoint());
|
||||
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
||||
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
|
||||
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
|
||||
cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
|
||||
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
|
||||
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
||||
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
@ -230,14 +230,14 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestClear()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
var cpi = new LegacyControlPointInfo();
|
||||
|
||||
cpi.Add(0, new TimingControlPoint());
|
||||
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
||||
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
|
||||
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
|
||||
cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
|
||||
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
|
||||
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
||||
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
|
BIN
osu.Game.Tests/Resources/Samples/blank.wav
Normal file
BIN
osu.Game.Tests/Resources/Samples/blank.wav
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Samples/corrupt.wav
Normal file
BIN
osu.Game.Tests/Resources/Samples/corrupt.wav
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
osu.Game.Tests/Resources/Samples/test-sample-cut.mp3
Normal file
BIN
osu.Game.Tests/Resources/Samples/test-sample-cut.mp3
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4
Normal file
BIN
osu.Game.Tests/Resources/Videos/test-video-with-audio.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
osu.Game.Tests/Resources/Videos/test-video.mp4
Normal file
BIN
osu.Game.Tests/Resources/Videos/test-video.mp4
Normal file
Binary file not shown.
@ -163,5 +163,11 @@ namespace osu.Game.Tests.Visual.Audio
|
||||
}
|
||||
|
||||
private void waitTrackPlay() => AddWaitStep("Let track play", 10);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
track?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
public new float DistanceSpacing => base.DistanceSpacing;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK.Input;
|
||||
@ -30,23 +31,35 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
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));
|
||||
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("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||
checkMutations();
|
||||
|
||||
AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
|
||||
|
||||
checkMutations();
|
||||
|
||||
AddStep("Exit", () => InputManager.Key(Key.Escape));
|
||||
|
||||
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));
|
||||
|
||||
AddUntilStep("Wait for editor load", () => editor != null);
|
||||
|
||||
checkMutations();
|
||||
}
|
||||
|
||||
private void checkMutations()
|
||||
{
|
||||
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,9 +93,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
||||
{
|
||||
new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } },
|
||||
new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } },
|
||||
new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } }
|
||||
new MultiplierControlPoint(time_range) { EffectPoint = { ScrollSpeed = 1.25 } },
|
||||
new MultiplierControlPoint(1.5 * time_range) { EffectPoint = { ScrollSpeed = 1 } },
|
||||
new MultiplierControlPoint(2 * time_range) { EffectPoint = { ScrollSpeed = 1.5 } }
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
@ -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);
|
||||
}, 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);
|
||||
|
||||
@ -582,6 +589,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
|
||||
}
|
||||
|
||||
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
{
|
||||
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
|
@ -4,7 +4,7 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||
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
|
||||
() => 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)
|
||||
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
|
||||
|
@ -1,11 +1,15 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@ -19,28 +23,62 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("create component", () =>
|
||||
{
|
||||
LabelledSliderBar<double> component;
|
||||
FillFlowContainer flow;
|
||||
|
||||
Child = new Container
|
||||
Child = flow = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 500,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = component = new LabelledSliderBar<double>
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Current = new BindableDouble(5)
|
||||
new LabelledSliderBar<double>
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 1,
|
||||
}
|
||||
}
|
||||
Current = new BindableDouble(5)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 1,
|
||||
},
|
||||
Label = "a sample component",
|
||||
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
component.Label = "a sample component";
|
||||
component.Description = hasDescription ? "this text describes the component" : string.Empty;
|
||||
foreach (var colour in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
/// Delete a beatmap difficulty.
|
||||
/// </summary>
|
||||
@ -338,5 +333,14 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPostImports<out BeatmapSetInfo>
|
||||
|
||||
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PostImport
|
||||
{
|
||||
set => beatmapModelManager.PostImport = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +192,13 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
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 sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
@ -202,7 +209,6 @@ namespace osu.Game.Beatmaps
|
||||
using (ContextFactory.GetForWrite())
|
||||
{
|
||||
beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
|
||||
beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
|
||||
|
||||
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
|
||||
|
||||
|
@ -15,11 +15,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// The time at which the control point takes effect.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double Time => controlPointGroup?.Time ?? 0;
|
||||
public double Time { get; set; }
|
||||
|
||||
private ControlPointGroup controlPointGroup;
|
||||
|
||||
public void AttachGroup(ControlPointGroup pointGroup) => controlPointGroup = pointGroup;
|
||||
public void AttachGroup(ControlPointGroup pointGroup) => Time = pointGroup.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)
|
||||
{
|
||||
Time = other.Time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,14 +33,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
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>
|
||||
/// All effect points.
|
||||
/// </summary>
|
||||
@ -55,14 +47,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
[JsonIgnore]
|
||||
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>
|
||||
/// Finds the effect control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
@ -100,7 +84,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
groups.Clear();
|
||||
timingPoints.Clear();
|
||||
difficultyPoints.Clear();
|
||||
effectPoints.Clear();
|
||||
}
|
||||
|
||||
@ -277,10 +260,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
case EffectControlPoint _:
|
||||
existing = EffectPointAt(time);
|
||||
break;
|
||||
|
||||
case DifficultyControlPoint _:
|
||||
existing = DifficultyPointAt(time);
|
||||
break;
|
||||
}
|
||||
|
||||
return newPoint?.IsRedundant(existing) == true;
|
||||
@ -298,9 +277,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
effectPoints.Add(typed);
|
||||
break;
|
||||
|
||||
case DifficultyControlPoint typed:
|
||||
difficultyPoints.Add(typed);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"A control point of unexpected type {controlPoint.GetType()} was added to this {nameof(ControlPointInfo)}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,10 +293,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
case EffectControlPoint typed:
|
||||
effectPoints.Remove(typed);
|
||||
break;
|
||||
|
||||
case DifficultyControlPoint typed:
|
||||
difficultyPoints.Remove(typed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,17 +7,20 @@ using osuTK.Graphics;
|
||||
|
||||
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 static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
|
||||
{
|
||||
SpeedMultiplierBindable = { Disabled = true },
|
||||
SliderVelocityBindable = { Disabled = true },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The speed multiplier at this control point.
|
||||
/// The slider velocity at this control point.
|
||||
/// </summary>
|
||||
public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1)
|
||||
public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
|
||||
{
|
||||
Precision = 0.01,
|
||||
Default = 1,
|
||||
@ -28,21 +31,21 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
|
||||
|
||||
/// <summary>
|
||||
/// The speed multiplier at this control point.
|
||||
/// The slider velocity at this control point.
|
||||
/// </summary>
|
||||
public double SpeedMultiplier
|
||||
public double SliderVelocity
|
||||
{
|
||||
get => SpeedMultiplierBindable.Value;
|
||||
set => SpeedMultiplierBindable.Value = value;
|
||||
get => SliderVelocityBindable.Value;
|
||||
set => SliderVelocityBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> existing is DifficultyControlPoint existingDifficulty
|
||||
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
||||
&& SliderVelocity == existingDifficulty.SliderVelocity;
|
||||
|
||||
public override void CopyFrom(ControlPoint other)
|
||||
{
|
||||
SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier;
|
||||
SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity;
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
|
@ -12,7 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
||||
{
|
||||
KiaiModeBindable = { Disabled = true },
|
||||
OmitFirstBarLineBindable = { Disabled = true }
|
||||
OmitFirstBarLineBindable = { Disabled = true },
|
||||
ScrollSpeedBindable = { Disabled = true }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -20,6 +21,26 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
@ -49,12 +70,14 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
=> !OmitFirstBarLine
|
||||
&& existing is EffectControlPoint existingEffect
|
||||
&& KiaiMode == existingEffect.KiaiMode
|
||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine
|
||||
&& ScrollSpeed == existingEffect.ScrollSpeed;
|
||||
|
||||
public override void CopyFrom(ControlPoint other)
|
||||
{
|
||||
KiaiMode = ((EffectControlPoint)other).KiaiMode;
|
||||
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
|
||||
ScrollSpeed = ((EffectControlPoint)other).ScrollSpeed;
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ using osuTK.Graphics;
|
||||
|
||||
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 const string DEFAULT_BANK = "normal";
|
||||
|
@ -384,14 +384,21 @@ namespace osu.Game.Beatmaps.Formats
|
||||
addControlPoint(time, new LegacyDifficultyControlPoint(beatLength)
|
||||
#pragma warning restore 618
|
||||
{
|
||||
SpeedMultiplier = speedMultiplier,
|
||||
SliderVelocity = speedMultiplier,
|
||||
}, timingChange);
|
||||
|
||||
addControlPoint(time, new EffectControlPoint
|
||||
var effectPoint = new EffectControlPoint
|
||||
{
|
||||
KiaiMode = kiaiMode,
|
||||
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
|
||||
{
|
||||
|
@ -170,33 +170,30 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
||||
return;
|
||||
|
||||
var legacyControlPoints = new LegacyControlPointInfo();
|
||||
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
|
||||
legacyControlPoints.Add(point.Time, point.DeepClone());
|
||||
|
||||
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 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 point in legacyControlPoints.EffectPoints)
|
||||
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
|
||||
}
|
||||
|
||||
foreach (var group in beatmap.ControlPointInfo.Groups)
|
||||
foreach (var group in legacyControlPoints.Groups)
|
||||
{
|
||||
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.
|
||||
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
||||
var difficultyPoint = legacyControlPoints.DifficultyPointAt(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);
|
||||
}
|
||||
|
||||
void outputControlPointAt(double time, bool isTimingPoint)
|
||||
{
|
||||
var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time);
|
||||
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
||||
var samplePoint = legacyControlPoints.SamplePointAt(time);
|
||||
var effectPoint = legacyControlPoints.EffectPointAt(time);
|
||||
|
||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
||||
@ -230,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (effectPoint.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($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
||||
@ -238,6 +235,55 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
||||
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)
|
||||
|
@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
public LegacyDifficultyControlPoint()
|
||||
{
|
||||
SpeedMultiplierBindable.Precision = double.Epsilon;
|
||||
SliderVelocityBindable.Precision = double.Epsilon;
|
||||
}
|
||||
|
||||
public override void CopyFrom(ControlPoint other)
|
||||
|
@ -1,9 +1,10 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
@ -14,9 +15,9 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
/// All sound points.
|
||||
/// </summary>
|
||||
[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>
|
||||
/// Finds the sound control point that is active at <paramref name="time"/>.
|
||||
@ -26,35 +27,76 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
[NotNull]
|
||||
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()
|
||||
{
|
||||
base.Clear();
|
||||
samplePoints.Clear();
|
||||
difficultyPoints.Clear();
|
||||
}
|
||||
|
||||
protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint)
|
||||
{
|
||||
if (newPoint is SampleControlPoint)
|
||||
switch (newPoint)
|
||||
{
|
||||
var existing = BinarySearch(SamplePoints, time);
|
||||
return newPoint.IsRedundant(existing);
|
||||
}
|
||||
case SampleControlPoint _:
|
||||
// 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)
|
||||
{
|
||||
if (controlPoint is SampleControlPoint typed)
|
||||
samplePoints.Add(typed);
|
||||
switch (controlPoint)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (controlPoint is SampleControlPoint typed)
|
||||
samplePoints.Remove(typed);
|
||||
switch (controlPoint)
|
||||
{
|
||||
case SampleControlPoint typed:
|
||||
samplePoints.Remove(typed);
|
||||
break;
|
||||
|
||||
case DifficultyControlPoint typed:
|
||||
difficultyPoints.Remove(typed);
|
||||
break;
|
||||
}
|
||||
|
||||
base.GroupItemRemoved(controlPoint);
|
||||
}
|
||||
|
@ -189,11 +189,14 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public void CancelAsyncLoad()
|
||||
{
|
||||
loadCancellation?.Cancel();
|
||||
loadCancellation = new CancellationTokenSource();
|
||||
lock (beatmapFetchLock)
|
||||
{
|
||||
loadCancellation?.Cancel();
|
||||
loadCancellation = new CancellationTokenSource();
|
||||
|
||||
if (beatmapLoadTask?.IsCompleted != true)
|
||||
beatmapLoadTask = null;
|
||||
if (beatmapLoadTask?.IsCompleted != true)
|
||||
beatmapLoadTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
|
||||
@ -205,19 +208,27 @@ namespace osu.Game.Beatmaps
|
||||
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
|
||||
var b = GetBeatmap() ?? new Beatmap();
|
||||
lock (beatmapFetchLock)
|
||||
{
|
||||
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
|
||||
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
|
||||
// The original beatmap version needs to be preserved as the database doesn't contain it
|
||||
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
|
||||
|
||||
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
|
||||
b.BeatmapInfo = BeatmapInfo;
|
||||
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
|
||||
b.BeatmapInfo = BeatmapInfo;
|
||||
|
||||
return b;
|
||||
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
return b;
|
||||
}, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => BeatmapInfo.ToString();
|
||||
|
||||
|
@ -66,8 +66,12 @@ namespace osu.Game.Beatmaps
|
||||
lock (workingCache)
|
||||
{
|
||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
||||
|
||||
if (working != null)
|
||||
{
|
||||
Logger.Log($"Invalidating working beatmap cache for {info}");
|
||||
workingCache.Remove(working);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,6 +90,7 @@ namespace osu.Game.Beatmaps
|
||||
lock (workingCache)
|
||||
{
|
||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
||||
|
||||
if (working != null)
|
||||
return working;
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model 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 TFileModel : class, INamedFileInfo, new()
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Database
|
||||
/// A class which handles importing of associated models to the game store.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model type.</typeparam>
|
||||
public interface IModelImporter<TModel> : IPostNotifications
|
||||
public interface IModelImporter<TModel> : IPostNotifications, IPostImports<TModel>
|
||||
where TModel : class
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Statistics;
|
||||
@ -18,7 +17,7 @@ namespace osu.Game.Database
|
||||
/// <summary>
|
||||
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
||||
/// </summary>
|
||||
public class RealmContextFactory : Component, IRealmFactory
|
||||
public class RealmContextFactory : IDisposable, IRealmFactory
|
||||
{
|
||||
private readonly Storage storage;
|
||||
|
||||
@ -79,10 +78,11 @@ namespace osu.Game.Database
|
||||
/// <returns></returns>
|
||||
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)
|
||||
{
|
||||
if (context?.Refresh() == true)
|
||||
@ -92,7 +92,7 @@ namespace osu.Game.Database
|
||||
|
||||
public Realm CreateContext()
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (isDisposed)
|
||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||
|
||||
try
|
||||
@ -132,7 +132,7 @@ namespace osu.Game.Database
|
||||
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
|
||||
public IDisposable BlockAllOperations()
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (isDisposed)
|
||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||
|
||||
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)
|
||||
{
|
||||
context?.Dispose();
|
||||
}
|
||||
|
||||
if (!IsDisposed)
|
||||
if (!isDisposed)
|
||||
{
|
||||
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
|
||||
contextCreationLock.Wait();
|
||||
contextCreationLock.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
@ -141,12 +144,12 @@ namespace osu.Game.Graphics.Containers
|
||||
Child = box = new Box { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
Colour = defaultColour = colours.Gray8;
|
||||
hoverColour = colours.GrayF;
|
||||
highlightColour = colours.Green;
|
||||
highlightColour = colourProvider?.Highlight1 ?? colours.Green;
|
||||
}
|
||||
|
||||
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
@ -12,63 +13,74 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
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 EXPANDED_SIZE = 40;
|
||||
public const float HEIGHT = 15;
|
||||
|
||||
public const float EXPANDED_SIZE = 50;
|
||||
|
||||
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 readonly Box fill;
|
||||
private readonly Container main;
|
||||
|
||||
public Nub()
|
||||
{
|
||||
Box fill;
|
||||
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||
|
||||
Size = new Vector2(COLLAPSED_SIZE, 12);
|
||||
|
||||
BorderColour = Color4.White;
|
||||
BorderThickness = border_width;
|
||||
|
||||
Masking = true;
|
||||
|
||||
Children = new[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
fill = new Box
|
||||
main = new CircularContainer
|
||||
{
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = border_width,
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
Anchor = Anchor.TopCentre,
|
||||
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]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.Pink;
|
||||
GlowingAccentColour = colours.PinkLighter;
|
||||
GlowColour = colours.PinkDarker;
|
||||
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||
GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.2f) ?? colours.PinkLighter;
|
||||
GlowColour = colourProvider?.Highlight1 ?? colours.PinkLighter;
|
||||
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
main.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = GlowColour.Opacity(0),
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 10,
|
||||
Roundness = 8,
|
||||
Radius = 8,
|
||||
Roundness = 5,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(onCurrentValueChanged, true);
|
||||
}
|
||||
|
||||
private bool glowing;
|
||||
|
||||
public bool Glowing
|
||||
@ -80,28 +92,17 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
if (value)
|
||||
{
|
||||
this.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
||||
FadeEdgeEffectTo(1, animate_in_duration, Easing.OutQuint);
|
||||
main.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
||||
main.FadeEdgeEffectTo(0.2f, animate_in_duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
FadeEdgeEffectTo(0, animate_out_duration);
|
||||
this.FadeColour(AccentColour, animate_out_duration);
|
||||
main.FadeEdgeEffectTo(0, animate_out_duration, Easing.OutQuint);
|
||||
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>();
|
||||
|
||||
public Bindable<bool> Current
|
||||
@ -126,7 +127,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
accentColour = value;
|
||||
if (!Glowing)
|
||||
Colour = value;
|
||||
main.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +140,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
glowingAccentColour = value;
|
||||
if (Glowing)
|
||||
Colour = value;
|
||||
main.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,10 +153,22 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
glowColour = value;
|
||||
|
||||
var effect = EdgeEffect;
|
||||
var effect = main.EdgeEffect;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,11 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuCheckbox : Checkbox
|
||||
{
|
||||
public Color4 CheckedColor { get; set; } = Color4.Cyan;
|
||||
public Color4 UncheckedColor { get; set; } = Color4.White;
|
||||
public int FadeDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to play sounds when the state changes as a result of user interaction.
|
||||
/// </summary>
|
||||
@ -104,14 +99,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
Nub.Glowing = true;
|
||||
Nub.Expanded = true;
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
Nub.Glowing = false;
|
||||
Nub.Expanded = false;
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Linq;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -14,13 +15,15 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
|
||||
{
|
||||
private const float corner_radius = 4;
|
||||
private const float corner_radius = 5;
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
@ -34,11 +37,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
if (accentColour == default)
|
||||
accentColour = colours.PinkDarker;
|
||||
accentColour = colourProvider?.Light4 ?? colours.PinkDarker;
|
||||
updateAccentColour();
|
||||
}
|
||||
|
||||
@ -59,14 +62,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public override bool HandleNonPositionalInput => State == MenuState.Open;
|
||||
|
||||
private Sample sampleOpen;
|
||||
private Sample sampleClose;
|
||||
private Sample? sampleOpen;
|
||||
private Sample? sampleClose;
|
||||
|
||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||
public OsuDropdownMenu()
|
||||
{
|
||||
CornerRadius = corner_radius;
|
||||
BackgroundColour = Color4.Black.Opacity(0.5f);
|
||||
|
||||
MaskingContainer.CornerRadius = corner_radius;
|
||||
Alpha = 0;
|
||||
@ -75,9 +77,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
ItemsContainer.Padding = new MarginPadding(5);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, AudioManager audio)
|
||||
{
|
||||
BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
|
||||
|
||||
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
|
||||
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
|
||||
}
|
||||
@ -159,6 +163,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
BackgroundColourHover = accentColour ?? nonAccentHoverColour;
|
||||
BackgroundColourSelected = accentColour ?? nonAccentSelectedColour;
|
||||
BackgroundColour = BackgroundColourHover.Opacity(0);
|
||||
|
||||
UpdateBackgroundColour();
|
||||
UpdateForegroundColour();
|
||||
}
|
||||
@ -178,8 +184,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = Color4.Transparent;
|
||||
|
||||
nonAccentHoverColour = colours.PinkDarker;
|
||||
nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
|
||||
updateColours();
|
||||
@ -187,16 +191,29 @@ namespace osu.Game.Graphics.UserInterface
|
||||
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()
|
||||
{
|
||||
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 new class Content : FillFlowContainer, IHasText
|
||||
protected new class Content : CompositeDrawable, IHasText
|
||||
{
|
||||
public LocalisableString Text
|
||||
{
|
||||
@ -207,32 +224,64 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public readonly OsuSpriteText Label;
|
||||
public readonly SpriteIcon Chevron;
|
||||
|
||||
private const float chevron_offset = -3;
|
||||
|
||||
public Content()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Horizontal;
|
||||
|
||||
Children = new Drawable[]
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Chevron = new SpriteIcon
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.5f,
|
||||
Size = new Vector2(8),
|
||||
Alpha = 0,
|
||||
X = chevron_offset,
|
||||
Margin = new MarginPadding { Left = 3, Right = 3 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
},
|
||||
Label = new OsuSpriteText
|
||||
{
|
||||
X = 15,
|
||||
Origin = 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()
|
||||
{
|
||||
Foreground.Padding = new MarginPadding(4);
|
||||
Foreground.Padding = new MarginPadding(10);
|
||||
|
||||
AutoSizeAxes = Axes.None;
|
||||
Margin = new MarginPadding { Bottom = 4 };
|
||||
@ -303,8 +352,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Margin = new MarginPadding { Horizontal = 5 },
|
||||
Size = new Vector2(12),
|
||||
Size = new Vector2(16),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -313,11 +361,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
AddInternal(new HoverClickSounds());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
BackgroundColour = Color4.Black.Opacity(0.5f);
|
||||
BackgroundColourHover = colours.PinkDarker;
|
||||
BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
|
||||
BackgroundColourHover = colourProvider?.Light4 ?? colours.PinkDarker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,13 @@
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -16,6 +18,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -52,34 +55,63 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
accentColour = value;
|
||||
leftBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Colour4 backgroundColour;
|
||||
|
||||
public Color4 BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
set
|
||||
{
|
||||
backgroundColour = value;
|
||||
rightBox.Colour = value;
|
||||
}
|
||||
}
|
||||
|
||||
public OsuSliderBar()
|
||||
{
|
||||
Height = 12;
|
||||
RangePadding = 20;
|
||||
Height = Nub.HEIGHT;
|
||||
RangePadding = Nub.EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftBox = new Box
|
||||
new Container
|
||||
{
|
||||
Height = 2,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
Position = new Vector2(2, 0),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
rightBox = new Box
|
||||
{
|
||||
Height = 2,
|
||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||
Position = new Vector2(-2, 0),
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Alpha = 0.5f,
|
||||
Padding = new MarginPadding { Horizontal = 2 },
|
||||
Child = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Masking = true,
|
||||
CornerRadius = 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
|
||||
{
|
||||
@ -88,7 +120,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Expanded = true,
|
||||
Current = { Value = true }
|
||||
},
|
||||
},
|
||||
new HoverClickSounds()
|
||||
@ -97,11 +129,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(AudioManager audio, [CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
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()
|
||||
@ -119,26 +152,25 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
Nub.Glowing = true;
|
||||
updateGlow();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
Nub.Glowing = false;
|
||||
updateGlow();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
Nub.Current.Value = true;
|
||||
return base.OnMouseDown(e);
|
||||
updateGlow();
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
private void updateGlow()
|
||||
{
|
||||
Nub.Current.Value = false;
|
||||
base.OnMouseUp(e);
|
||||
Nub.Glowing = IsHovered || IsDragged;
|
||||
}
|
||||
|
||||
protected override void OnUserChange(T value)
|
||||
|
@ -2,11 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -15,30 +12,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
|
||||
|
||||
protected override DropdownMenu CreateMenu() => new SlimMenu();
|
||||
|
||||
private class SlimDropdownHeader : OsuDropdownHeader
|
||||
{
|
||||
public SlimDropdownHeader()
|
||||
{
|
||||
Height = 25;
|
||||
Icon.Size = new Vector2(16);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
@ -44,6 +47,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
protected readonly T Component;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly GridContainer grid;
|
||||
private readonly OsuTextFlowContainer labelText;
|
||||
private readonly OsuTextFlowContainer descriptionText;
|
||||
@ -62,10 +66,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("1c2125"),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@ -146,9 +149,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour osuColour)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour osuColour)
|
||||
{
|
||||
background.Colour = colourProvider?.Background4 ?? Color4Extensions.FromHex(@"1c2125");
|
||||
descriptionText.Colour = osuColour.Yellow;
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@ -23,10 +25,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.Blue3;
|
||||
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -10,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -66,11 +69,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
|
||||
{
|
||||
enabledColour = colours.BlueDark;
|
||||
disabledColour = colours.Gray3;
|
||||
enabledColour = colourProvider?.Highlight1 ?? colours.BlueDark;
|
||||
disabledColour = colourProvider?.Background3 ?? colours.Gray3;
|
||||
|
||||
switchContainer.Colour = enabledColour;
|
||||
fill.Colour = disabledColour;
|
||||
|
30
osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs
Normal file
30
osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -374,7 +374,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
UserJoined?.Invoke(user);
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
|
||||
|
@ -554,6 +554,7 @@ namespace osu.Game
|
||||
{
|
||||
beatmap.OldValue?.CancelAsyncLoad();
|
||||
beatmap.NewValue?.BeginAsyncLoad();
|
||||
Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}");
|
||||
}
|
||||
|
||||
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||
@ -642,7 +643,7 @@ namespace osu.Game
|
||||
SkinManager.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.PostImport = items => PresentScore(items.First().Value);
|
||||
|
@ -187,8 +187,6 @@ namespace osu.Game
|
||||
|
||||
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
|
||||
|
||||
AddInternal(realmFactory);
|
||||
|
||||
dependencies.CacheAs(Storage);
|
||||
|
||||
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||
@ -529,6 +527,7 @@ namespace osu.Game
|
||||
LocalConfig?.Dispose();
|
||||
|
||||
contextFactory?.FlushConnections();
|
||||
realmFactory?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,12 @@ namespace osu.Game.Overlays.Notifications
|
||||
set
|
||||
{
|
||||
progress = value;
|
||||
Scheduler.AddOnce(() => progressBar.Progress = progress);
|
||||
Scheduler.AddOnce(updateProgress, progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgress(float progress) => progressBar.Progress = progress;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
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 SettingsDropdown()
|
||||
{
|
||||
FlowContent.Spacing = new Vector2(0, 10);
|
||||
}
|
||||
|
||||
protected sealed override Drawable CreateControl() => CreateDropdown();
|
||||
|
||||
protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl();
|
||||
@ -35,7 +41,6 @@ namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public DropdownControl()
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5 };
|
||||
RelativeSizeAxes = Axes.X;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public DropdownControl()
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5 };
|
||||
RelativeSizeAxes = Axes.X;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
protected override Drawable CreateControl() => new TSlider
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5, Bottom = 5 },
|
||||
Margin = new MarginPadding { Vertical = 10 },
|
||||
RelativeSizeAxes = Axes.X
|
||||
};
|
||||
|
||||
|
@ -47,9 +47,34 @@ namespace osu.Game.Rulesets.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<TLookup> pendingWrites = new HashSet<TLookup>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -80,7 +105,8 @@ namespace osu.Game.Rulesets.Configuration
|
||||
|
||||
bindable.ValueChanged += b =>
|
||||
{
|
||||
realmFactory?.Context.Write(() => setting.Value = b.NewValue.ToString());
|
||||
lock (pendingWrites)
|
||||
pendingWrites.Add(lookup);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Edit
|
||||
new CheckAudioQuality(),
|
||||
new CheckMutedObjects(),
|
||||
new CheckFewHitsounds(),
|
||||
new CheckTooShortAudioFiles(),
|
||||
new CheckAudioInVideo(),
|
||||
|
||||
// Files
|
||||
new CheckZeroByteFiles(),
|
||||
|
||||
// Compose
|
||||
new CheckUnsnappedObjects(),
|
||||
|
112
osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs
Normal file
112
osu.Game/Rulesets/Edit/Checks/CheckAudioInVideo.cs
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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);
|
||||
}
|
||||
}
|
||||
}
|
85
osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs
Normal file
85
osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
43
osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs
Normal file
43
osu.Game/Rulesets/Edit/Checks/CheckZeroByteFiles.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -389,41 +388,42 @@ namespace osu.Game.Rulesets.Edit
|
||||
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 * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
|
||||
return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor);
|
||||
}
|
||||
|
||||
public override float DurationToDistance(double referenceTime, double duration)
|
||||
public override float DurationToDistance(HitObject referenceObject, double duration)
|
||||
{
|
||||
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
||||
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime));
|
||||
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
|
||||
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);
|
||||
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength;
|
||||
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
|
||||
return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength;
|
||||
}
|
||||
|
||||
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
||||
=> BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime;
|
||||
public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
|
||||
=> 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.
|
||||
// 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)
|
||||
snappedEndTime -= beatLength;
|
||||
|
||||
return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
|
||||
return DurationToDistance(referenceObject, snappedEndTime - startTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -466,15 +466,15 @@ namespace osu.Game.Rulesets.Edit
|
||||
public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
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
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
@ -27,41 +28,41 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// <summary>
|
||||
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
||||
/// </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>
|
||||
float GetBeatSnapDistanceAt(double referenceTime);
|
||||
float GetBeatSnapDistanceAt(HitObject referenceObject);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a duration to a distance.
|
||||
/// </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>
|
||||
/// <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>
|
||||
/// Converts a distance to a duration.
|
||||
/// </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>
|
||||
/// <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>
|
||||
/// Converts a distance to a snapped duration.
|
||||
/// </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>
|
||||
/// <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>
|
||||
/// Converts an unsnapped distance to a snapped distance.
|
||||
/// The returned distance will always be floored (as to never exceed the provided <paramref name="distance"/>.
|
||||
/// </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>
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,8 @@ namespace osu.Game.Rulesets.Objects
|
||||
}
|
||||
}
|
||||
|
||||
public SampleControlPoint SampleControlPoint;
|
||||
public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT;
|
||||
public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="HitObject"/> is in Kiai time.
|
||||
@ -94,6 +95,12 @@ namespace osu.Game.Rulesets.Objects
|
||||
|
||||
foreach (var nested in nestedHitObjects)
|
||||
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>
|
||||
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);
|
||||
|
||||
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 = legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency);
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleControlPoint ??= SampleControlPoint.DEFAULT;
|
||||
SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone();
|
||||
SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
|
||||
}
|
||||
|
||||
nestedHitObjects.Clear();
|
||||
|
@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
namespace osu.Game.Rulesets.Timing
|
||||
{
|
||||
/// <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>
|
||||
public class MultiplierControlPoint : IComparable<MultiplierControlPoint>
|
||||
{
|
||||
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Timing
|
||||
/// <summary>
|
||||
/// The aggregate multiplier which this <see cref="MultiplierControlPoint"/> provides.
|
||||
/// </summary>
|
||||
public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength;
|
||||
public double Multiplier => Velocity * EffectPoint.ScrollSpeed * BaseBeatLength / TimingPoint.BeatLength;
|
||||
|
||||
/// <summary>
|
||||
/// 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();
|
||||
|
||||
/// <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>
|
||||
public DifficultyControlPoint DifficultyPoint = new DifficultyControlPoint();
|
||||
public EffectControlPoint EffectPoint = new EffectControlPoint();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="MultiplierControlPoint"/>. This is required for JSON serialization
|
||||
|
@ -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
|
||||
var lastTimingPoint = new TimingControlPoint();
|
||||
var lastDifficultyPoint = new DifficultyControlPoint();
|
||||
var lastEffectPoint = new EffectControlPoint();
|
||||
var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
|
||||
|
||||
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
|
||||
var timingChanges = allPoints.Select(c =>
|
||||
{
|
||||
if (c is TimingControlPoint timingPoint)
|
||||
lastTimingPoint = timingPoint;
|
||||
else if (c is DifficultyControlPoint difficultyPoint)
|
||||
lastDifficultyPoint = difficultyPoint;
|
||||
switch (c)
|
||||
{
|
||||
case TimingControlPoint timingPoint:
|
||||
lastTimingPoint = timingPoint;
|
||||
break;
|
||||
|
||||
case EffectControlPoint difficultyPoint:
|
||||
lastEffectPoint = difficultyPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
return new MultiplierControlPoint(c.Time)
|
||||
{
|
||||
Velocity = Beatmap.Difficulty.SliderMultiplier,
|
||||
BaseBeatLength = baseBeatLength,
|
||||
TimingPoint = lastTimingPoint,
|
||||
DifficultyPoint = lastDifficultyPoint
|
||||
EffectPoint = lastEffectPoint
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -25,7 +25,7 @@ using osu.Game.Rulesets.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 Func<BeatmapDifficultyCache> difficulties;
|
||||
|
@ -5,14 +5,15 @@ using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
|
||||
{
|
||||
protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null)
|
||||
: base(startPosition, startTime, endTime)
|
||||
protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
|
||||
: base(referenceObject, startPosition, startTime, endTime)
|
||||
{
|
||||
}
|
||||
|
||||
@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
Vector2 normalisedDirection = direction * new Vector2(1f / distance);
|
||||
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
|
||||
|
||||
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length));
|
||||
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(ReferenceObject, (snappedPosition - StartPosition).Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
|
||||
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 double? endTime;
|
||||
|
||||
protected readonly HitObject ReferenceObject;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DistanceSnapGrid"/>.
|
||||
/// </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="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>
|
||||
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;
|
||||
|
||||
StartPosition = startPosition;
|
||||
StartTime = startTime;
|
||||
|
||||
@ -80,7 +86,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private void updateSpacing()
|
||||
{
|
||||
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
|
||||
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject);
|
||||
|
||||
if (endTime == null)
|
||||
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
|
||||
double maxDuration = endTime.Value - StartTime + 1;
|
||||
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing));
|
||||
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(ReferenceObject, DistanceSpacing));
|
||||
}
|
||||
|
||||
gridCache.Invalidate();
|
||||
|
@ -1,27 +1,106 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.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.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
|
||||
{
|
||||
public class DifficultyPointPiece : TopPointPiece
|
||||
public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
|
||||
{
|
||||
private readonly HitObject hitObject;
|
||||
|
||||
private readonly BindableNumber<double> speedMultiplier;
|
||||
|
||||
public DifficultyPointPiece(DifficultyControlPoint point)
|
||||
: base(point)
|
||||
public DifficultyPointPiece(HitObject hitObject)
|
||||
: base(hitObject.DifficultyControlPoint)
|
||||
{
|
||||
speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy();
|
||||
this.hitObject = hitObject;
|
||||
|
||||
Y = Height;
|
||||
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -3,88 +3,102 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
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.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
|
||||
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 BindableNumber<int> volume;
|
||||
|
||||
private OsuSpriteText text;
|
||||
private Container volumeBox;
|
||||
|
||||
private const int max_volume_height = 22;
|
||||
|
||||
public SamplePointPiece(SampleControlPoint samplePoint)
|
||||
public SamplePointPiece(HitObject hitObject)
|
||||
: base(hitObject.SampleControlPoint)
|
||||
{
|
||||
this.samplePoint = samplePoint;
|
||||
volume = samplePoint.SampleVolumeBindable.GetBoundCopy();
|
||||
bank = samplePoint.SampleBankBindable.GetBoundCopy();
|
||||
this.hitObject = hitObject;
|
||||
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
|
||||
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Margin = new MarginPadding { Vertical = 5 };
|
||||
volume.BindValueChanged(volume => updateText());
|
||||
bank.BindValueChanged(bank => updateText(), true);
|
||||
}
|
||||
|
||||
Origin = Anchor.BottomCentre;
|
||||
Anchor = Anchor.BottomCentre;
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
this.ShowPopover();
|
||||
return true;
|
||||
}
|
||||
|
||||
AutoSizeAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
private void updateText()
|
||||
{
|
||||
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,
|
||||
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 FillFlowContainer
|
||||
{
|
||||
new Box
|
||||
Width = 200,
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Colour = colour,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding(5),
|
||||
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
|
||||
Colour = colours.B5,
|
||||
bank = new LabelledTextBox
|
||||
{
|
||||
Label = "Bank Name",
|
||||
},
|
||||
volume = new SliderWithTextBoxInput<int>("Volume")
|
||||
{
|
||||
Current = new SampleControlPoint().SampleVolumeBindable,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
volume.BindValueChanged(volume => volumeBox.Height = max_volume_height * volume.NewValue / 100f, true);
|
||||
bank.BindValueChanged(bank => text.Text = bank.NewValue, true);
|
||||
bank.Current = point.SampleBankBindable;
|
||||
bank.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
||||
|
||||
volume.Current = point.SampleVolumeBindable;
|
||||
volume.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
@ -58,7 +59,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private Track track;
|
||||
|
||||
private const float timeline_height = 72;
|
||||
private const float timeline_expanded_height = 156;
|
||||
private const float timeline_expanded_height = 94;
|
||||
|
||||
public Timeline(Drawable userContent)
|
||||
{
|
||||
@ -158,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
if (visible.NewValue)
|
||||
{
|
||||
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.
|
||||
controlPoints.Delay(180).FadeIn(400, Easing.OutQuint);
|
||||
@ -298,14 +299,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private double getTimeFromPosition(Vector2 localPosition) =>
|
||||
(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();
|
||||
}
|
||||
}
|
||||
|
@ -45,17 +45,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
switch (point)
|
||||
{
|
||||
case DifficultyControlPoint difficultyPoint:
|
||||
AddInternal(new DifficultyPointPiece(difficultyPoint) { Depth = -2 });
|
||||
break;
|
||||
|
||||
case TimingControlPoint timingPoint:
|
||||
AddInternal(new TimingPointPiece(timingPoint));
|
||||
break;
|
||||
|
||||
case SampleControlPoint samplePoint:
|
||||
AddInternal(new SamplePointPiece(samplePoint) { Depth = -1 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user