Merge branch 'master' into playlist-item-iruleset

This commit is contained in:
Dan Balasescu
2021-11-16 16:19:10 +09:00
committed by GitHub
38 changed files with 1115 additions and 174 deletions

View File

@ -1,9 +1,11 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@ -14,19 +16,20 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Timing;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
{
private readonly HitObject hitObject;
public readonly HitObject HitObject;
private readonly BindableNumber<double> speedMultiplier;
public DifficultyPointPiece(HitObject hitObject)
: base(hitObject.DifficultyControlPoint)
{
this.hitObject = hitObject;
HitObject = hitObject;
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
}
@ -44,14 +47,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return true;
}
public Popover GetPopover() => new DifficultyEditPopover(hitObject);
public Popover GetPopover() => new DifficultyEditPopover(HitObject);
public class DifficultyEditPopover : OsuPopover
{
private readonly HitObject hitObject;
private readonly DifficultyControlPoint point;
private SliderWithTextBoxInput<double> sliderVelocitySlider;
private IndeterminateSliderWithTextBoxInput<double> sliderVelocitySlider;
[Resolved(canBeNull: true)]
private EditorBeatmap beatmap { get; set; }
@ -59,7 +61,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public DifficultyEditPopover(HitObject hitObject)
{
this.hitObject = hitObject;
point = hitObject.DifficultyControlPoint;
}
[BackgroundDependencyLoader]
@ -72,11 +73,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Width = 200,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{
sliderVelocitySlider = new SliderWithTextBoxInput<double>("Velocity")
sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput<double>("Velocity", new DifficultyControlPoint().SliderVelocityBindable)
{
Current = new DifficultyControlPoint().SliderVelocityBindable,
KeyboardStep = 0.1f
},
new OsuTextFlowContainer
@ -89,17 +90,37 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
};
var selectedPointBindable = point.SliderVelocityBindable;
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray();
// 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;
// even if there are multiple objects selected, we can still display a value if they all have the same value.
var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null;
sliderVelocitySlider.Current = selectedPointBindable;
sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject));
if (selectedPointBindable != null)
{
// 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.
sliderVelocitySlider.Current.Value = selectedPointBindable.Value;
}
sliderVelocitySlider.Current.BindValueChanged(val =>
{
if (val.NewValue == null)
return;
beatmap.BeginChange();
foreach (var h in relevantObjects)
{
h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value;
beatmap.Update(h);
}
beatmap.EndChange();
});
}
}
}

View File

@ -1,9 +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 System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@ -14,12 +19,13 @@ using osu.Game.Graphics;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Timing;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public class SamplePointPiece : HitObjectPointPiece, IHasPopover
{
private readonly HitObject hitObject;
public readonly HitObject HitObject;
private readonly Bindable<string> bank;
private readonly BindableNumber<int> volume;
@ -27,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public SamplePointPiece(HitObject hitObject)
: base(hitObject.SampleControlPoint)
{
this.hitObject = hitObject;
HitObject = hitObject;
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
}
@ -50,23 +56,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Label.Text = $"{bank.Value} {volume.Value}";
}
public Popover GetPopover() => new SampleEditPopover(hitObject);
public Popover GetPopover() => new SampleEditPopover(HitObject);
public class SampleEditPopover : OsuPopover
{
private readonly HitObject hitObject;
private readonly SampleControlPoint point;
private LabelledTextBox bank;
private SliderWithTextBoxInput<int> volume;
private LabelledTextBox bank = null!;
private IndeterminateSliderWithTextBoxInput<int> volume = null!;
[Resolved(canBeNull: true)]
private EditorBeatmap beatmap { get; set; }
private EditorBeatmap beatmap { get; set; } = null!;
public SampleEditPopover(HitObject hitObject)
{
this.hitObject = hitObject;
point = hitObject.SampleControlPoint;
}
[BackgroundDependencyLoader]
@ -79,25 +83,84 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Width = 200,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
bank = new LabelledTextBox
{
Label = "Bank Name",
},
volume = new SliderWithTextBoxInput<int>("Volume")
{
Current = new SampleControlPoint().SampleVolumeBindable,
}
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new SampleControlPoint().SampleVolumeBindable)
}
}
};
bank.Current = point.SampleBankBindable;
bank.Current.BindValueChanged(_ => beatmap.Update(hitObject));
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray();
volume.Current = point.SampleVolumeBindable;
volume.Current.BindValueChanged(_ => beatmap.Update(hitObject));
// even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value.
string? commonBank = getCommonBank(relevantControlPoints);
if (!string.IsNullOrEmpty(commonBank))
bank.Current.Value = commonBank;
int? commonVolume = getCommonVolume(relevantControlPoints);
if (commonVolume != null)
volume.Current.Value = commonVolume.Value;
updateBankPlaceholderText(relevantObjects);
bank.Current.BindValueChanged(val =>
{
updateBankFor(relevantObjects, val.NewValue);
updateBankPlaceholderText(relevantObjects);
});
// on commit, ensure that the value is correct by sourcing it from the objects' control points again.
// this ensures that committing empty text causes a revert to the previous value.
bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints);
volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue));
}
private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null;
private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null;
private void updateBankFor(IEnumerable<HitObject> objects, string? newBank)
{
if (string.IsNullOrEmpty(newBank))
return;
beatmap.BeginChange();
foreach (var h in objects)
{
h.SampleControlPoint.SampleBank = newBank;
beatmap.Update(h);
}
beatmap.EndChange();
}
private void updateBankPlaceholderText(IEnumerable<HitObject> objects)
{
string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray());
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null;
}
private void updateVolumeFor(IEnumerable<HitObject> objects, int? newVolume)
{
if (newVolume == null)
return;
beatmap.BeginChange();
foreach (var h in objects)
{
h.SampleControlPoint.SampleVolume = newVolume.Value;
beatmap.Update(h);
}
beatmap.EndChange();
}
}
}

View File

@ -31,6 +31,7 @@ using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Design;
using osu.Game.Screens.Edit.GameplayTest;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Screens.Edit.Verify;
@ -324,6 +325,19 @@ namespace osu.Game.Screens.Edit
/// </summary>
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
/// <summary>
/// Creates an <see cref="EditorState"/> instance representing the current state of the editor.
/// </summary>
/// <param name="nextBeatmap">
/// The next beatmap to be shown, in the case of difficulty switch.
/// <see langword="null"/> indicates that the beatmap will not be changing.
/// </param>
public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState
{
Time = clock.CurrentTimeAccurate,
ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty
};
/// <summary>
/// Restore the editor to a provided state.
/// </summary>
@ -486,7 +500,18 @@ namespace osu.Game.Screens.Edit
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
dimBackground();
resetTrack(true);
}
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
dimBackground();
}
private void dimBackground()
{
ApplyToBackground(b =>
{
// todo: temporary. we want to be applying dim using the UserDimContainer eventually.
@ -495,8 +520,6 @@ namespace osu.Game.Screens.Edit
b.IgnoreUserSettings.Value = true;
b.BlurAmount.Value = 0;
});
resetTrack(true);
}
public override bool OnExiting(IScreen next)
@ -535,9 +558,9 @@ namespace osu.Game.Screens.Edit
public override void OnSuspending(IScreen next)
{
refetchBeatmap();
base.OnSuspending(next);
clock.Stop();
refetchBeatmap();
}
private void refetchBeatmap()
@ -770,11 +793,7 @@ namespace osu.Game.Screens.Edit
return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
}
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState
{
Time = clock.CurrentTimeAccurate,
ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty
});
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap));
private void cancelExit()
{
@ -797,7 +816,7 @@ namespace osu.Game.Screens.Edit
pushEditorPlayer();
}
void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer()));
void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this));
}
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);

View File

@ -3,21 +3,30 @@
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Screens.Play;
namespace osu.Game.Screens.Edit
namespace osu.Game.Screens.Edit.GameplayTest
{
public class EditorPlayer : Player
{
public EditorPlayer()
: base(new PlayerConfiguration { ShowResults = false })
{
}
private readonly Editor editor;
private readonly EditorState editorState;
[Resolved]
private MusicController musicController { get; set; }
public EditorPlayer(Editor editor)
: base(new PlayerConfiguration { ShowResults = false })
{
this.editor = editor;
editorState = editor.GetState();
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
=> new MasterGameplayClockContainer(beatmap, editorState.Time, true);
protected override void LoadComplete()
{
base.LoadComplete();
@ -35,9 +44,22 @@ namespace osu.Game.Screens.Edit
protected override bool CheckModsAllowFailure() => false; // never fail.
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
// finish alpha transforms on entering to avoid gameplay starting in a half-hidden state.
// the finish calls are purposefully not propagated to children to avoid messing up their state.
FinishTransforms();
GameplayClockContainer.FinishTransforms(false, nameof(Alpha));
}
public override bool OnExiting(IScreen next)
{
musicController.Stop();
editorState.Time = GameplayClockContainer.CurrentTime;
editor.RestoreState(editorState);
return base.OnExiting(next);
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
namespace osu.Game.Screens.Edit.GameplayTest
{
public class EditorPlayerLoader : PlayerLoader
{
[Resolved]
private OsuLogo osuLogo { get; set; }
public EditorPlayerLoader(Editor editor)
: base(() => new EditorPlayer(editor))
{
}
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
MetadataInfo.FinishTransforms(true);
}
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
// call base with resuming forcefully set to true to reduce logo movements.
base.LogoArriving(logo, true);
logo.FinishTransforms(true, nameof(Scale));
}
protected override void ContentOut()
{
base.ContentOut();
osuLogo.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
}
protected override double PlayerPushDelay => 0;
}
}

View File

@ -5,7 +5,7 @@ using System;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Screens.Edit
namespace osu.Game.Screens.Edit.GameplayTest
{
public class SaveBeforeGameplayTestDialog : PopupDialog
{

View File

@ -0,0 +1,123 @@
// 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.Globalization;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
using osu.Game.Utils;
using osuTK;
namespace osu.Game.Screens.Edit.Timing
{
/// <summary>
/// Analogous to <see cref="SliderWithTextBoxInput{T}"/>, but supports scenarios
/// where multiple objects with multiple different property values are selected
/// by providing an "indeterminate state".
/// </summary>
public class IndeterminateSliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T?>
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
{
/// <summary>
/// A custom step value for each key press which actuates a change on this control.
/// </summary>
public float KeyboardStep
{
get => slider.KeyboardStep;
set => slider.KeyboardStep = value;
}
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
public Bindable<T?> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly SettingsSlider<T> slider;
private readonly LabelledTextBox textbox;
/// <summary>
/// Creates an <see cref="IndeterminateSliderWithTextBoxInput{T}"/>.
/// </summary>
/// <param name="labelText">The label text for the slider and text box.</param>
/// <param name="indeterminateValue">
/// Bindable to use for the slider until a non-null value is set for <see cref="Current"/>.
/// In particular, it can be used to control min/max bounds and precision in the case of <see cref="BindableNumber{T}"/>s.
/// </param>
public IndeterminateSliderWithTextBoxInput(LocalisableString labelText, Bindable<T> indeterminateValue)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
textbox = new LabelledTextBox
{
Label = labelText,
},
slider = new SettingsSlider<T>
{
TransferValueOnCommit = true,
RelativeSizeAxes = Axes.X,
Current = indeterminateValue
}
}
},
};
textbox.OnCommit += (t, isNew) =>
{
if (!isNew) return;
try
{
slider.Current.Parse(t.Text);
}
catch
{
// TriggerChange below will restore the previous text value on failure.
}
// This is run regardless of parsing success as the parsed number may not actually trigger a change
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
Current.TriggerChange();
};
slider.Current.BindValueChanged(val => Current.Value = val.NewValue);
Current.BindValueChanged(_ => updateState(), true);
}
private void updateState()
{
if (Current.Value is T nonNullValue)
{
slider.Current.Value = nonNullValue;
// use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly.
decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo);
textbox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
textbox.PlaceholderText = string.Empty;
}
else
{
textbox.Text = null;
textbox.PlaceholderText = "(multiple)";
}
}
}
}

View File

@ -107,7 +107,8 @@ namespace osu.Game.Screens.Play
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
{
RemoveFilters();
// Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep.
RemoveFilters(false);
OnComplete?.Invoke();
});
@ -137,15 +138,16 @@ namespace osu.Game.Screens.Play
Content.FadeColour(Color4.Gray, duration);
}
public void RemoveFilters()
public void RemoveFilters(bool resetTrackFrequency = true)
{
if (resetTrackFrequency)
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
if (filters.Parent == null)
return;
RemoveInternal(filters);
filters.Dispose();
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
}
protected override void Update()

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Threading.Tasks;
using JetBrains.Annotations;
using ManagedBass.Fx;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
@ -35,7 +36,9 @@ namespace osu.Game.Screens.Play
{
protected const float BACKGROUND_BLUR = 15;
private const double content_out_duration = 300;
protected const double CONTENT_OUT_DURATION = 300;
protected virtual double PlayerPushDelay => 1800;
public override bool HideOverlaysOnEnter => hideOverlays;
@ -67,6 +70,7 @@ namespace osu.Game.Screens.Play
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
private AudioFilter lowPassFilter;
private AudioFilter highPassFilter;
protected bool BackgroundBrightnessReduction
{
@ -168,7 +172,8 @@ namespace osu.Game.Screens.Play
},
idleTracker = new IdleTracker(750),
}),
lowPassFilter = new AudioFilter(audio.TrackMixer)
lowPassFilter = new AudioFilter(audio.TrackMixer),
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass)
};
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
@ -210,7 +215,7 @@ namespace osu.Game.Screens.Play
// after an initial delay, start the debounced load check.
// this will continue to execute even after resuming back on restart.
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + PlayerPushDelay, 0));
showMuteWarningIfNeeded();
showBatteryWarningIfNeeded();
@ -239,18 +244,19 @@ namespace osu.Game.Screens.Play
Beatmap.Value.Track.Stop();
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
highPassFilter.CutoffTo(0);
}
public override bool OnExiting(IScreen next)
{
cancelLoad();
contentOut();
ContentOut();
// If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed).
epilepsyWarning?.Hide();
// Ensure the screen doesn't expire until all the outwards fade operations have completed.
this.Delay(content_out_duration).FadeOut();
this.Delay(CONTENT_OUT_DURATION).FadeOut();
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
@ -266,9 +272,9 @@ namespace osu.Game.Screens.Play
const double duration = 300;
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In);
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint);
logo.ScaleTo(new Vector2(0.15f), duration, Easing.In);
logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint);
logo.FadeIn(350);
Scheduler.AddDelayed(() =>
@ -352,18 +358,20 @@ namespace osu.Game.Screens.Play
content.FadeInFromZero(400);
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in)
ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint));
}
private void contentOut()
protected virtual void ContentOut()
{
// Ensure the logo is no longer tracking before we scale the content
content.StopTracking();
content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint);
content.FadeOut(content_out_duration, Easing.OutQuint);
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration);
content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION);
highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION);
}
private void pushWhenLoaded()
@ -388,9 +396,9 @@ namespace osu.Game.Screens.Play
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
var consumedPlayer = consumePlayer();
contentOut();
ContentOut();
TransformSequence<PlayerLoader> pushSequence = this.Delay(content_out_duration);
TransformSequence<PlayerLoader> pushSequence = this.Delay(CONTENT_OUT_DURATION);
// only show if the warning was created (i.e. the beatmap needs it)
// and this is not a restart of the map (the warning expires after first load).
@ -412,7 +420,7 @@ namespace osu.Game.Screens.Play
else
{
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
this.TransformBindableTo(volumeAdjustment, 0, content_out_duration, Easing.OutCubic);
this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic);
}
pushSequence.Schedule(() =>

View File

@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play
colourNormal = colours.Yellow;
colourHover = colours.YellowDark;
sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection");
sampleConfirm = audio.Samples.Get(@"UI/submit-select");
Children = new Drawable[]
{

View File

@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel
RelativeSizeAxes = Axes.Both,
};
sampleHover = audio.Samples.Get("SongSelect/song-ping");
sampleHover = audio.Samples.Get("UI/default-hover");
}
public bool InsetForBorder

View File

@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
private double audioFeedbackLastPlaybackTime;
[Resolved]
private MusicController music { get; set; }
@ -435,6 +437,7 @@ namespace osu.Game.Screens.Select
}
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
private BeatmapInfo beatmapInfoPrevious;
private BeatmapInfo beatmapInfoNoDebounce;
private RulesetInfo rulesetNoDebounce;
@ -477,6 +480,21 @@ namespace osu.Game.Screens.Select
else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
if (beatmap != beatmapInfoPrevious)
{
if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50)
{
if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID)
sampleChangeDifficulty.Play();
else
sampleChangeBeatmap.Play();
audioFeedbackLastPlaybackTime = Time.Current;
}
beatmapInfoPrevious = beatmap;
}
void run()
{
// clear pending task immediately to track any potential nested debounce operation.
@ -508,18 +526,7 @@ namespace osu.Game.Screens.Select
if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
{
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
if (beatmap != null)
{
if (beatmap.BeatmapSetInfoID == lastSetID)
sampleChangeDifficulty.Play();
else
sampleChangeBeatmap.Play();
}
}
if (this.IsCurrentScreen())