1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 09:43:10 +08:00

Merge branch 'master' into inheritable-allow-track-adjust

This commit is contained in:
Dean Herbert 2021-09-15 22:26:13 +09:00 committed by GitHub
commit 931e873a7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 262 additions and 110 deletions

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
c.Add(CreateHitObject().With(h => c.Add(CreateHitObject().With(h =>
{ {
h.HitObject.StartTime = START_TIME; h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange; h.AccentColour.Value = Color4.Orange;
})); }));
}) })
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
c.Add(CreateHitObject().With(h => c.Add(CreateHitObject().With(h =>
{ {
h.HitObject.StartTime = START_TIME; h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange; h.AccentColour.Value = Color4.Orange;
})); }));
}) })

View File

@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
/// </summary> /// </summary>
public abstract class ManiaSkinnableTestScene : SkinnableTestScene public abstract class ManiaSkinnableTestScene : SkinnableTestScene
{ {
protected const double START_TIME = 1000000000;
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>(); public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction; IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000); IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(5000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm(); IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
}
private class ZeroScrollAlgorithm : IScrollAlgorithm
{
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
=> double.MinValue;
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
=> scrollLength;
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
=> (float)((time - START_TIME) / timeRange) * scrollLength;
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
=> 0;
public void Reset()
{
}
} }
} }
} }

View File

@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.7568168283591499d, "diffcalc-test")] [TestCase(6.6634445062299665d, "diffcalc-test")]
[TestCase(1.0348244046058293d, "zero-length-sliders")] [TestCase(1.0414203870195022d, "zero-length-sliders")]
public void Test(double expected, string name) public void Test(double expected, string name)
=> base.Test(expected, name); => base.Test(expected, name);
[TestCase(8.4783236764532557d, "diffcalc-test")] [TestCase(8.3858089051603368d, "diffcalc-test")]
[TestCase(1.2708532136987165d, "zero-length-sliders")] [TestCase(1.2723279173428435d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime()); => Test(expected, name, new OsuModDoubleTime());

View File

@ -34,7 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1);
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
HitWindows hitWindows = new OsuHitWindows(); HitWindows hitWindows = new OsuHitWindows();
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);

View File

@ -55,6 +55,59 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("stack empty", () => Stack.CurrentScreen == null); AddAssert("stack empty", () => Stack.CurrentScreen == null);
} }
[Test]
public void TestClockPositionPreservedBetweenSwitches()
{
BeatmapInfo targetDifficulty = null;
AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000));
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000);
AddStep("exit editor", () => Stack.Exit());
// ensure editor loader didn't resume.
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test]
public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset)
{
BeatmapInfo targetDifficulty = null;
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
AddStep("copy object", () => Editor.Copy());
AddStep("set target difficulty", () =>
{
targetDifficulty = sameRuleset
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
});
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
AddStep("paste object", () => Editor.Paste());
if (sameRuleset)
AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any());
else
AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any());
AddStep("exit editor", () => Stack.Exit());
if (sameRuleset)
{
AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction());
}
// ensure editor loader didn't resume.
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test] [Test]
public void TestPreventSwitchDueToUnsavedChanges() public void TestPreventSwitchDueToUnsavedChanges()
{ {
@ -118,7 +171,7 @@ namespace osu.Game.Tests.Visual.Editing
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty) private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
{ {
AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke()));
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true);
} }
} }
} }

View File

@ -54,7 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{ {
Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay }) Recorder = recorder = new TestReplayRecorder(new Score
{
Replay = replay,
ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
})
{ {
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
}, },

View File

@ -45,7 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{ {
Recorder = new TestReplayRecorder(new Score { Replay = replay }) Recorder = new TestReplayRecorder(new Score
{
Replay = replay,
ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
})
{ {
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
}, },

View File

@ -354,7 +354,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestReplayRecorder : ReplayRecorder<TestAction> internal class TestReplayRecorder : ReplayRecorder<TestAction>
{ {
public TestReplayRecorder() public TestReplayRecorder()
: base(new Score()) : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } })
{ {
} }

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -82,7 +83,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
public void TestJoinRoomWithPassword() public void TestJoinRoomWithIncorrectPassword()
{
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible);
}
[Test]
public void TestJoinRoomWithCorrectPassword()
{ {
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
SelectedRoom.Value = new Room(); SelectedRoom.Value = new Room();
Child = new MultiplayerMatchFooter Child = new Container
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Height = 50 RelativeSizeAxes = Axes.X,
Height = 50,
Child = new MultiplayerMatchFooter()
}; };
}); });
} }

View File

@ -38,6 +38,33 @@ namespace osu.Game.Extensions
return repeatDelegate; return repeatDelegate;
} }
/// <summary>
/// Shakes this drawable.
/// </summary>
/// <param name="target">The target to shake.</param>
/// <param name="shakeDuration">The length of a single shake.</param>
/// <param name="shakeMagnitude">Pixels of displacement per shake.</param>
/// <param name="maximumLength">The maximum length the shake should last.</param>
public static void Shake(this Drawable target, double shakeDuration = 80, float shakeMagnitude = 8, double? maximumLength = null)
{
// if we don't have enough time, don't bother shaking.
if (maximumLength < shakeDuration * 2)
return;
var sequence = target.MoveToX(shakeMagnitude, shakeDuration / 2, Easing.OutSine).Then()
.MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then();
// if we don't have enough time for the second shake, skip it.
if (!maximumLength.HasValue || maximumLength >= shakeDuration * 4)
{
sequence = sequence
.MoveToX(shakeMagnitude, shakeDuration, Easing.InOutSine).Then()
.MoveToX(-shakeMagnitude, shakeDuration, Easing.InOutSine).Then();
}
sequence.MoveToX(0, shakeDuration / 2, Easing.InSine);
}
/// <summary> /// <summary>
/// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position. /// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position.
/// </summary> /// </summary>

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Extensions;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
@ -16,40 +16,10 @@ namespace osu.Game.Graphics.Containers
/// </summary> /// </summary>
public float ShakeDuration = 80; public float ShakeDuration = 80;
/// <summary>
/// Total number of shakes. May be shortened if possible.
/// </summary>
public float TotalShakes = 4;
/// <summary>
/// Pixels of displacement per shake.
/// </summary>
public float ShakeMagnitude = 8;
/// <summary> /// <summary>
/// Shake the contents of this container. /// Shake the contents of this container.
/// </summary> /// </summary>
/// <param name="maximumLength">The maximum length the shake should last.</param> /// <param name="maximumLength">The maximum length the shake should last.</param>
public void Shake(double? maximumLength = null) public void Shake(double? maximumLength = null) => this.Shake(ShakeDuration, maximumLength: maximumLength);
{
const float shake_amount = 8;
// if we don't have enough time, don't bother shaking.
if (maximumLength < ShakeDuration * 2)
return;
var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then()
.MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
// if we don't have enough time for the second shake, skip it.
if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4)
{
sequence = sequence
.MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then()
.MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then();
}
sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine);
}
} }
} }

View File

@ -179,11 +179,7 @@ namespace osu.Game.Online.Rooms
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded(); Status.Value = new RoomStatusEnded();
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, other.RemoveExpiredPlaylistItems();
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
if (!(Status.Value is RoomStatusEnded))
other.Playlist.RemoveAll(i => i.Expired);
if (!Playlist.SequenceEqual(other.Playlist)) if (!Playlist.SequenceEqual(other.Playlist))
{ {
@ -200,6 +196,15 @@ namespace osu.Game.Online.Rooms
Position.Value = other.Position.Value; Position.Value = other.Position.Value;
} }
public void RemoveExpiredPlaylistItems()
{
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
if (!(Status.Value is RoomStatusEnded))
Playlist.RemoveAll(i => i.Expired);
}
public bool ShouldSerializeRoomID() => false; public bool ShouldSerializeRoomID() => false;
public bool ShouldSerializeHost() => false; public bool ShouldSerializeHost() => false;
public bool ShouldSerializeEndDate() => false; public bool ShouldSerializeEndDate() => false;

View File

@ -15,8 +15,6 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Replays.Legacy; using osu.Game.Replays.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -46,15 +44,8 @@ namespace osu.Game.Online.Spectator
private readonly BindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>(); private readonly BindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
private IBeatmap? currentBeatmap; private IBeatmap? currentBeatmap;
private Score? currentScore; private Score? currentScore;
[Resolved]
private IBindable<RulesetInfo> currentRuleset { get; set; } = null!;
[Resolved]
private IBindable<IReadOnlyList<Mod>> currentMods { get; set; } = null!;
private readonly SpectatorState currentState = new SpectatorState(); private readonly SpectatorState currentState = new SpectatorState();
/// <summary> /// <summary>
@ -153,9 +144,9 @@ namespace osu.Game.Online.Spectator
IsPlaying = true; IsPlaying = true;
// transfer state at point of beginning play // transfer state at point of beginning play
currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; currentState.BeatmapID = score.ScoreInfo.Beatmap.OnlineBeatmapID;
currentState.RulesetID = currentRuleset.Value.ID; currentState.RulesetID = score.ScoreInfo.RulesetID;
currentState.Mods = currentMods.Value.Select(m => new APIMod(m)); currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
currentBeatmap = beatmap.PlayableBeatmap; currentBeatmap = beatmap.PlayableBeatmap;
currentScore = score; currentScore = score;

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose
public bool OnPressed(PlatformAction action) public bool OnPressed(PlatformAction action)
{ {
if (action == PlatformAction.Copy) if (action == PlatformAction.Copy)
host.GetClipboard().SetText(formatSelectionAsString()); host.GetClipboard()?.SetText(formatSelectionAsString());
return false; return false;
} }

View File

@ -317,6 +317,16 @@ namespace osu.Game.Screens.Edit
/// </summary> /// </summary>
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
/// <summary>
/// Restore the editor to a provided state.
/// </summary>
/// <param name="state">The state to restore.</param>
public void RestoreState([NotNull] EditorState state) => Schedule(() =>
{
clock.Seek(state.Time);
clipboard.Value = state.ClipboardContent;
});
protected void Save() protected void Save()
{ {
// no longer new after first user-triggered save. // no longer new after first user-triggered save.
@ -740,7 +750,11 @@ namespace osu.Game.Screens.Edit
return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
} }
protected void SwitchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState
{
Time = clock.CurrentTimeAccurate,
ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty
});
private void cancelExit() => loader?.CancelPendingDifficultySwitch(); private void cancelExit() => loader?.CancelPendingDifficultySwitch();

View File

@ -20,6 +20,13 @@ namespace osu.Game.Screens.Edit
/// </summary> /// </summary>
public class EditorLoader : ScreenWithBeatmapBackground public class EditorLoader : ScreenWithBeatmapBackground
{ {
/// <summary>
/// The stored state from the last editor opened.
/// This will be read by the next editor instance to be opened to restore any relevant previous state.
/// </summary>
[CanBeNull]
private EditorState state;
public override float BackgroundParallaxAmount => 0.1f; public override float BackgroundParallaxAmount => 0.1f;
public override bool AllowBackButton => false; public override bool AllowBackButton => false;
@ -61,7 +68,7 @@ namespace osu.Game.Screens.Edit
} }
} }
public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState)
{ {
scheduledDifficultySwitch?.Cancel(); scheduledDifficultySwitch?.Cancel();
ValidForResume = true; ValidForResume = true;
@ -70,7 +77,8 @@ namespace osu.Game.Screens.Edit
scheduledDifficultySwitch = Schedule(() => scheduledDifficultySwitch = Schedule(() =>
{ {
Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap);
state = editorState;
// This screen is a weird exception to the rule that nothing after song select changes the global beatmap. // This screen is a weird exception to the rule that nothing after song select changes the global beatmap.
// Because of this, we need to update the background stack's beatmap to match. // Because of this, we need to update the background stack's beatmap to match.
@ -83,7 +91,13 @@ namespace osu.Game.Screens.Edit
private void pushEditor() private void pushEditor()
{ {
this.Push(CreateEditor()); var editor = CreateEditor();
this.Push(editor);
if (state != null)
editor.RestoreState(state);
ValidForResume = false; ValidForResume = false;
} }

View File

@ -0,0 +1,23 @@
// 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
namespace osu.Game.Screens.Edit
{
/// <summary>
/// Structure used to convey the general state of an <see cref="Editor"/> instance.
/// </summary>
public class EditorState
{
/// <summary>
/// The current audio time.
/// </summary>
public double Time { get; set; }
/// <summary>
/// The editor clipboard content.
/// </summary>
public string ClipboardContent { get; set; } = string.Empty;
}
}

View File

@ -57,7 +57,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
} }
foreach (var incoming in result) foreach (var incoming in result)
{
incoming.RemoveExpiredPlaylistItems();
RoomManager.AddOrUpdateRoom(incoming); RoomManager.AddOrUpdateRoom(incoming);
}
initialRoomsReceived.Value = true; initialRoomsReceived.Value = true;
tcs.SetResult(true); tcs.SetResult(true);

View File

@ -87,9 +87,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
currentJoinRoomRequest.Failure += exception => currentJoinRoomRequest.Failure += exception =>
{ {
if (!(exception is OperationCanceledException)) if (exception is OperationCanceledException)
Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); return;
onError?.Invoke(exception.ToString());
onError?.Invoke(exception.Message);
}; };
api.Queue(currentJoinRoomRequest); api.Queue(currentJoinRoomRequest);

View File

@ -39,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
pollReq.Success += result => pollReq.Success += result =>
{ {
result.RemoveExpiredPlaylistItems();
RoomManager.AddOrUpdateRoom(result); RoomManager.AddOrUpdateRoom(result);
tcs.SetResult(true); tcs.SetResult(true);
}; };

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
@ -15,6 +14,9 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
@ -120,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
} }
} }
public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join }; public Popover GetPopover() => new PasswordEntryPopover(Room);
public MenuItem[] ContextMenuItems => new MenuItem[] public MenuItem[] ContextMenuItems => new MenuItem[]
{ {
@ -176,7 +178,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{ {
private readonly Room room; private readonly Room room;
public Action<Room, string> JoinRequested; [Resolved(canBeNull: true)]
private LoungeSubScreen lounge { get; set; }
public PasswordEntryPopover(Room room) public PasswordEntryPopover(Room room)
{ {
@ -184,30 +187,61 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
} }
private OsuPasswordTextBox passwordTextbox; private OsuPasswordTextBox passwordTextbox;
private TriangleButton joinButton;
private OsuSpriteText errorText;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuColour colours)
{ {
Child = new FillFlowContainer Child = new FillFlowContainer
{ {
Margin = new MarginPadding(10), Margin = new MarginPadding(10),
Spacing = new Vector2(5), Spacing = new Vector2(5),
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
passwordTextbox = new OsuPasswordTextBox new FillFlowContainer
{ {
Width = 200, Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
passwordTextbox = new OsuPasswordTextBox
{
Width = 200,
PlaceholderText = "password",
},
joinButton = new TriangleButton
{
Width = 80,
Text = "Join Room",
}
}
}, },
new TriangleButton errorText = new OsuSpriteText
{ {
Width = 80, Colour = colours.Red,
Text = "Join Room", },
Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
}
} }
}; };
joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed);
}
private void joinFailed(string error)
{
passwordTextbox.Text = string.Empty;
errorText.Text = error;
errorText
.FadeIn()
.FlashColour(Color4.White, 200)
.Delay(1000)
.FadeOutFromOne(1000, Easing.In);
Body.Shake();
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -215,7 +249,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.LoadComplete(); base.LoadComplete();
Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox)); Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox));
passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text); passwordTextbox.OnCommit += (_, __) => lounge?.Join(room, passwordTextbox.Text, null, joinFailed);
} }
} }
} }

View File

@ -290,7 +290,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
popoverContainer.HidePopover(); popoverContainer.HidePopover();
} }
public void Join(Room room, string password) => Schedule(() => public void Join(Room room, string password, Action<Room> onSuccess = null, Action<string> onFailure = null) => Schedule(() =>
{ {
if (joiningRoomOperation != null) if (joiningRoomOperation != null)
return; return;
@ -302,10 +302,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
Open(room); Open(room);
joiningRoomOperation?.Dispose(); joiningRoomOperation?.Dispose();
joiningRoomOperation = null; joiningRoomOperation = null;
}, _ => onSuccess?.Invoke(room);
}, error =>
{ {
joiningRoomOperation?.Dispose(); joiningRoomOperation?.Dispose();
joiningRoomOperation = null; joiningRoomOperation = null;
onFailure?.Invoke(error);
}); });
}); });

View File

@ -23,7 +23,10 @@ namespace osu.Game.Tests.Beatmaps
protected abstract string ResourceAssembly { get; } protected abstract string ResourceAssembly { get; }
protected void Test(double expected, string name, params Mod[] mods) protected void Test(double expected, string name, params Mod[] mods)
=> Assert.AreEqual(expected, CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating); {
// Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences.
Assert.That(CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating, Is.EqualTo(expected).Within(0.00001));
}
private WorkingBeatmap getBeatmap(string name) private WorkingBeatmap getBeatmap(string name)
{ {