Merge branch 'master' into player-mod-reinstantiation

This commit is contained in:
Dean Herbert
2019-04-16 22:35:21 +08:00
committed by GitHub
60 changed files with 161 additions and 127 deletions

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@ -110,7 +109,7 @@ namespace osu.Game.Screens.Multi.Match.Components
},
};
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty<Mod>(), true);
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty<Mod>(), true);
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
}

View File

@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -179,7 +179,7 @@ namespace osu.Game.Screens.Multi.Match
{
RoomManager?.PartRoom();
SelectedMods.Value = Enumerable.Empty<Mod>();
Mods.Value = Array.Empty<Mod>();
return base.OnExiting(next);
}
@ -193,7 +193,7 @@ namespace osu.Game.Screens.Multi.Match
var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
SelectedMods.Value = e.NewValue?.RequiredMods ?? Enumerable.Empty<Mod>();
Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
if (e.NewValue?.Ruleset != null)
Ruleset.Value = e.NewValue.Ruleset;
}

View File

@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Play
if (ruleset.Value.ID != playlistItem.Ruleset.ID)
throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset");
if (!playlistItem.RequiredMods.All(m => SelectedMods.Value.Contains(m)))
if (!playlistItem.RequiredMods.All(m => Mods.Value.Contains(m)))
throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods");
var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID);

View File

@ -65,7 +65,7 @@ namespace osu.Game.Screens
public Bindable<RulesetInfo> Ruleset { get; private set; }
public Bindable<IEnumerable<Mod>> SelectedMods { get; private set; }
public Bindable<IReadOnlyList<Mod>> Mods { get; private set; }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@ -73,7 +73,7 @@ namespace osu.Game.Screens
Beatmap = screenDependencies.Beatmap;
Ruleset = screenDependencies.Ruleset;
SelectedMods = screenDependencies.SelectedMods;
Mods = screenDependencies.Mods;
return base.CreateChildDependencies(screenDependencies);
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens
public Bindable<RulesetInfo> Ruleset { get; }
public Bindable<IEnumerable<Mod>> SelectedMods { get; }
public Bindable<IReadOnlyList<Mod>> Mods { get; }
public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent)
: base(parent)
@ -31,15 +31,15 @@ namespace osu.Game.Screens
if (Ruleset == null)
Cache(Ruleset = parent.Get<Bindable<RulesetInfo>>().BeginLease(true));
SelectedMods = parent.Get<LeasedBindable<IEnumerable<Mod>>>()?.GetBoundCopy();
if (SelectedMods == null)
Cache(SelectedMods = parent.Get<Bindable<IEnumerable<Mod>>>().BeginLease(true));
Mods = parent.Get<LeasedBindable<IReadOnlyList<Mod>>>()?.GetBoundCopy();
if (Mods == null)
Cache(Mods = parent.Get<Bindable<IReadOnlyList<Mod>>>().BeginLease(true));
}
else
{
Beatmap = (parent.Get<LeasedBindable<WorkingBeatmap>>() ?? parent.Get<Bindable<WorkingBeatmap>>()).GetBoundCopy();
Ruleset = (parent.Get<LeasedBindable<RulesetInfo>>() ?? parent.Get<Bindable<RulesetInfo>>()).GetBoundCopy();
SelectedMods = (parent.Get<LeasedBindable<IEnumerable<Mod>>>() ?? parent.Get<Bindable<IEnumerable<Mod>>>()).GetBoundCopy();
Mods = (parent.Get<LeasedBindable<IReadOnlyList<Mod>>>() ?? parent.Get<Bindable<IReadOnlyList<Mod>>>()).GetBoundCopy();
}
}
}

View File

@ -23,7 +23,8 @@ namespace osu.Game.Screens.Play
/// </summary>
public class GameplayClockContainer : Container
{
private readonly IEnumerable<Mod> mods;
private readonly WorkingBeatmap beatmap;
private readonly IReadOnlyList<Mod> mods;
/// <summary>
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
@ -37,6 +38,8 @@ namespace osu.Game.Screens.Play
/// </summary>
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
private readonly double gameplayStartTime;
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
{
Default = 1,
@ -53,11 +56,15 @@ namespace osu.Game.Screens.Play
private Bindable<double> userAudioOffset;
private readonly FramedOffsetClock offsetClock;
private readonly FramedOffsetClock userOffsetClock;
public GameplayClockContainer(WorkingBeatmap beatmap, IEnumerable<Mod> mods, double gameplayStartTime)
private readonly FramedOffsetClock platformOffsetClock;
public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList<Mod> mods, double gameplayStartTime)
{
this.beatmap = beatmap;
this.mods = mods;
this.gameplayStartTime = gameplayStartTime;
RelativeSizeAxes = Axes.Both;
@ -65,30 +72,31 @@ namespace osu.Game.Screens.Play
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn));
adjustableClock.ProcessFrame();
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
// the final usable gameplay clock with user-set offsets applied.
offsetClock = new FramedOffsetClock(platformOffsetClock);
userOffsetClock = new FramedOffsetClock(platformOffsetClock);
// the clock to be exposed via DI to children.
GameplayClock = new GameplayClock(offsetClock);
GameplayClock = new GameplayClock(userOffsetClock);
GameplayClock.IsPaused.BindTo(IsPaused);
}
private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true);
userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true);
UserPlaybackRate.ValueChanged += _ => updateRate();
Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn));
adjustableClock.ProcessFrame();
}
public void Restart()
@ -105,9 +113,7 @@ namespace osu.Game.Screens.Play
this.Delay(750).Schedule(() =>
{
if (!IsPaused.Value)
{
adjustableClock.Start();
}
Start();
});
});
});
@ -122,7 +128,19 @@ namespace osu.Game.Screens.Play
IsPaused.Value = false;
}
public void Seek(double time) => adjustableClock.Seek(time);
/// <summary>
/// Seek to a specific time in gameplay.
/// <remarks>
/// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track).
/// </remarks>
/// </summary>
/// <param name="time">The destination time to seek to.</param>
public void Seek(double time)
{
// remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track.
// we may want to consider reversing the application of offsets in the future as it may feel more correct.
adjustableClock.Seek(time - totalOffset);
}
public void Stop()
{
@ -139,7 +157,7 @@ namespace osu.Game.Screens.Play
protected override void Update()
{
if (!IsPaused.Value)
offsetClock.ProcessFrame();
userOffsetClock.ProcessFrame();
base.Update();
}

View File

@ -18,15 +18,15 @@ using osu.Game.Graphics;
namespace osu.Game.Screens.Play.HUD
{
public class ModDisplay : Container, IHasCurrentValue<IEnumerable<Mod>>
public class ModDisplay : Container, IHasCurrentValue<IReadOnlyList<Mod>>
{
private const int fade_duration = 1000;
public bool DisplayUnrankedText = true;
private readonly Bindable<IEnumerable<Mod>> current = new Bindable<IEnumerable<Mod>>();
private readonly Bindable<IReadOnlyList<Mod>> current = new Bindable<IReadOnlyList<Mod>>();
public Bindable<IEnumerable<Mod>> Current
public Bindable<IReadOnlyList<Mod>> Current
{
get => current;
set

View File

@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play
public Action<double> RequestSeek;
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IEnumerable<Mod> mods)
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
{
RelativeSizeAxes = Axes.Both;

View File

@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
InternalChild = GameplayClockContainer = new GameplayClockContainer(working, SelectedMods.Value, DrawableRuleset.GameplayStartTime);
InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime);
GameplayClockContainer.Children = new[]
{
@ -130,7 +130,7 @@ namespace osu.Game.Screens.Play
},
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, SelectedMods.Value)
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value)
{
HoldToQuit = { Action = performUserRequestedExit },
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } },
@ -177,7 +177,7 @@ namespace osu.Game.Screens.Play
ScoreProcessor.AllJudged += onCompletion;
ScoreProcessor.Failed += onFail;
foreach (var mod in SelectedMods.Value.OfType<IApplicableToScoreProcessor>())
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
}
@ -199,7 +199,7 @@ namespace osu.Game.Screens.Play
try
{
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, SelectedMods.Value);
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value);
}
catch (BeatmapInvalidForRulesetException)
{
@ -207,7 +207,7 @@ namespace osu.Game.Screens.Play
// let's try again forcing the beatmap's ruleset.
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, SelectedMods.Value);
DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value);
}
if (!DrawableRuleset.Objects.Any())
@ -278,7 +278,7 @@ namespace osu.Game.Screens.Play
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = ruleset,
Mods = SelectedMods.Value.ToArray(),
Mods = Mods.Value.ToArray(),
User = api.LocalUser.Value,
};
@ -332,7 +332,7 @@ namespace osu.Game.Screens.Play
private bool onFail()
{
if (SelectedMods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
if (Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false;
GameplayClockContainer.Stop();

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
info = new BeatmapMetadataDisplay(Beatmap.Value, SelectedMods.Value)
info = new BeatmapMetadataDisplay(Beatmap.Value, Mods.Value)
{
Alpha = 0,
Anchor = Anchor.Centre,
@ -301,7 +301,7 @@ namespace osu.Game.Screens.Play
}
private readonly WorkingBeatmap beatmap;
private readonly IEnumerable<Mod> mods;
private readonly IReadOnlyList<Mod> mods;
private LoadingAnimation loading;
private Sprite backgroundSprite;
private ModDisplay modDisplay;
@ -323,7 +323,7 @@ namespace osu.Game.Screens.Play
}
}
public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IEnumerable<Mod> mods)
public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
{
this.beatmap = beatmap;
this.mods = mods;

View File

@ -310,12 +310,12 @@ namespace osu.Game.Screens.Select
try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Enumerable.Empty<Mod>());
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Enumerable.Empty<Mod>());
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}
labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -42,7 +41,7 @@ namespace osu.Game.Screens.Select
RulesetID = Ruleset.Value.ID ?? 0
};
item.RequiredMods.AddRange(SelectedMods.Value);
item.RequiredMods.AddRange(Mods.Value);
Selected?.Invoke(item);
@ -61,12 +60,12 @@ namespace osu.Game.Screens.Select
{
Ruleset.Value = CurrentItem.Value.Ruleset;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
SelectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty<Mod>();
Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
}
Beatmap.Disabled = true;
Ruleset.Disabled = true;
SelectedMods.Disabled = true;
Mods.Disabled = true;
return false;
}
@ -77,7 +76,7 @@ namespace osu.Game.Screens.Select
Beatmap.Disabled = false;
Ruleset.Disabled = false;
SelectedMods.Disabled = false;
Mods.Disabled = false;
}
}
}

View File

@ -52,10 +52,10 @@ namespace osu.Game.Screens.Select
var auto = Ruleset.Value.CreateInstance().GetAutoplayMod();
var autoType = auto.GetType();
var mods = SelectedMods.Value;
var mods = Mods.Value;
if (mods.All(m => m.GetType() != autoType))
{
SelectedMods.Value = mods.Append(auto);
Mods.Value = mods.Append(auto).ToArray();
removeAutoModOnResume = true;
}
}

View File

@ -84,8 +84,8 @@ namespace osu.Game.Screens.Select
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
[Cached]
[Cached(Type = typeof(IBindable<IEnumerable<Mod>>))]
private readonly Bindable<IEnumerable<Mod>> selectedMods = new Bindable<IEnumerable<Mod>>(Enumerable.Empty<Mod>()); // Bound to the game's mods, but is not reset on exiting
[Cached(Type = typeof(IBindable<IReadOnlyList<Mod>>))]
private readonly Bindable<IReadOnlyList<Mod>> mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>()); // Bound to the game's mods, but is not reset on exiting
protected SongSelect()
{
@ -219,7 +219,7 @@ namespace osu.Game.Screens.Select
[BackgroundDependencyLoader(true)]
private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins)
{
selectedMods.BindTo(SelectedMods);
mods.BindTo(Mods);
if (Footer != null)
{
@ -394,7 +394,7 @@ namespace osu.Game.Screens.Select
{
Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\"");
selectedMods.Value = Enumerable.Empty<Mod>();
mods.Value = Array.Empty<Mod>();
decoupledRuleset.Value = ruleset;
// force a filter before attempting to change the beatmap.
@ -529,8 +529,8 @@ namespace osu.Game.Screens.Select
if (Beatmap.Value.Track != null)
Beatmap.Value.Track.Looping = false;
selectedMods.UnbindAll();
SelectedMods.Value = Enumerable.Empty<Mod>();
mods.UnbindAll();
Mods.Value = Array.Empty<Mod>();
return false;
}