1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 18:42:56 +08:00

Merge pull request #20157 from peppy/true-gameplay-rate

Refactor `TrueGameplayRate` to account for only gameplay adjustments, no matter what
This commit is contained in:
Dan Balasescu 2022-09-08 19:54:32 +09:00 committed by GitHub
commit 9aab502adc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 126 additions and 89 deletions

View File

@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
currentRotation += angle; currentRotation += angle;
// rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback
// (see: ModTimeRamp) // (see: ModTimeRamp)
drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate));
} }
private void resetState(DrawableHitObject obj) private void resetState(DrawableHitObject obj)

View File

@ -1,8 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -13,21 +14,20 @@ namespace osu.Game.Tests.NonVisual
{ {
[TestCase(0)] [TestCase(0)]
[TestCase(1)] [TestCase(1)]
public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate) public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRate)
{ {
var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate });
var gameplayClock = new TestGameplayClockContainer(framedClock); var gameplayClock = new TestGameplayClockContainer(framedClock);
Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0)); Assert.That(gameplayClock.GetTrueGameplayRate(), Is.EqualTo(2));
} }
private class TestGameplayClockContainer : GameplayClockContainer private class TestGameplayClockContainer : GameplayClockContainer
{ {
public override IEnumerable<double> NonGameplayAdjustments => new[] { 0.0 };
public TestGameplayClockContainer(IFrameBasedClock underlyingClock) public TestGameplayClockContainer(IFrameBasedClock underlyingClock)
: base(underlyingClock) : base(underlyingClock)
{ {
AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0));
} }
} }
} }

View File

@ -370,7 +370,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void confirmNoTrackAdjustments() private void confirmNoTrackAdjustments()
{ {
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); AddUntilStep("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value, () => Is.EqualTo(1));
} }
private void restart() => AddStep("restart", () => Player.Restart()); private void restart() => AddStep("restart", () => Player.Restart());

View File

@ -13,9 +13,11 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -332,6 +334,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000); AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType<DrawableRuleset>().Single().FrameStableClock.CurrentTime > 30000);
} }
[Test]
public void TestGameplayRateAdjust()
{
start(getPlayerIds(4), mods: new[] { new APIMod(new OsuModDoubleTime()) });
loadSpectateScreen();
sendFrames(getPlayerIds(4), 300);
AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5));
}
[Test] [Test]
public void TestPlayersLeaveWhileSpectating() public void TestPlayersLeaveWhileSpectating()
{ {
@ -420,7 +434,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
private void start(int[] userIds, int? beatmapId = null) private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null)
{ {
AddStep("start play", () => AddStep("start play", () =>
{ {
@ -429,10 +443,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
var user = new MultiplayerRoomUser(id) var user = new MultiplayerRoomUser(id)
{ {
User = new APIUser { Id = id }, User = new APIUser { Id = id },
Mods = mods ?? Array.Empty<APIMod>(),
}; };
OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true); OnlinePlayDependencies.MultiplayerClient.AddUser(user, true);
SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId); SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId, mods);
playingUsers.Add(user); playingUsers.Add(user);
} }

View File

@ -2,15 +2,13 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -263,27 +261,11 @@ namespace osu.Game.Rulesets.UI
public FrameTimeInfo TimeInfo => framedClock.TimeInfo; public FrameTimeInfo TimeInfo => framedClock.TimeInfo;
public double TrueGameplayRate
{
get
{
double baseRate = Rate;
foreach (double adjustment in NonGameplayAdjustments)
{
if (Precision.AlmostEquals(adjustment, 0))
return 0;
baseRate /= adjustment;
}
return baseRate;
}
}
public double StartTime => parentGameplayClock?.StartTime ?? 0; public double StartTime => parentGameplayClock?.StartTime ?? 0;
public IEnumerable<double> NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<double>(); private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments();
public IAdjustableAudioComponent AdjustmentsFromMods => parentGameplayClock?.AdjustmentsFromMods ?? gameplayAdjustments;
#endregion #endregion

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -13,6 +14,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary> /// </summary>
public class MultiSpectatorPlayer : SpectatorPlayer public class MultiSpectatorPlayer : SpectatorPlayer
{ {
/// <summary>
/// All adjustments applied to the clock of this <see cref="MultiSpectatorPlayer"/> which come from mods.
/// </summary>
public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods;
private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments();
private readonly SpectatorPlayerClock spectatorPlayerClock; private readonly SpectatorPlayerClock spectatorPlayerClock;
/// <summary> /// <summary>
@ -53,6 +60,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
} }
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
=> new GameplayClockContainer(spectatorPlayerClock); {
var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock);
clockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods);
return gameplayClockContainer;
}
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -43,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
[Resolved] [Resolved]
private MultiplayerClient multiplayerClient { get; set; } = null!; private MultiplayerClient multiplayerClient { get; set; } = null!;
private IAggregateAudioAdjustment? boundAdjustments;
private readonly PlayerArea[] instances; private readonly PlayerArea[] instances;
private MasterGameplayClockContainer masterClockContainer = null!; private MasterGameplayClockContainer masterClockContainer = null!;
private SpectatorSyncManager syncManager = null!; private SpectatorSyncManager syncManager = null!;
@ -157,6 +160,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
base.LoadComplete(); base.LoadComplete();
masterClockContainer.Reset(); masterClockContainer.Reset();
// Start with adjustments from the first player to keep a sane state.
bindAudioAdjustments(instances.First());
} }
protected override void Update() protected override void Update()
@ -169,11 +175,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
.OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)) .OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime))
.FirstOrDefault(); .FirstOrDefault();
// Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio.
if (currentAudioSource != null)
bindAudioAdjustments(currentAudioSource);
foreach (var instance in instances) foreach (var instance in instances)
instance.Mute = instance != currentAudioSource; instance.Mute = instance != currentAudioSource;
} }
} }
private void bindAudioAdjustments(PlayerArea first)
{
if (boundAdjustments != null)
masterClockContainer.AdjustmentsFromMods.UnbindAdjustments(boundAdjustments);
boundAdjustments = first.ClockAdjustmentsFromMods;
masterClockContainer.AdjustmentsFromMods.BindAdjustments(boundAdjustments);
}
private bool isCandidateAudioSource(SpectatorPlayerClock? clock) private bool isCandidateAudioSource(SpectatorPlayerClock? clock)
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames; => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames;

View File

@ -42,6 +42,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary> /// </summary>
public readonly SpectatorPlayerClock SpectatorPlayerClock; public readonly SpectatorPlayerClock SpectatorPlayerClock;
/// <summary>
/// The clock adjustments applied by the <see cref="Player"/> loaded in this area.
/// </summary>
public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods;
/// <summary> /// <summary>
/// The currently-loaded score. /// The currently-loaded score.
/// </summary> /// </summary>
@ -50,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
[Resolved] [Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!; private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments();
private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly BindableDouble volumeAdjustment = new BindableDouble();
private readonly Container gameplayContent; private readonly Container gameplayContent;
private readonly LoadingLayer loadingLayer; private readonly LoadingLayer loadingLayer;
@ -97,6 +103,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{ {
var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock);
player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); player.OnGameplayStarted += () => OnGameplayStarted?.Invoke();
clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods);
return player; return player;
})); }));

View File

@ -2,15 +2,13 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
@ -46,7 +44,7 @@ namespace osu.Game.Screens.Play
/// </remarks> /// </remarks>
public double StartTime { get; protected set; } public double StartTime { get; protected set; }
public virtual IEnumerable<double> NonGameplayAdjustments => Enumerable.Empty<double>(); public IAdjustableAudioComponent AdjustmentsFromMods { get; } = new AudioAdjustments();
private readonly BindableBool isPaused = new BindableBool(true); private readonly BindableBool isPaused = new BindableBool(true);
@ -196,7 +194,9 @@ namespace osu.Game.Screens.Play
void IAdjustableClock.Reset() => Reset(); void IAdjustableClock.Reset() => Reset();
public void ResetSpeedAdjustments() => throw new NotImplementedException(); public virtual void ResetSpeedAdjustments()
{
}
double IAdjustableClock.Rate double IAdjustableClock.Rate
{ {
@ -222,23 +222,5 @@ namespace osu.Game.Screens.Play
public double FramesPerSecond => GameplayClock.FramesPerSecond; public double FramesPerSecond => GameplayClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo;
public double TrueGameplayRate
{
get
{
double baseRate = Rate;
foreach (double adjustment in NonGameplayAdjustments)
{
if (Precision.AlmostEquals(adjustment, 0))
return 0;
baseRate /= adjustment;
}
return baseRate;
}
}
} }
} }

View File

@ -0,0 +1,24 @@
// 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;
namespace osu.Game.Screens.Play
{
public static class GameplayClockExtensions
{
/// <summary>
/// The rate of gameplay when playback is at 100%.
/// This excludes any seeking / user adjustments.
/// </summary>
public static double GetTrueGameplayRate(this IGameplayClock clock)
{
// To handle rewind, we still want to maintain the same direction as the underlying clock.
double rate = clock.Rate == 0 ? 1 : Math.Sign(clock.Rate);
return rate
* clock.AdjustmentsFromMods.AggregateFrequency.Value
* clock.AdjustmentsFromMods.AggregateTempo.Value;
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -9,12 +9,6 @@ namespace osu.Game.Screens.Play
{ {
public interface IGameplayClock : IFrameBasedClock public interface IGameplayClock : IFrameBasedClock
{ {
/// <summary>
/// The rate of gameplay when playback is at 100%.
/// This excludes any seeking / user adjustments.
/// </summary>
double TrueGameplayRate { get; }
/// <summary> /// <summary>
/// The time from which the clock should start. Will be seeked to on calling <see cref="GameplayClockContainer.Reset"/>. /// The time from which the clock should start. Will be seeked to on calling <see cref="GameplayClockContainer.Reset"/>.
/// </summary> /// </summary>
@ -25,9 +19,9 @@ namespace osu.Game.Screens.Play
double StartTime { get; } double StartTime { get; }
/// <summary> /// <summary>
/// All adjustments applied to this clock which don't come from gameplay or mods. /// All adjustments applied to this clock which come from mods.
/// </summary> /// </summary>
IEnumerable<double> NonGameplayAdjustments { get; } IAdjustableAudioComponent AdjustmentsFromMods { get; }
IBindable<bool> IsPaused { get; } IBindable<bool> IsPaused { get; }
} }

View File

@ -2,8 +2,8 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
@ -41,9 +42,9 @@ namespace osu.Game.Screens.Play
private readonly WorkingBeatmap beatmap; private readonly WorkingBeatmap beatmap;
private readonly double skipTargetTime; private readonly Track track;
private readonly List<Bindable<double>> nonGameplayAdjustments = new List<Bindable<double>>(); private readonly double skipTargetTime;
/// <summary> /// <summary>
/// Stores the time at which the last <see cref="StopGameplayClock"/> call was triggered. /// Stores the time at which the last <see cref="StopGameplayClock"/> call was triggered.
@ -56,7 +57,8 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
private double? actualStopTime; private double? actualStopTime;
public override IEnumerable<double> NonGameplayAdjustments => nonGameplayAdjustments.Select(b => b.Value); [Resolved]
private MusicController musicController { get; set; } = null!;
/// <summary> /// <summary>
/// Create a new master gameplay clock container. /// Create a new master gameplay clock container.
@ -69,6 +71,8 @@ namespace osu.Game.Screens.Play
this.beatmap = beatmap; this.beatmap = beatmap;
this.skipTargetTime = skipTargetTime; this.skipTargetTime = skipTargetTime;
track = beatmap.Track;
StartTime = findEarliestStartTime(); StartTime = findEarliestStartTime();
} }
@ -195,15 +199,12 @@ namespace osu.Game.Screens.Play
if (speedAdjustmentsApplied) if (speedAdjustmentsApplied)
return; return;
if (SourceClock is not Track track) musicController.ResetTrackAdjustments();
return;
track.BindAdjustments(AdjustmentsFromMods);
track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
nonGameplayAdjustments.Add(GameplayClock.ExternalPauseFrequencyAdjust);
nonGameplayAdjustments.Add(UserPlaybackRate);
speedAdjustmentsApplied = true; speedAdjustmentsApplied = true;
} }
@ -212,15 +213,10 @@ namespace osu.Game.Screens.Play
if (!speedAdjustmentsApplied) if (!speedAdjustmentsApplied)
return; return;
if (SourceClock is not Track track) track.UnbindAdjustments(AdjustmentsFromMods);
return;
track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust);
track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
nonGameplayAdjustments.Remove(GameplayClock.ExternalPauseFrequencyAdjust);
nonGameplayAdjustments.Remove(UserPlaybackRate);
speedAdjustmentsApplied = false; speedAdjustmentsApplied = false;
} }

View File

@ -996,12 +996,8 @@ namespace osu.Game.Screens.Play
foreach (var mod in GameplayState.Mods.OfType<IApplicableToHUD>()) foreach (var mod in GameplayState.Mods.OfType<IApplicableToHUD>())
mod.ApplyToHUD(HUDOverlay); mod.ApplyToHUD(HUDOverlay);
// Our mods are local copies of the global mods so they need to be re-applied to the track.
// This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack.
// Todo: In the future, player will receive in a track and will probably not have to worry about this...
musicController.ResetTrackAdjustments();
foreach (var mod in GameplayState.Mods.OfType<IApplicableToTrack>()) foreach (var mod in GameplayState.Mods.OfType<IApplicableToTrack>())
mod.ApplyToTrack(musicController.CurrentTrack); mod.ApplyToTrack(GameplayClockContainer.AdjustmentsFromMods);
updateGameplayState(); updateGameplayState();

View File

@ -81,13 +81,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void Disconnect() => isConnected.Value = false; public void Disconnect() => isConnected.Value = false;
public MultiplayerRoomUser AddUser(APIUser user, bool markAsPlaying = false) public MultiplayerRoomUser AddUser(APIUser user, bool markAsPlaying = false)
{ => AddUser(new MultiplayerRoomUser(user.Id) { User = user }, markAsPlaying);
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
public MultiplayerRoomUser AddUser(MultiplayerRoomUser roomUser, bool markAsPlaying = false)
{
addUser(roomUser); addUser(roomUser);
if (markAsPlaying) if (markAsPlaying)
PlayingUserIds.Add(user.Id); PlayingUserIds.Add(roomUser.UserID);
return roomUser; return roomUser;
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -37,6 +38,7 @@ namespace osu.Game.Tests.Visual.Spectator
private readonly Dictionary<int, ReplayFrame> lastReceivedUserFrames = new Dictionary<int, ReplayFrame>(); private readonly Dictionary<int, ReplayFrame> lastReceivedUserFrames = new Dictionary<int, ReplayFrame>();
private readonly Dictionary<int, int> userBeatmapDictionary = new Dictionary<int, int>(); private readonly Dictionary<int, int> userBeatmapDictionary = new Dictionary<int, int>();
private readonly Dictionary<int, APIMod[]> userModsDictionary = new Dictionary<int, APIMod[]>();
private readonly Dictionary<int, int> userNextFrameDictionary = new Dictionary<int, int>(); private readonly Dictionary<int, int> userNextFrameDictionary = new Dictionary<int, int>();
[Resolved] [Resolved]
@ -52,9 +54,11 @@ namespace osu.Game.Tests.Visual.Spectator
/// </summary> /// </summary>
/// <param name="userId">The user to start play for.</param> /// <param name="userId">The user to start play for.</param>
/// <param name="beatmapId">The playing beatmap id.</param> /// <param name="beatmapId">The playing beatmap id.</param>
public void SendStartPlay(int userId, int beatmapId) /// <param name="mods">The mods the user has applied.</param>
public void SendStartPlay(int userId, int beatmapId, APIMod[]? mods = null)
{ {
userBeatmapDictionary[userId] = beatmapId; userBeatmapDictionary[userId] = beatmapId;
userModsDictionary[userId] = mods ?? Array.Empty<APIMod>();
userNextFrameDictionary[userId] = 0; userNextFrameDictionary[userId] = 0;
sendPlayingState(userId); sendPlayingState(userId);
} }
@ -73,10 +77,12 @@ namespace osu.Game.Tests.Visual.Spectator
{ {
BeatmapID = userBeatmapDictionary[userId], BeatmapID = userBeatmapDictionary[userId],
RulesetID = 0, RulesetID = 0,
Mods = userModsDictionary[userId],
State = state State = state
}); });
userBeatmapDictionary.Remove(userId); userBeatmapDictionary.Remove(userId);
userModsDictionary.Remove(userId);
} }
/// <summary> /// <summary>
@ -125,6 +131,7 @@ namespace osu.Game.Tests.Visual.Spectator
// Track the local user's playing beatmap ID. // Track the local user's playing beatmap ID.
Debug.Assert(state.BeatmapID != null); Debug.Assert(state.BeatmapID != null);
userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value; userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value;
userModsDictionary[api.LocalUser.Value.Id] = state.Mods.ToArray();
return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state);
} }
@ -158,6 +165,7 @@ namespace osu.Game.Tests.Visual.Spectator
{ {
BeatmapID = userBeatmapDictionary[userId], BeatmapID = userBeatmapDictionary[userId],
RulesetID = 0, RulesetID = 0,
Mods = userModsDictionary[userId],
State = SpectatedUserState.Playing State = SpectatedUserState.Playing
}); });
} }