1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 03:25:11 +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 =>
{
h.HitObject.StartTime = START_TIME;
h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(CreateHitObject().With(h =>
{
h.HitObject.StartTime = START_TIME;
h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})

View File

@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
/// </summary>
public abstract class ManiaSkinnableTestScene : SkinnableTestScene
{
protected const double START_TIME = 1000000000;
[Cached(Type = typeof(IScrollingInfo))]
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>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm();
}
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()
{
}
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(5000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
}
}
}

View File

@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.7568168283591499d, "diffcalc-test")]
[TestCase(1.0348244046058293d, "zero-length-sliders")]
[TestCase(6.6634445062299665d, "diffcalc-test")]
[TestCase(1.0414203870195022d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
[TestCase(8.4783236764532557d, "diffcalc-test")]
[TestCase(1.2708532136987165d, "zero-length-sliders")]
[TestCase(8.3858089051603368d, "diffcalc-test")]
[TestCase(1.2723279173428435d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name)
=> 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 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.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);

View File

@ -55,6 +55,59 @@ namespace osu.Game.Tests.Visual.Editing
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]
public void TestPreventSwitchDueToUnsavedChanges()
{
@ -118,7 +171,7 @@ namespace osu.Game.Tests.Visual.Editing
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
{
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)
{
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),
},

View File

@ -45,7 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
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)
},

View File

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

View File

@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Framework.Testing;
@ -82,7 +83,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[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;

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value = new Room();
Child = new MultiplayerMatchFooter
Child = new Container
{
Anchor = 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;
}
/// <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>
/// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position.
/// </summary>

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Extensions;
namespace osu.Game.Graphics.Containers
{
@ -16,40 +16,10 @@ namespace osu.Game.Graphics.Containers
/// </summary>
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>
/// Shake the contents of this container.
/// </summary>
/// <param name="maximumLength">The maximum length the shake should last.</param>
public void Shake(double? maximumLength = null)
{
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);
}
public void Shake(double? maximumLength = null) => this.Shake(ShakeDuration, maximumLength: maximumLength);
}
}

View File

@ -179,11 +179,7 @@ namespace osu.Game.Online.Rooms
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
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,
// 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);
other.RemoveExpiredPlaylistItems();
if (!Playlist.SequenceEqual(other.Playlist))
{
@ -200,6 +196,15 @@ namespace osu.Game.Online.Rooms
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 ShouldSerializeHost() => false;
public bool ShouldSerializeEndDate() => false;

View File

@ -15,8 +15,6 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Scoring;
@ -46,15 +44,8 @@ namespace osu.Game.Online.Spectator
private readonly BindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
private IBeatmap? currentBeatmap;
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();
/// <summary>
@ -153,9 +144,9 @@ namespace osu.Game.Online.Spectator
IsPlaying = true;
// transfer state at point of beginning play
currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID;
currentState.RulesetID = currentRuleset.Value.ID;
currentState.Mods = currentMods.Value.Select(m => new APIMod(m));
currentState.BeatmapID = score.ScoreInfo.Beatmap.OnlineBeatmapID;
currentState.RulesetID = score.ScoreInfo.RulesetID;
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
currentBeatmap = beatmap.PlayableBeatmap;
currentScore = score;

View File

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

View File

@ -317,6 +317,16 @@ namespace osu.Game.Screens.Edit
/// </summary>
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()
{
// no longer new after first user-triggered save.
@ -740,7 +750,11 @@ namespace osu.Game.Screens.Edit
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();

View File

@ -20,6 +20,13 @@ namespace osu.Game.Screens.Edit
/// </summary>
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 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();
ValidForResume = true;
@ -70,7 +77,8 @@ namespace osu.Game.Screens.Edit
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.
// 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()
{
this.Push(CreateEditor());
var editor = CreateEditor();
this.Push(editor);
if (state != null)
editor.RestoreState(state);
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)
{
incoming.RemoveExpiredPlaylistItems();
RoomManager.AddOrUpdateRoom(incoming);
}
initialRoomsReceived.Value = true;
tcs.SetResult(true);

View File

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

View File

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

View File

@ -1,7 +1,6 @@
// 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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -15,6 +14,9 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
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.UserInterfaceV2;
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[]
{
@ -176,7 +178,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
private readonly Room room;
public Action<Room, string> JoinRequested;
[Resolved(canBeNull: true)]
private LoungeSubScreen lounge { get; set; }
public PasswordEntryPopover(Room room)
{
@ -184,30 +187,61 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
}
private OsuPasswordTextBox passwordTextbox;
private TriangleButton joinButton;
private OsuSpriteText errorText;
[BackgroundDependencyLoader]
private void load()
private void load(OsuColour colours)
{
Child = new FillFlowContainer
{
Margin = new MarginPadding(10),
Spacing = new Vector2(5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Direction = FillDirection.Vertical,
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,
Text = "Join Room",
Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
}
Colour = colours.Red,
},
}
};
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()
@ -215,7 +249,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.LoadComplete();
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();
}
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)
return;
@ -302,10 +302,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
Open(room);
joiningRoomOperation?.Dispose();
joiningRoomOperation = null;
}, _ =>
onSuccess?.Invoke(room);
}, error =>
{
joiningRoomOperation?.Dispose();
joiningRoomOperation = null;
onFailure?.Invoke(error);
});
});

View File

@ -23,7 +23,10 @@ namespace osu.Game.Tests.Beatmaps
protected abstract string ResourceAssembly { get; }
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)
{