mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 00:23:01 +08:00
Merge branch 'master' into fix-realm-post-storage-migration-failure
This commit is contained in:
commit
54790bb758
@ -1,6 +1,6 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
|
||||
Thank you for showing interest in the development of osu!. We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
|
||||
|
||||
These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner.
|
||||
|
||||
@ -32,7 +32,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in
|
||||
|
||||
* **Provide more information when asked to do so.**
|
||||
|
||||
Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
|
||||
Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local osu! database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
|
||||
|
||||
* **When submitting a feature proposal, please describe it in the most understandable way you can.**
|
||||
|
||||
@ -54,7 +54,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in
|
||||
|
||||
We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label.
|
||||
|
||||
However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
|
||||
However, do keep in mind that the core team is committed to bringing osu!(lazer) up to par with osu!(stable) first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
|
||||
|
||||
Here are some key things to note before jumping in:
|
||||
|
||||
@ -128,7 +128,7 @@ Here are some key things to note before jumping in:
|
||||
|
||||
* **Don't mistake criticism of code for criticism of your person.**
|
||||
|
||||
As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
|
||||
As mentioned before, we are highly committed to quality when it comes to the osu! project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
|
||||
|
||||
* **Feel free to reach out for help.**
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||
|
||||
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge.
|
||||
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
|
||||
|
||||
## Status
|
||||
|
||||
|
@ -51,8 +51,8 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1210.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1215.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -55,73 +55,75 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
bool firstDeltaSwitch = false;
|
||||
|
||||
for (int i = Previous.Count - 2; i > 0; i--)
|
||||
int rhythmStart = 0;
|
||||
|
||||
while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max)
|
||||
rhythmStart++;
|
||||
|
||||
for (int i = rhythmStart; i > 0; i--)
|
||||
{
|
||||
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1];
|
||||
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i];
|
||||
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1];
|
||||
|
||||
double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now
|
||||
double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
|
||||
|
||||
if (currHistoricalDecay != 0)
|
||||
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
|
||||
|
||||
double currDelta = currObj.StrainTime;
|
||||
double prevDelta = prevObj.StrainTime;
|
||||
double lastDelta = lastObj.StrainTime;
|
||||
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
|
||||
|
||||
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
|
||||
|
||||
windowPenalty = Math.Min(1, windowPenalty);
|
||||
|
||||
double effectiveRatio = windowPenalty * currRatio;
|
||||
|
||||
if (firstDeltaSwitch)
|
||||
{
|
||||
currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count.
|
||||
|
||||
double currDelta = currObj.StrainTime;
|
||||
double prevDelta = prevObj.StrainTime;
|
||||
double lastDelta = lastObj.StrainTime;
|
||||
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
|
||||
|
||||
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
|
||||
|
||||
windowPenalty = Math.Min(1, windowPenalty);
|
||||
|
||||
double effectiveRatio = windowPenalty * currRatio;
|
||||
|
||||
if (firstDeltaSwitch)
|
||||
if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
|
||||
{
|
||||
if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta))
|
||||
{
|
||||
if (islandSize < 7)
|
||||
islandSize++; // island is still progressing, count size.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
||||
effectiveRatio *= 0.125;
|
||||
|
||||
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
||||
effectiveRatio *= 0.25;
|
||||
|
||||
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
||||
effectiveRatio *= 0.25;
|
||||
|
||||
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
|
||||
effectiveRatio *= 0.50;
|
||||
|
||||
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
|
||||
effectiveRatio *= 0.125;
|
||||
|
||||
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
|
||||
|
||||
startRatio = effectiveRatio;
|
||||
|
||||
previousIslandSize = islandSize; // log the last island size.
|
||||
|
||||
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
|
||||
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
|
||||
|
||||
islandSize = 1;
|
||||
}
|
||||
if (islandSize < 7)
|
||||
islandSize++; // island is still progressing, count size.
|
||||
}
|
||||
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
|
||||
else
|
||||
{
|
||||
// Begin counting island until we change speed again.
|
||||
firstDeltaSwitch = true;
|
||||
if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
||||
effectiveRatio *= 0.125;
|
||||
|
||||
if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
||||
effectiveRatio *= 0.25;
|
||||
|
||||
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
||||
effectiveRatio *= 0.25;
|
||||
|
||||
if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5)
|
||||
effectiveRatio *= 0.50;
|
||||
|
||||
if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
|
||||
effectiveRatio *= 0.125;
|
||||
|
||||
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2;
|
||||
|
||||
startRatio = effectiveRatio;
|
||||
|
||||
previousIslandSize = islandSize; // log the last island size.
|
||||
|
||||
if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting
|
||||
firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
|
||||
|
||||
islandSize = 1;
|
||||
}
|
||||
}
|
||||
else if (prevDelta > 1.25 * currDelta) // we want to be speeding up.
|
||||
{
|
||||
// Begin counting island until we change speed again.
|
||||
firstDeltaSwitch = true;
|
||||
startRatio = effectiveRatio;
|
||||
islandSize = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.StateChanges.Events;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu
|
||||
@ -39,6 +41,19 @@ namespace osu.Game.Rulesets.Osu
|
||||
return base.Handle(e);
|
||||
}
|
||||
|
||||
protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
|
||||
{
|
||||
if (!AllowUserCursorMovement)
|
||||
{
|
||||
// Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse.
|
||||
// Primarily relied upon by the "autopilot" osu! mod.
|
||||
var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position);
|
||||
e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null);
|
||||
}
|
||||
|
||||
return base.HandleMouseTouchStateChange(e);
|
||||
}
|
||||
|
||||
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
|
||||
{
|
||||
public bool AllowUserPresses = true;
|
||||
|
@ -45,8 +45,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
||||
AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5);
|
||||
checkPlayingUserCount(0);
|
||||
|
||||
AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
|
||||
|
||||
changeState(3, MultiplayerUserState.WaitingForLoad);
|
||||
checkPlayingUserCount(3);
|
||||
|
||||
@ -64,8 +62,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
|
||||
|
||||
AddStep("leave room", () => Client.LeaveRoom());
|
||||
checkPlayingUserCount(0);
|
||||
|
||||
AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
90
osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
Normal file
90
osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
Normal file
@ -0,0 +1,90 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Tests.Online.Chat
|
||||
{
|
||||
[TestFixture]
|
||||
public class MessageNotifierTest
|
||||
{
|
||||
[Test]
|
||||
public void TestContainsUsernameMidlinePositive()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test message", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameStartOfLinePositive()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test message", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameEndOfLinePositive()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameMidlineNegative()
|
||||
{
|
||||
Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a testmessage for notifications", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameStartOfLineNegative()
|
||||
{
|
||||
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Testmessage", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameEndOfLineNegative()
|
||||
{
|
||||
Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a notificationtest", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameBetweenInterpunction()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameUnicode()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test \u0460\u0460 message", "\u0460\u0460"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameUnicodeNegative()
|
||||
{
|
||||
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameSpecialCharactersPositive()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test [#^-^#] message", "[#^-^#]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameSpecialCharactersNegative()
|
||||
{
|
||||
Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameAtSign()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("@username hi", "username"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContainsUsernameColon()
|
||||
{
|
||||
Assert.IsTrue(MessageNotifier.CheckContainsUsername("username: hi", "username"));
|
||||
}
|
||||
}
|
||||
}
|
@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online
|
||||
public void TestTrackerRespectsChecksum()
|
||||
{
|
||||
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
|
||||
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
|
||||
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
|
||||
|
||||
AddStep("import altered beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
|
||||
});
|
||||
addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded);
|
||||
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
|
||||
|
||||
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = selectedItem }
|
||||
});
|
||||
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
|
||||
|
||||
AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait());
|
||||
addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
|
||||
}
|
||||
|
||||
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)
|
||||
|
@ -5,8 +5,11 @@ using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
@ -15,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
@ -22,8 +26,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public class TestSceneBackgroundScreenDefault : OsuTestScene
|
||||
{
|
||||
private BackgroundScreenStack stack;
|
||||
private BackgroundScreenDefault screen;
|
||||
|
||||
private TestBackgroundScreenDefault screen;
|
||||
private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType<Graphics.Backgrounds.Background>().FirstOrDefault();
|
||||
|
||||
[Resolved]
|
||||
@ -36,10 +39,95 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create background stack", () => Child = stack = new BackgroundScreenStack());
|
||||
AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false)));
|
||||
AddStep("push default screen", () => stack.Push(screen = new TestBackgroundScreenDefault()));
|
||||
AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapBackgroundTracksBeatmap()
|
||||
{
|
||||
setSupporter(true);
|
||||
setSourceMode(BackgroundSource.Beatmap);
|
||||
|
||||
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
|
||||
|
||||
Graphics.Backgrounds.Background last = null;
|
||||
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
|
||||
AddStep("store background", () => last = getCurrentBackground());
|
||||
|
||||
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
|
||||
AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
|
||||
|
||||
AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
|
||||
AddStep("store background", () => last = getCurrentBackground());
|
||||
|
||||
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
|
||||
AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true);
|
||||
AddUntilStep("background is new beatmap background", () => last != getCurrentBackground());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapBackgroundTracksBeatmapWhenSuspended()
|
||||
{
|
||||
setSupporter(true);
|
||||
setSourceMode(BackgroundSource.Beatmap);
|
||||
|
||||
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
|
||||
|
||||
BackgroundScreenBeatmap nestedScreen = null;
|
||||
|
||||
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
|
||||
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
|
||||
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
|
||||
|
||||
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
|
||||
|
||||
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
|
||||
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
|
||||
|
||||
AddStep("pop screen back to top level", () => screen.MakeCurrent());
|
||||
|
||||
AddAssert("top level background changed", () => screen.CheckLastLoadChange() == true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapBackgroundIgnoresNoChangeWhenSuspended()
|
||||
{
|
||||
BackgroundScreenBeatmap nestedScreen = null;
|
||||
WorkingBeatmap originalWorking = null;
|
||||
|
||||
setSupporter(true);
|
||||
setSourceMode(BackgroundSource.Beatmap);
|
||||
|
||||
AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground));
|
||||
|
||||
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
|
||||
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
|
||||
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
|
||||
|
||||
// we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
|
||||
AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
|
||||
|
||||
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
|
||||
AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
|
||||
|
||||
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
|
||||
|
||||
AddStep("pop screen back to top level", () => screen.MakeCurrent());
|
||||
|
||||
AddStep("top level screen is current", () => screen.IsCurrentScreen());
|
||||
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundTypeSwitch()
|
||||
{
|
||||
@ -78,36 +166,24 @@ namespace osu.Game.Tests.Visual.Background
|
||||
[TestCase(BackgroundSource.Skin, typeof(SkinBackground))]
|
||||
public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType)
|
||||
{
|
||||
Graphics.Backgrounds.Background last = null;
|
||||
|
||||
setSourceMode(source);
|
||||
setSupporter(true);
|
||||
if (source == BackgroundSource.Skin)
|
||||
setCustomSkin();
|
||||
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType);
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == backgroundType);
|
||||
AddAssert("next doesn't load new background", () => screen.Next() == false);
|
||||
|
||||
// doesn't really need to be checked but might as well.
|
||||
AddWaitStep("wait a bit", 5);
|
||||
AddUntilStep("ensure same background instance", () => last == getCurrentBackground());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter)
|
||||
{
|
||||
Graphics.Backgrounds.Background last = null;
|
||||
|
||||
setSourceMode(BackgroundSource.Skin);
|
||||
setSupporter(supporter);
|
||||
setDefaultSkin();
|
||||
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
|
||||
AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background));
|
||||
AddAssert("next cycles background", () => screen.Next());
|
||||
|
||||
// doesn't really need to be checked but might as well.
|
||||
AddWaitStep("wait a bit", 5);
|
||||
AddUntilStep("ensure different background instance", () => last != getCurrentBackground());
|
||||
}
|
||||
|
||||
private void setSourceMode(BackgroundSource source) =>
|
||||
@ -120,6 +196,42 @@ namespace osu.Game.Tests.Visual.Background
|
||||
Id = API.LocalUser.Value.Id + 1,
|
||||
});
|
||||
|
||||
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
|
||||
|
||||
private class TestBackgroundScreenDefault : BackgroundScreenDefault
|
||||
{
|
||||
private bool? lastLoadTriggerCausedChange;
|
||||
|
||||
public TestBackgroundScreenDefault()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Next()
|
||||
{
|
||||
bool didChange = base.Next();
|
||||
lastLoadTriggerCausedChange = didChange;
|
||||
return didChange;
|
||||
}
|
||||
|
||||
public bool? CheckLastLoadChange()
|
||||
{
|
||||
bool? lastChange = lastLoadTriggerCausedChange;
|
||||
lastLoadTriggerCausedChange = null;
|
||||
return lastChange;
|
||||
}
|
||||
}
|
||||
|
||||
private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap
|
||||
{
|
||||
public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager)
|
||||
: base(new Beatmap(), null, audioManager)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Texture GetBackground() => new Texture(1, 1);
|
||||
}
|
||||
|
||||
private void setCustomSkin()
|
||||
{
|
||||
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
|
||||
|
@ -6,12 +6,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
@ -19,11 +19,10 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public class TestSceneBeatmapCard : OsuTestScene
|
||||
public class TestSceneBeatmapCard : OsuManualInputManagerTestScene
|
||||
{
|
||||
/// <summary>
|
||||
/// All cards on this scene use a common online ID to ensure that map download, preview tracks, etc. can be tested manually with online sources.
|
||||
@ -253,14 +252,32 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
public void TestNormal()
|
||||
{
|
||||
createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
|
||||
}
|
||||
|
||||
AddToggleStep("toggle expanded state", expanded =>
|
||||
{
|
||||
var card = this.ChildrenOfType<BeatmapCard>().Last();
|
||||
if (!card.Expanded.Disabled)
|
||||
card.Expanded.Value = expanded;
|
||||
});
|
||||
AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType<BeatmapCard>().ForEach(card => card.Expanded.Disabled = disabled));
|
||||
[Test]
|
||||
public void TestHoverState()
|
||||
{
|
||||
AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCard(s)));
|
||||
|
||||
AddStep("Hover card", () => InputManager.MoveMouseTo(firstCard()));
|
||||
AddWaitStep("wait for potential state change", 5);
|
||||
AddAssert("card is not expanded", () => !firstCard().Expanded.Value);
|
||||
|
||||
AddStep("Hover spectrum display", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType<DifficultySpectrumDisplay>().Single()));
|
||||
AddUntilStep("card is expanded", () => firstCard().Expanded.Value);
|
||||
|
||||
AddStep("Hover difficulty content", () => InputManager.MoveMouseTo(firstCard().ChildrenOfType<BeatmapCardDifficultyList>().Single()));
|
||||
AddWaitStep("wait for potential state change", 5);
|
||||
AddAssert("card is still expanded", () => firstCard().Expanded.Value);
|
||||
|
||||
AddStep("Hover main content again", () => InputManager.MoveMouseTo(firstCard()));
|
||||
AddWaitStep("wait for potential state change", 5);
|
||||
AddAssert("card is still expanded", () => firstCard().Expanded.Value);
|
||||
|
||||
AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType<BeatmapCard>().Last()));
|
||||
AddUntilStep("card is not expanded", () => !firstCard().Expanded.Value);
|
||||
|
||||
BeatmapCard firstCard() => this.ChildrenOfType<BeatmapCard>().First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
|
||||
|
||||
var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
|
||||
target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
|
||||
double targetTime = addEventToLoop ? 20000 : 0;
|
||||
target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1);
|
||||
|
||||
// these should be ignored due to being in the future.
|
||||
sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
|
||||
loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
|
||||
loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1);
|
||||
|
||||
storyboard.GetLayer("Background").Add(sprite);
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFirstItemSelectedByDefault()
|
||||
{
|
||||
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -27,13 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2);
|
||||
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID);
|
||||
|
||||
addItem(() => InitialBeatmap);
|
||||
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
|
||||
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID);
|
||||
|
||||
AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
AddAssert("first item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1);
|
||||
AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true);
|
||||
AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
AddAssert("last item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -55,12 +53,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
RunGameplay();
|
||||
|
||||
AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
|
||||
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID);
|
||||
AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID);
|
||||
|
||||
RunGameplay();
|
||||
|
||||
AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true);
|
||||
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID);
|
||||
AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[2].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -74,15 +72,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly));
|
||||
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
|
||||
AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID);
|
||||
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
||||
AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
||||
AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorrectItemSelectedAfterNewItemAdded()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
|
||||
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@ -20,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFirstItemSelectedByDefault()
|
||||
{
|
||||
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -28,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
selectNewItem(() => InitialBeatmap);
|
||||
|
||||
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
selectNewItem(() => OtherBeatmap);
|
||||
|
||||
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
|
||||
AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
|
||||
AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
||||
AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID);
|
||||
AddAssert("second playlist item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -85,6 +86,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void selectNewItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddUntilStep("wait for playlist panels to load", () =>
|
||||
{
|
||||
var queueList = this.ChildrenOfType<MultiplayerQueueList>().Single();
|
||||
return queueList.ChildrenOfType<DrawableRoomPlaylistItem>().Count() == queueList.Items.Count;
|
||||
});
|
||||
|
||||
AddStep("click edit button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().First());
|
||||
@ -97,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
||||
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID);
|
||||
AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID);
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
|
@ -30,6 +30,8 @@ using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Spectate;
|
||||
@ -391,9 +393,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready));
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
|
||||
pressReadyButton();
|
||||
|
||||
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
|
||||
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
@ -413,10 +415,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
|
||||
pressReadyButton();
|
||||
|
||||
AddStep("Enter song select", () =>
|
||||
{
|
||||
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen;
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value);
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
@ -592,20 +596,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
|
||||
AddStep("click ready button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(readyButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||
|
||||
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player);
|
||||
enterGameplay();
|
||||
|
||||
// Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out.
|
||||
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
|
||||
@ -665,7 +656,173 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
}
|
||||
|
||||
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
|
||||
[Test]
|
||||
public void TestSpectatingStateResetOnBackButtonDuringGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
||||
AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||
|
||||
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready));
|
||||
|
||||
pressReadyButton(1234);
|
||||
AddUntilStep("wait for gameplay", () => (multiplayerScreenStack.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true);
|
||||
|
||||
AddStep("press back button and exit", () =>
|
||||
{
|
||||
multiplayerScreenStack.OnBackButton();
|
||||
multiplayerScreenStack.Exit();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for return to match subscreen", () => multiplayerScreenStack.MultiplayerScreen.IsCurrentScreen());
|
||||
AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("set spectating state", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
||||
AddUntilStep("state set to spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||
|
||||
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("set other user ready", () => client.ChangeUserState(1234, MultiplayerUserState.Ready));
|
||||
|
||||
pressReadyButton(1234);
|
||||
AddUntilStep("wait for gameplay", () => (multiplayerScreenStack.CurrentScreen as MultiSpectatorScreen)?.IsLoaded == true);
|
||||
AddStep("set other user loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded));
|
||||
AddStep("set other user finished play", () => client.ChangeUserState(1234, MultiplayerUserState.FinishedPlay));
|
||||
|
||||
AddStep("press back button and exit", () =>
|
||||
{
|
||||
multiplayerScreenStack.OnBackButton();
|
||||
multiplayerScreenStack.Exit();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for return to match subscreen", () => multiplayerScreenStack.MultiplayerScreen.IsCurrentScreen());
|
||||
AddWaitStep("wait for possible state change", 5);
|
||||
AddUntilStep("user state is spectating", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemAddedByOtherUserDuringGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
enterGameplay();
|
||||
|
||||
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
|
||||
{
|
||||
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1
|
||||
})));
|
||||
|
||||
AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2);
|
||||
|
||||
AddStep("exit gameplay as initial user", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent());
|
||||
AddUntilStep("queue contains item", () => this.ChildrenOfType<MultiplayerQueueList>().Single().Items.Single().ID == 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemAddedAndDeletedByOtherUserDuringGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
enterGameplay();
|
||||
|
||||
AddStep("join other user", () => client.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
|
||||
{
|
||||
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo.OnlineID ?? -1
|
||||
})));
|
||||
|
||||
AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2);
|
||||
|
||||
AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2));
|
||||
AddUntilStep("item removed from playlist", () => client.Room?.Playlist.Count == 1);
|
||||
|
||||
AddStep("exit gameplay as initial user", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent());
|
||||
AddUntilStep("queue is empty", () => this.ChildrenOfType<MultiplayerQueueList>().Single().Items.Count == 0);
|
||||
}
|
||||
|
||||
private void enterGameplay()
|
||||
{
|
||||
pressReadyButton();
|
||||
pressReadyButton();
|
||||
AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player);
|
||||
}
|
||||
|
||||
private ReadyButton readyButton => this.ChildrenOfType<ReadyButton>().Single();
|
||||
|
||||
private void pressReadyButton(int? playingUserId = null)
|
||||
{
|
||||
AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value);
|
||||
|
||||
MultiplayerUserState lastState = MultiplayerUserState.Idle;
|
||||
MultiplayerRoomUser user = null;
|
||||
|
||||
AddStep("click ready button", () =>
|
||||
{
|
||||
user = playingUserId == null ? client.LocalUser : client.Room?.Users.Single(u => u.UserID == playingUserId);
|
||||
lastState = user?.State ?? MultiplayerUserState.Idle;
|
||||
|
||||
InputManager.MoveMouseTo(readyButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for state change", () => user?.State != lastState);
|
||||
}
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
|
||||
@ -27,7 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("initialise gameplay", () =>
|
||||
{
|
||||
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
|
||||
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
||||
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
|
||||
}, Client.Room?.Users.ToArray()));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
@ -27,11 +28,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerQueueList : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerQueueList playlist;
|
||||
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
|
||||
|
||||
private MultiplayerQueueList playlist;
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
@ -50,12 +52,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
selectedItem.Value = null;
|
||||
|
||||
Child = playlist = new MultiplayerQueueList
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem },
|
||||
SelectedItem = { BindTarget = selectedItem },
|
||||
Items = { BindTarget = Client.APIRoom!.Playlist }
|
||||
};
|
||||
});
|
||||
@ -107,22 +111,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
|
||||
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
|
||||
assertDeleteButtonVisibility(0, false);
|
||||
|
||||
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
|
||||
|
||||
AddStep("select item 0", () => selectedItem.Value = playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().ElementAt(0).Model);
|
||||
assertDeleteButtonVisibility(0, false);
|
||||
assertDeleteButtonVisibility(1, true);
|
||||
|
||||
// Run through gameplay.
|
||||
AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready));
|
||||
AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
|
||||
AddStep("start match", () => Client.StartMatch());
|
||||
AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
|
||||
AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded));
|
||||
AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing);
|
||||
AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay));
|
||||
AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results);
|
||||
|
||||
AddStep("select item 1", () => selectedItem.Value = playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().ElementAt(1).Model);
|
||||
assertDeleteButtonVisibility(0, true);
|
||||
assertDeleteButtonVisibility(1, false);
|
||||
}
|
||||
|
||||
|
@ -393,6 +393,25 @@ namespace osu.Game.Tests.Visual.Online
|
||||
channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultiplayerChannelIsNotShown()
|
||||
{
|
||||
Channel multiplayerChannel = null;
|
||||
|
||||
AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser())
|
||||
{
|
||||
Name = "#mp_1",
|
||||
Type = ChannelType.Multiplayer,
|
||||
}));
|
||||
|
||||
AddAssert("channel joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel));
|
||||
AddAssert("channel not present in overlay", () => !chatOverlay.TabMap.ContainsKey(multiplayerChannel));
|
||||
AddAssert("multiplayer channel is not current", () => channelManager.CurrentChannel.Value != multiplayerChannel);
|
||||
|
||||
AddStep("leave channel", () => channelManager.LeaveChannel(multiplayerChannel));
|
||||
AddAssert("channel left", () => !channelManager.JoinedChannels.Contains(multiplayerChannel));
|
||||
}
|
||||
|
||||
private void pressChannelHotkey(int number)
|
||||
{
|
||||
var channelKey = Key.Number0 + number;
|
||||
|
@ -15,9 +15,11 @@ using osu.Game.Database;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -112,37 +114,80 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[Test]
|
||||
public void TestBeatmapUpdatedOnReImport()
|
||||
{
|
||||
BeatmapSetInfo importedSet = null;
|
||||
string realHash = null;
|
||||
int realOnlineId = 0;
|
||||
int realOnlineSetId = 0;
|
||||
|
||||
AddStep("import altered beatmap", () =>
|
||||
AddStep("store real beatmap values", () =>
|
||||
{
|
||||
IBeatmap beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
|
||||
|
||||
// intentionally increment online IDs to clash with import below.
|
||||
beatmap.BeatmapInfo.OnlineID++;
|
||||
beatmap.BeatmapInfo.BeatmapSet.OnlineID++;
|
||||
|
||||
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
|
||||
realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash;
|
||||
realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID ?? -1;
|
||||
realOnlineSetId = importedBeatmap.Value.OnlineID ?? -1;
|
||||
});
|
||||
|
||||
AddStep("import modified beatmap", () =>
|
||||
{
|
||||
var modifiedBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
OnlineID = realOnlineId,
|
||||
BeatmapSet =
|
||||
{
|
||||
OnlineID = realOnlineSetId
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
modifiedBeatmap.HitObjects.Clear();
|
||||
modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 });
|
||||
|
||||
manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait();
|
||||
});
|
||||
|
||||
// Create the room using the real beatmap values.
|
||||
setupAndCreateRoom(room =>
|
||||
{
|
||||
room.Name.Value = "my awesome room";
|
||||
room.Host.Value = API.LocalUser.Value;
|
||||
room.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = importedSet.Beatmaps[0] },
|
||||
Beatmap =
|
||||
{
|
||||
Value = new BeatmapInfo
|
||||
{
|
||||
MD5Hash = realHash,
|
||||
OnlineID = realOnlineId,
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
OnlineID = realOnlineSetId,
|
||||
}
|
||||
}
|
||||
},
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1);
|
||||
AddAssert("match has default beatmap", () => match.Beatmap.IsDefault);
|
||||
|
||||
importBeatmap();
|
||||
AddStep("reimport original beatmap", () =>
|
||||
{
|
||||
var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
OnlineID = realOnlineId,
|
||||
BeatmapSet =
|
||||
{
|
||||
OnlineID = realOnlineSetId
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1);
|
||||
manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait();
|
||||
});
|
||||
|
||||
AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash);
|
||||
}
|
||||
|
||||
private void setupAndCreateRoom(Action<Room> room)
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual
|
||||
((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, game);
|
||||
}
|
||||
|
||||
public override bool OnBackButton() => multiplayerScreen.OnBackButton();
|
||||
public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton();
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
public const float TRANSITION_DURATION = 400;
|
||||
public const float CORNER_RADIUS = 10;
|
||||
|
||||
public Bindable<bool> Expanded { get; } = new BindableBool();
|
||||
public IBindable<bool> Expanded { get; }
|
||||
|
||||
private const float width = 408;
|
||||
private const float height = 100;
|
||||
@ -64,9 +64,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public BeatmapCard(APIBeatmapSet beatmapSet)
|
||||
public BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
Expanded = new BindableBool { Disabled = !allowExpansion };
|
||||
|
||||
this.beatmapSet = beatmapSet;
|
||||
favouriteState = new Bindable<BeatmapSetFavouriteState>(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount));
|
||||
downloadTracker = new BeatmapDownloadTracker(beatmapSet);
|
||||
@ -282,15 +284,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
Hovered = _ =>
|
||||
{
|
||||
content.ScheduleShow();
|
||||
content.ExpandAfterDelay();
|
||||
return false;
|
||||
},
|
||||
Unhovered = _ =>
|
||||
{
|
||||
// This hide should only trigger if the expanded content has not shown yet.
|
||||
// ie. if the user has not shown intent to want to see it (quickly moved over the info row area).
|
||||
// Handles the case where a user has not shown explicit intent to view expanded info.
|
||||
// ie. quickly moved over the info row area but didn't remain within it.
|
||||
if (!Expanded.Value)
|
||||
content.ScheduleHide();
|
||||
content.CancelExpand();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -366,8 +368,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
content.ScheduleHide();
|
||||
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
set => dropdownScroll.Child = value;
|
||||
}
|
||||
|
||||
public Bindable<bool> Expanded { get; } = new BindableBool();
|
||||
public IBindable<bool> Expanded => expanded;
|
||||
|
||||
private readonly BindableBool expanded = new BindableBool();
|
||||
|
||||
private readonly Box background;
|
||||
private readonly Container content;
|
||||
@ -54,7 +56,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
AutoSizeAxes = Axes.Y,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Unhovered = _ => checkForHide(),
|
||||
Unhovered = _ => updateFromHoverChange(),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
@ -76,10 +78,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Alpha = 0,
|
||||
Hovered = _ =>
|
||||
{
|
||||
keep();
|
||||
updateFromHoverChange();
|
||||
return true;
|
||||
},
|
||||
Unhovered = _ => checkForHide(),
|
||||
Unhovered = _ => updateFromHoverChange(),
|
||||
Child = dropdownScroll = new ExpandedContentScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -119,51 +121,20 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
private ScheduledDelegate? scheduledExpandedChange;
|
||||
|
||||
public void ScheduleShow()
|
||||
{
|
||||
scheduledExpandedChange?.Cancel();
|
||||
if (Expanded.Disabled || Expanded.Value)
|
||||
return;
|
||||
public void ExpandAfterDelay() => queueExpandedStateChange(true, 100);
|
||||
|
||||
scheduledExpandedChange = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (!Expanded.Disabled)
|
||||
Expanded.Value = true;
|
||||
}, 100);
|
||||
}
|
||||
public void CancelExpand() => scheduledExpandedChange?.Cancel();
|
||||
|
||||
public void ScheduleHide()
|
||||
{
|
||||
scheduledExpandedChange?.Cancel();
|
||||
if (Expanded.Disabled || !Expanded.Value)
|
||||
return;
|
||||
private void updateFromHoverChange() =>
|
||||
queueExpandedStateChange(content.IsHovered || dropdownContent.IsHovered, 100);
|
||||
|
||||
scheduledExpandedChange = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (!Expanded.Disabled)
|
||||
Expanded.Value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private void checkForHide()
|
||||
{
|
||||
if (Expanded.Disabled)
|
||||
return;
|
||||
|
||||
if (content.IsHovered || dropdownContent.IsHovered)
|
||||
return;
|
||||
|
||||
scheduledExpandedChange?.Cancel();
|
||||
Expanded.Value = false;
|
||||
}
|
||||
|
||||
private void keep()
|
||||
private void queueExpandedStateChange(bool newState, int delay = 0)
|
||||
{
|
||||
if (Expanded.Disabled)
|
||||
return;
|
||||
|
||||
scheduledExpandedChange?.Cancel();
|
||||
Expanded.Value = true;
|
||||
scheduledExpandedChange = Scheduler.AddDelayed(() => expanded.Value = newState, delay);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
|
@ -72,18 +72,21 @@ namespace osu.Game.Graphics.Cursor
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
// only trigger animation for main mouse buttons
|
||||
activeCursor.Scale = new Vector2(1);
|
||||
activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
|
||||
|
||||
activeCursor.AdditiveLayer.Alpha = 0;
|
||||
activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
|
||||
|
||||
if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating)
|
||||
if (State.Value == Visibility.Visible)
|
||||
{
|
||||
// if cursor is already rotating don't reset its rotate origin
|
||||
dragRotationState = DragRotationState.DragStarted;
|
||||
positionMouseDown = e.MousePosition;
|
||||
// only trigger animation for main mouse buttons
|
||||
activeCursor.Scale = new Vector2(1);
|
||||
activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
|
||||
|
||||
activeCursor.AdditiveLayer.Alpha = 0;
|
||||
activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
|
||||
|
||||
if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating)
|
||||
{
|
||||
// if cursor is already rotating don't reset its rotate origin
|
||||
dragRotationState = DragRotationState.DragStarted;
|
||||
positionMouseDown = e.MousePosition;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
|
@ -65,8 +65,10 @@ namespace osu.Game.Graphics
|
||||
|
||||
public void SetContent(DateTimeOffset date)
|
||||
{
|
||||
dateText.Text = $"{date:d MMMM yyyy} ";
|
||||
timeText.Text = $"{date:HH:mm:ss \"UTC\"z}";
|
||||
DateTimeOffset localDate = date.ToLocalTime();
|
||||
|
||||
dateText.Text = $"{localDate:d MMMM yyyy} ";
|
||||
timeText.Text = $"{localDate:HH:mm:ss \"UTC\"z}";
|
||||
}
|
||||
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
|
@ -1,10 +1,12 @@
|
||||
// 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.Extensions;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuNumberBox : OsuTextBox
|
||||
{
|
||||
protected override bool CanAddCharacter(char character) => char.IsNumber(character);
|
||||
protected override bool CanAddCharacter(char character) => character.IsAsciiDigit();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
// 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 System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -120,16 +120,21 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
private void checkForMentions(Channel channel, Message message)
|
||||
{
|
||||
if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return;
|
||||
if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return;
|
||||
|
||||
notifications.Post(new MentionNotification(message.Sender.Username, channel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if <paramref name="message"/> contains <paramref name="username"/>.
|
||||
/// Checks if <paramref name="message"/> mentions <paramref name="username"/>.
|
||||
/// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces).
|
||||
/// </summary>
|
||||
private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase);
|
||||
public static bool CheckContainsUsername(string message, string username)
|
||||
{
|
||||
string fullName = Regex.Escape(username);
|
||||
string underscoreName = Regex.Escape(username.Replace(' ', '_'));
|
||||
return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
public class PrivateMessageNotification : OpenChannelNotification
|
||||
{
|
||||
|
@ -77,6 +77,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <exception cref="InvalidStateException">If an attempt to start the game occurs when the game's (or users') state disallows it.</exception>
|
||||
Task StartMatch();
|
||||
|
||||
/// <summary>
|
||||
/// Aborts an ongoing gameplay load.
|
||||
/// </summary>
|
||||
Task AbortGameplay();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the playlist.
|
||||
/// </summary>
|
||||
|
@ -95,8 +95,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
protected readonly BindableList<int> PlayingUserIds = new BindableList<int>();
|
||||
|
||||
public readonly Bindable<PlaylistItem?> CurrentMatchPlayingItem = new Bindable<PlaylistItem?>();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
|
||||
/// </summary>
|
||||
@ -162,9 +160,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
|
||||
Debug.Assert(joinedRoom != null);
|
||||
|
||||
// Populate playlist items.
|
||||
var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false);
|
||||
|
||||
// Populate users.
|
||||
Debug.Assert(joinedRoom.Users != null);
|
||||
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false);
|
||||
@ -176,7 +171,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
APIRoom = room;
|
||||
|
||||
APIRoom.Playlist.Clear();
|
||||
APIRoom.Playlist.AddRange(playlistItems);
|
||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||
|
||||
Debug.Assert(LocalUser != null);
|
||||
addUserToAPIRoom(LocalUser);
|
||||
@ -219,7 +214,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
APIRoom = null;
|
||||
Room = null;
|
||||
CurrentMatchPlayingItem.Value = null;
|
||||
PlayingUserIds.Clear();
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
@ -333,6 +327,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task StartMatch();
|
||||
|
||||
public abstract Task AbortGameplay();
|
||||
|
||||
public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item);
|
||||
@ -477,28 +473,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
// ensure the new selected item is populated immediately.
|
||||
var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId);
|
||||
|
||||
if (playlistItem != null)
|
||||
{
|
||||
GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b =>
|
||||
{
|
||||
// Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards.
|
||||
bool success = b.Exception == null;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (success)
|
||||
playlistItem.Beatmap.Value = b.Result;
|
||||
|
||||
updateLocalRoomSettings(newSettings);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Scheduler.Add(() => updateLocalRoomSettings(newSettings));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -653,12 +628,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlaylistItemAdded(MultiplayerPlaylistItem item)
|
||||
public Task PlaylistItemAdded(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
|
||||
return Task.CompletedTask;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
@ -668,11 +641,13 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist.Add(item);
|
||||
APIRoom.Playlist.Add(playlistItem);
|
||||
APIRoom.Playlist.Add(createPlaylistItem(item));
|
||||
|
||||
ItemAdded?.Invoke(item);
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task PlaylistItemRemoved(long playlistItemId)
|
||||
@ -697,12 +672,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlaylistItemChanged(MultiplayerPlaylistItem item)
|
||||
public Task PlaylistItemChanged(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
|
||||
return Task.CompletedTask;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
@ -715,15 +688,13 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
|
||||
APIRoom.Playlist.RemoveAt(existingIndex);
|
||||
APIRoom.Playlist.Insert(existingIndex, playlistItem);
|
||||
|
||||
// If the currently-selected item was the one that got replaced, update the selected item to the new one.
|
||||
if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID)
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
|
||||
|
||||
ItemChanged?.Invoke(item);
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -752,12 +723,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
APIRoom.Password.Value = Room.Settings.Password;
|
||||
APIRoom.Type.Value = Room.Settings.MatchType;
|
||||
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId);
|
||||
RoomUpdated?.Invoke();
|
||||
}
|
||||
|
||||
private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately)
|
||||
private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
var ruleset = Rulesets.GetRuleset(item.RulesetID);
|
||||
|
||||
@ -779,9 +749,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
|
||||
if (populateBeatmapImmediately)
|
||||
playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
@ -791,7 +758,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="beatmapId">The beatmap ID.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the request.</param>
|
||||
/// <returns>The <see cref="APIBeatmap"/> retrieval task.</returns>
|
||||
protected abstract Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default);
|
||||
public abstract Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
|
||||
|
@ -154,6 +154,14 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch));
|
||||
}
|
||||
|
||||
public override Task AbortGameplay()
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay));
|
||||
}
|
||||
|
||||
public override Task AddPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
@ -178,7 +186,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId);
|
||||
}
|
||||
|
||||
protected override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
|
||||
public override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken);
|
||||
}
|
||||
|
@ -40,6 +40,11 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
private BeatmapDownloadTracker downloadTracker;
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap matching the required hash (and providing a final <see cref="BeatmapAvailability.LocallyAvailable"/> state).
|
||||
/// </summary>
|
||||
private BeatmapInfo matchingHash;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -71,13 +76,34 @@ namespace osu.Game.Online.Rooms
|
||||
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
|
||||
}, true);
|
||||
}, true);
|
||||
|
||||
// These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs.
|
||||
// During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one.
|
||||
// This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching).
|
||||
beatmapManager.ItemUpdated += itemUpdated;
|
||||
beatmapManager.ItemRemoved += itemRemoved;
|
||||
}
|
||||
|
||||
private void itemUpdated(BeatmapSetInfo item) => Schedule(() =>
|
||||
{
|
||||
if (matchingHash?.BeatmapSet.ID == item.ID || SelectedItem.Value?.Beatmap.Value.BeatmapSet?.OnlineID == item.OnlineID)
|
||||
updateAvailability();
|
||||
});
|
||||
|
||||
private void itemRemoved(BeatmapSetInfo item) => Schedule(() =>
|
||||
{
|
||||
if (matchingHash?.BeatmapSet.ID == item.ID)
|
||||
updateAvailability();
|
||||
});
|
||||
|
||||
private void updateAvailability()
|
||||
{
|
||||
if (downloadTracker == null)
|
||||
return;
|
||||
|
||||
// will be repopulated below if still valid.
|
||||
matchingHash = null;
|
||||
|
||||
switch (downloadTracker.State.Value)
|
||||
{
|
||||
case DownloadState.NotDownloaded:
|
||||
@ -93,7 +119,9 @@ namespace osu.Game.Online.Rooms
|
||||
break;
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
bool hashMatches = checkHashValidity();
|
||||
matchingHash = findMatchingHash();
|
||||
|
||||
bool hashMatches = matchingHash != null;
|
||||
|
||||
availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded();
|
||||
|
||||
@ -108,12 +136,23 @@ namespace osu.Game.Online.Rooms
|
||||
}
|
||||
}
|
||||
|
||||
private bool checkHashValidity()
|
||||
private BeatmapInfo findMatchingHash()
|
||||
{
|
||||
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
|
||||
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||
|
||||
return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null;
|
||||
return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (beatmapManager != null)
|
||||
{
|
||||
beatmapManager.ItemUpdated -= itemUpdated;
|
||||
beatmapManager.ItemRemoved -= itemRemoved;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,10 +237,7 @@ namespace osu.Game.Overlays
|
||||
Schedule(() =>
|
||||
{
|
||||
// TODO: consider scheduling bindable callbacks to not perform when overlay is not present.
|
||||
channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged;
|
||||
|
||||
foreach (Channel channel in channelManager.JoinedChannels)
|
||||
ChannelTabControl.AddChannel(channel);
|
||||
channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
|
||||
|
||||
channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged;
|
||||
availableChannelsChanged(null, null);
|
||||
@ -436,12 +433,19 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (Channel channel in args.NewItems.Cast<Channel>())
|
||||
ChannelTabControl.AddChannel(channel);
|
||||
{
|
||||
if (channel.Type != ChannelType.Multiplayer)
|
||||
ChannelTabControl.AddChannel(channel);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (Channel channel in args.OldItems.Cast<Channel>())
|
||||
{
|
||||
if (!ChannelTabControl.Items.Contains(channel))
|
||||
continue;
|
||||
|
||||
ChannelTabControl.RemoveChannel(channel);
|
||||
|
||||
var loaded = loadedChannels.Find(c => c.Channel == channel);
|
||||
|
@ -5,12 +5,14 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -28,6 +30,8 @@ namespace osu.Game.Overlays.OSD
|
||||
private Sample sampleOff;
|
||||
private Sample sampleChange;
|
||||
|
||||
private Bindable<double?> lastPlaybackTime;
|
||||
|
||||
public TrackedSettingToast(SettingDescription description)
|
||||
: base(description.Name, description.Value, description.Shortcut)
|
||||
{
|
||||
@ -75,10 +79,28 @@ namespace osu.Game.Overlays.OSD
|
||||
optionLights.Add(new OptionLight { Glowing = i == selectedOption });
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private SessionStatics statics { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
playSound();
|
||||
}
|
||||
|
||||
private void playSound()
|
||||
{
|
||||
// This debounce code roughly follows what we're using in HoverSampleDebounceComponent.
|
||||
// We're sharing the existing static for hover sounds because it doesn't really matter if they block each other.
|
||||
// This is a simple solution, but if this ever becomes a problem (or other performance issues arise),
|
||||
// the whole toast system should be rewritten to avoid recreating this drawable each time a value changes.
|
||||
lastPlaybackTime = statics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime);
|
||||
|
||||
bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= OsuGameBase.SAMPLE_DEBOUNCE_TIME;
|
||||
|
||||
if (!enoughTimePassedSinceLastPlayback) return;
|
||||
|
||||
if (optionCount == 1)
|
||||
{
|
||||
if (selectedOption == 0)
|
||||
@ -93,6 +115,8 @@ namespace osu.Game.Overlays.OSD
|
||||
sampleChange.Frequency.Value = 1 + (double)selectedOption / (optionCount - 1) * 0.25f;
|
||||
sampleChange.Play();
|
||||
}
|
||||
|
||||
lastPlaybackTime.Value = Time.Current;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Overlays
|
||||
DisplayTemporarily(box);
|
||||
});
|
||||
|
||||
private void displayTrackedSettingChange(SettingDescription description) => Display(new TrackedSettingToast(description));
|
||||
private void displayTrackedSettingChange(SettingDescription description) => Scheduler.AddOnce(Display, new TrackedSettingToast(description));
|
||||
|
||||
private TransformSequence<Drawable> fadeIn;
|
||||
private ScheduledDelegate fadeOut;
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
private class OutlinedNumberBox : OutlinedTextBox
|
||||
{
|
||||
protected override bool CanAddCharacter(char character) => char.IsNumber(character);
|
||||
protected override bool CanAddCharacter(char character) => character.IsAsciiDigit();
|
||||
|
||||
public new void NotifyInputError() => base.NotifyInputError();
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods));
|
||||
|
||||
// AlwaysPresent required for hitsounds
|
||||
drawableRuleset.Playfield.AlwaysPresent = true;
|
||||
drawableRuleset.Playfield.Hide();
|
||||
drawableRuleset.AlwaysPresent = true;
|
||||
drawableRuleset.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,16 +48,19 @@ namespace osu.Game.Screens.Backgrounds
|
||||
|
||||
AddInternal(seasonalBackgroundLoader);
|
||||
|
||||
user.ValueChanged += _ => Next();
|
||||
skin.ValueChanged += _ => Next();
|
||||
mode.ValueChanged += _ => Next();
|
||||
beatmap.ValueChanged += _ => Next();
|
||||
introSequence.ValueChanged += _ => Next();
|
||||
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Next();
|
||||
user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
|
||||
skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
|
||||
mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
|
||||
beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
|
||||
introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired);
|
||||
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired);
|
||||
|
||||
currentDisplay = RNG.Next(0, background_count);
|
||||
|
||||
Next();
|
||||
|
||||
// helper function required for AddOnce usage.
|
||||
void loadNextIfRequired() => Next();
|
||||
}
|
||||
|
||||
private ScheduledDelegate nextTask;
|
||||
@ -67,7 +70,7 @@ namespace osu.Game.Screens.Backgrounds
|
||||
/// Request loading the next background.
|
||||
/// </summary>
|
||||
/// <returns>Whether a new background was queued for load. May return false if the current background is still valid.</returns>
|
||||
public bool Next()
|
||||
public virtual bool Next()
|
||||
{
|
||||
var nextBackground = createBackground();
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -24,6 +25,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
using osu.Game.Rulesets;
|
||||
@ -90,6 +92,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved]
|
||||
private UserLookupCache userLookupCache { get; set; }
|
||||
|
||||
[CanBeNull]
|
||||
[Resolved(CanBeNull = true)]
|
||||
private MultiplayerClient multiplayerClient { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; }
|
||||
|
||||
@ -157,7 +163,15 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
if (Item.Beatmap.Value == null)
|
||||
{
|
||||
var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false);
|
||||
IBeatmapInfo foundBeatmap;
|
||||
|
||||
if (multiplayerClient != null)
|
||||
// This call can eventually go away (and use the else case below).
|
||||
// Currently required only due to the method being overridden to provide special behaviour in tests.
|
||||
foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false);
|
||||
else
|
||||
foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
Schedule(() => Item.Beatmap.Value = foundBeatmap);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
|
||||
{
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
public override bool? AllowTrackAdjustments => true;
|
||||
|
||||
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
protected OnlinePlayScreen ParentScreen { get; private set; }
|
||||
|
||||
[Cached]
|
||||
private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; }
|
||||
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||
|
||||
protected IBindable<BeatmapAvailability> BeatmapAvailability => beatmapAvailabilityTracker.Availability;
|
||||
|
||||
@ -90,11 +90,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
|
||||
beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = SelectedItem }
|
||||
};
|
||||
|
||||
RoomId.BindTo(room.RoomID);
|
||||
}
|
||||
|
||||
@ -247,10 +242,10 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
}, true);
|
||||
|
||||
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
|
||||
|
||||
beatmapManager.ItemUpdated += beatmapUpdated;
|
||||
|
||||
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
|
||||
|
||||
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -374,8 +369,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
}
|
||||
}
|
||||
|
||||
private void beatmapUpdated(BeatmapSetInfo set) => Schedule(updateWorkingBeatmap);
|
||||
|
||||
private void updateWorkingBeatmap()
|
||||
{
|
||||
var beatmap = SelectedItem.Value?.Beatmap.Value;
|
||||
@ -443,14 +436,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
/// <param name="room">The room to change the settings of.</param>
|
||||
protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (beatmapManager != null)
|
||||
beatmapManager.ItemUpdated -= beatmapUpdated;
|
||||
}
|
||||
|
||||
public class UserModSelectButton : PurpleTriangleButton
|
||||
{
|
||||
}
|
||||
|
@ -63,6 +63,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready");
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => updateState());
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
@ -104,7 +111,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
bool enableButton =
|
||||
Room?.State == MultiplayerRoomState.Open
|
||||
&& Client.CurrentMatchPlayingItem.Value?.Expired == false
|
||||
&& SelectedItem.Value?.ID == Room.Settings.PlaylistItemId
|
||||
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
||||
|
@ -121,7 +121,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
|
||||
private void addItemToLists(MultiplayerPlaylistItem item)
|
||||
{
|
||||
var apiItem = Playlist.Single(i => i.ID == item.ID);
|
||||
var apiItem = Playlist.SingleOrDefault(i => i.ID == item.ID);
|
||||
|
||||
// Item could have been removed from the playlist while the local player was in gameplay.
|
||||
if (apiItem == null)
|
||||
return;
|
||||
|
||||
if (item.Expired)
|
||||
historyList.Items.Add(apiItem);
|
||||
|
@ -1,6 +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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -18,8 +19,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.OnResuming(last);
|
||||
|
||||
if (client.Room != null && client.LocalUser?.State != MultiplayerUserState.Spectating)
|
||||
client.ChangeState(MultiplayerUserState.Idle);
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(client.LocalUser != null);
|
||||
|
||||
switch (client.LocalUser.State)
|
||||
{
|
||||
case MultiplayerUserState.Spectating:
|
||||
break;
|
||||
|
||||
case MultiplayerUserState.WaitingForLoad:
|
||||
case MultiplayerUserState.Loaded:
|
||||
case MultiplayerUserState.Playing:
|
||||
client.AbortGameplay();
|
||||
break;
|
||||
|
||||
default:
|
||||
client.ChangeState(MultiplayerUserState.Idle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string ScreenTitle => "Multiplayer";
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; }
|
||||
|
||||
private readonly PlaylistItem itemToEdit;
|
||||
private readonly long? itemToEdit;
|
||||
|
||||
private LoadingLayer loadingLayer;
|
||||
|
||||
@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
/// <param name="itemToEdit">The item to be edited. May be null, in which case a new item will be added to the playlist.</param>
|
||||
/// <param name="beatmap">An optional initial beatmap selection to perform.</param>
|
||||
/// <param name="ruleset">An optional initial ruleset selection to perform.</param>
|
||||
public MultiplayerMatchSongSelect(Room room, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
|
||||
public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
|
||||
: base(room)
|
||||
{
|
||||
this.itemToEdit = itemToEdit;
|
||||
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
var multiplayerItem = new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = itemToEdit?.ID ?? 0,
|
||||
ID = itemToEdit ?? 0,
|
||||
BeatmapID = item.BeatmapID,
|
||||
BeatmapChecksum = item.Beatmap.Value.MD5Hash,
|
||||
RulesetID = item.RulesetID,
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -67,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindTo(client.CurrentMatchPlayingItem);
|
||||
|
||||
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
|
||||
UserMods.BindValueChanged(onUserModsChanged);
|
||||
|
||||
@ -147,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
new MultiplayerPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RequestEdit = OpenSongSelection
|
||||
RequestEdit = item => OpenSongSelection(item.ID)
|
||||
}
|
||||
},
|
||||
new[]
|
||||
@ -224,7 +223,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
/// Opens the song selection screen to add or edit an item.
|
||||
/// </summary>
|
||||
/// <param name="itemToEdit">An optional playlist item to edit. If null, a new item will be added instead.</param>
|
||||
internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null)
|
||||
internal void OpenSongSelection(long? itemToEdit = null)
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
@ -327,10 +326,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||
client.ChangeState(MultiplayerUserState.Idle);
|
||||
}
|
||||
else
|
||||
else if (client.LocalUser?.State == MultiplayerUserState.Spectating
|
||||
&& (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
|
||||
{
|
||||
if (client.LocalUser?.State == MultiplayerUserState.Spectating && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
|
||||
onLoadRequested();
|
||||
onLoadRequested();
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,11 +388,50 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
updateCurrentItem();
|
||||
|
||||
addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0;
|
||||
|
||||
Scheduler.AddOnce(UpdateMods);
|
||||
}
|
||||
|
||||
private void updateCurrentItem()
|
||||
{
|
||||
Debug.Assert(client.Room != null);
|
||||
|
||||
var expectedSelectedItem = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId);
|
||||
|
||||
if (expectedSelectedItem == null)
|
||||
return;
|
||||
|
||||
// There's no reason to renew the selected item if its content hasn't changed.
|
||||
if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null)
|
||||
return;
|
||||
|
||||
// Clear the selected item while the lookup is performed, so components like the ready button can enter their disabled states.
|
||||
SelectedItem.Value = null;
|
||||
|
||||
if (expectedSelectedItem.Beatmap.Value == null)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var beatmap = await client.GetAPIBeatmap(expectedSelectedItem.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
expectedSelectedItem.Beatmap.Value = beatmap;
|
||||
|
||||
if (Room.Playlist.SingleOrDefault(i => i.ID == client.Room?.Settings.PlaylistItemId)?.Equals(expectedSelectedItem) == true)
|
||||
applyCurrentItem();
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
applyCurrentItem();
|
||||
|
||||
void applyCurrentItem() => SelectedItem.Value = expectedSelectedItem;
|
||||
}
|
||||
|
||||
private void handleRoomLost() => Schedule(() =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
@ -446,6 +484,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
if (!client.IsHost)
|
||||
{
|
||||
// todo: should handle this when the request queue is implemented.
|
||||
@ -454,7 +495,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset));
|
||||
this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
@ -226,8 +227,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
|
||||
public override bool OnBackButton()
|
||||
{
|
||||
// On a manual exit, set the player state back to idle.
|
||||
multiplayerClient.ChangeState(MultiplayerUserState.Idle);
|
||||
Debug.Assert(multiplayerClient.Room != null);
|
||||
|
||||
// On a manual exit, set the player back to idle unless gameplay has finished.
|
||||
if (multiplayerClient.Room.State != MultiplayerRoomState.Open)
|
||||
multiplayerClient.ChangeState(MultiplayerUserState.Idle);
|
||||
|
||||
return base.OnBackButton();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using ManagedBass.Fx;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -18,6 +19,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Audio.Effects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -58,6 +60,12 @@ namespace osu.Game.Screens.Play
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The player screen background, used to adjust appearance on failing.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public BackgroundScreen Background { private get; set; }
|
||||
|
||||
public FailAnimation(DrawableRuleset drawableRuleset)
|
||||
{
|
||||
this.drawableRuleset = drawableRuleset;
|
||||
@ -136,6 +144,9 @@ namespace osu.Game.Screens.Play
|
||||
Content.ScaleTo(0.85f, duration, Easing.OutQuart);
|
||||
Content.RotateTo(1, duration, Easing.OutQuart);
|
||||
Content.FadeColour(Color4.Gray, duration);
|
||||
|
||||
// Will be restored by `ApplyToBackground` logic in `SongSelect`.
|
||||
Background?.FadeColour(OsuColour.Gray(0.3f), 60);
|
||||
}
|
||||
|
||||
public void RemoveFilters(bool resetTrackFrequency = true)
|
||||
|
@ -921,6 +921,8 @@ namespace osu.Game.Screens.Play
|
||||
b.IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||
|
||||
b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
|
||||
|
||||
failAnimationLayer.Background = b;
|
||||
});
|
||||
|
||||
HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||
|
@ -228,10 +228,7 @@ namespace osu.Game.Screens.Play
|
||||
onlineBeatmapRequest.Success += beatmapSet => Schedule(() =>
|
||||
{
|
||||
this.beatmapSet = beatmapSet;
|
||||
beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet)
|
||||
{
|
||||
Expanded = { Disabled = true }
|
||||
};
|
||||
beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet, allowExpansion: false);
|
||||
checkForAutomaticDownload();
|
||||
});
|
||||
|
||||
|
@ -33,10 +33,8 @@ namespace osu.Game.Storyboards
|
||||
|
||||
foreach (var l in loops)
|
||||
{
|
||||
if (!(l.EarliestDisplayedTime is double lEarliest))
|
||||
continue;
|
||||
|
||||
earliestStartTime = Math.Min(earliestStartTime, lEarliest);
|
||||
if (l.EarliestDisplayedTime is double loopEarliestDisplayTime)
|
||||
earliestStartTime = Math.Min(earliestStartTime, l.LoopStartTime + loopEarliestDisplayTime);
|
||||
}
|
||||
|
||||
if (earliestStartTime < double.MaxValue)
|
||||
|
@ -128,6 +128,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
case MultiplayerRoomState.WaitingForLoad:
|
||||
if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad))
|
||||
{
|
||||
var loadedUsers = Room.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray();
|
||||
|
||||
if (loadedUsers.Length == 0)
|
||||
{
|
||||
// all users have bailed from the load sequence. cancel the game start.
|
||||
ChangeRoomState(MultiplayerRoomState.Open);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded))
|
||||
ChangeUserState(u.UserID, MultiplayerUserState.Playing);
|
||||
|
||||
@ -143,8 +152,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay))
|
||||
ChangeUserState(u.UserID, MultiplayerUserState.Results);
|
||||
ChangeRoomState(MultiplayerRoomState.Open);
|
||||
|
||||
ChangeRoomState(MultiplayerRoomState.Open);
|
||||
((IMultiplayerClient)this).ResultsReady();
|
||||
|
||||
FinishCurrentItem().Wait();
|
||||
@ -242,6 +251,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
public override Task ChangeState(MultiplayerUserState newState)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
if (newState == MultiplayerUserState.Idle && LocalUser?.State == MultiplayerUserState.WaitingForLoad)
|
||||
return Task.CompletedTask;
|
||||
|
||||
ChangeUserState(api.LocalUser.Value.Id, newState);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -303,6 +317,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return ((IMultiplayerClient)this).LoadRequested();
|
||||
}
|
||||
|
||||
public override Task AbortGameplay()
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(LocalUser != null);
|
||||
|
||||
ChangeUserState(LocalUser.UserID, MultiplayerUserState.Idle);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
@ -376,7 +400,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId);
|
||||
|
||||
protected override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
|
||||
public override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist)
|
||||
.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet
|
||||
|
@ -36,8 +36,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.7.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1210.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1215.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
|
||||
<PackageReference Include="Sentry" Version="3.12.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
|
@ -60,8 +60,8 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1210.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1215.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||
<PropertyGroup>
|
||||
@ -83,7 +83,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1210.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1215.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user