Merge branch 'master' into localise-beatmap-set-overlay

This commit is contained in:
Salman Ahmed
2021-08-22 10:53:31 +03:00
203 changed files with 4364 additions and 2866 deletions

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.820.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.819.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -5,23 +5,23 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Desktop.Windows namespace osu.Desktop.Windows
{ {
public class GameplayWinKeyBlocker : Component public class GameplayWinKeyBlocker : Component
{ {
private Bindable<bool> disableWinKey; private Bindable<bool> disableWinKey;
private Bindable<bool> localUserPlaying; private IBindable<bool> localUserPlaying;
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGame game, OsuConfigManager config) private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
{ {
localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking()); localUserPlaying.BindValueChanged(_ => updateBlocking());
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey); disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);

View File

@ -0,0 +1,34 @@
// 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 BenchmarkDotNet.Attributes;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Benchmarks
{
public class BenchmarkMod : BenchmarkTest
{
private OsuModDoubleTime mod;
[Params(1, 10, 100)]
public int Times { get; set; }
[GlobalSetup]
public void GlobalSetup()
{
mod = new OsuModDoubleTime();
}
[Benchmark]
public int ModHashCode()
{
var hashCode = new HashCode();
for (int i = 0; i < Times; i++)
hashCode.Add(mod);
return hashCode.ToHashCode();
}
}
}

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{ {
public class Movement : StrainSkill public class Movement : StrainDecaySkill
{ {
private const float absolute_player_positioning_error = 16f; private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f; private const float normalized_hitobject_radius = 41.0f;

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{ {
public class Strain : StrainSkill public class Strain : StrainDecaySkill
{ {
private const double individual_decay_base = 0.125; private const double individual_decay_base = 0.125;
private const double overall_decay_base = 0.30; private const double overall_decay_base = 0.30;
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
return individualStrain + overallStrain - CurrentStrain; return individualStrain + overallStrain - CurrentStrain;
} }
protected override double GetPeakStrain(double offset) protected override double CalculateInitialStrain(double offset)
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base) => applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base); + applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);

View File

@ -10,7 +10,7 @@ using osu.Framework.Utils;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{ {
public abstract class OsuStrainSkill : StrainSkill public abstract class OsuStrainSkill : StrainDecaySkill
{ {
/// <summary> /// <summary>
/// The number of sections with the highest strains, which the peak strain reductions will apply to. /// The number of sections with the highest strains, which the peak strain reductions will apply to.

View File

@ -63,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
return; return;
if (hitObject is DrawableHitCircle circle) if (hitObject is DrawableHitCircle circle)
{
using (circle.BeginAbsoluteSequence(circle.HitStateUpdateTime))
{ {
circle.ApproachCircle circle.ApproachCircle
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4) .FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
@ -70,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
} }
}
if (hitObject is IHasMainCirclePiece mainPieceContainer) if (hitObject is IHasMainCirclePiece mainPieceContainer)
{ {

View File

@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override bool InterpolateMovements => !disjointTrail; protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1); protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
protected override bool AvoidDrawingNearCursor => !disjointTrail;
protected override void Update() protected override void Update()
{ {

View File

@ -138,6 +138,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected virtual bool InterpolateMovements => true; protected virtual bool InterpolateMovements => true;
protected virtual float IntervalMultiplier => 1.0f; protected virtual float IntervalMultiplier => 1.0f;
protected virtual bool AvoidDrawingNearCursor => false;
private Vector2? lastPosition; private Vector2? lastPosition;
private readonly InputResampler resampler = new InputResampler(); private readonly InputResampler resampler = new InputResampler();
@ -171,8 +172,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Vector2 direction = diff / distance; Vector2 direction = diff / distance;
float interval = partSize.X / 2.5f * IntervalMultiplier; float interval = partSize.X / 2.5f * IntervalMultiplier;
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
for (float d = interval; d < distance; d += interval) for (float d = interval; d < stopAt; d += interval)
{ {
lastPosition = pos1 + direction * d; lastPosition = pos1 + direction * d;
addPart(lastPosition.Value); addPart(lastPosition.Value);

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Calculates the colour coefficient of taiko difficulty. /// Calculates the colour coefficient of taiko difficulty.
/// </summary> /// </summary>
public class Colour : StrainSkill public class Colour : StrainDecaySkill
{ {
protected override double SkillMultiplier => 1; protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4; protected override double StrainDecayBase => 0.4;

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Calculates the rhythm coefficient of taiko difficulty. /// Calculates the rhythm coefficient of taiko difficulty.
/// </summary> /// </summary>
public class Rhythm : StrainSkill public class Rhythm : StrainDecaySkill
{ {
protected override double SkillMultiplier => 10; protected override double SkillMultiplier => 10;
protected override double StrainDecayBase => 0; protected override double StrainDecayBase => 0;

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <remarks> /// <remarks>
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit). /// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
/// </remarks> /// </remarks>
public class Stamina : StrainSkill public class Stamina : StrainDecaySkill
{ {
protected override double SkillMultiplier => 1; protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4; protected override double StrainDecayBase => 0.4;

View File

@ -1,56 +0,0 @@
// 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.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class BeatmapDifficultyCacheTest
{
[Test]
public void TestKeyEqualsWithDifferentModInstances()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
Assert.That(key1, Is.EqualTo(key2));
}
[Test]
public void TestKeyEqualsWithDifferentModOrder()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
Assert.That(key1, Is.EqualTo(key2));
}
[TestCase(1.3, DifficultyRating.Easy)]
[TestCase(1.993, DifficultyRating.Easy)]
[TestCase(1.998, DifficultyRating.Normal)]
[TestCase(2.4, DifficultyRating.Normal)]
[TestCase(2.693, DifficultyRating.Normal)]
[TestCase(2.698, DifficultyRating.Hard)]
[TestCase(3.5, DifficultyRating.Hard)]
[TestCase(3.993, DifficultyRating.Hard)]
[TestCase(3.997, DifficultyRating.Insane)]
[TestCase(5.0, DifficultyRating.Insane)]
[TestCase(5.292, DifficultyRating.Insane)]
[TestCase(5.297, DifficultyRating.Expert)]
[TestCase(6.2, DifficultyRating.Expert)]
[TestCase(6.493, DifficultyRating.Expert)]
[TestCase(6.498, DifficultyRating.ExpertPlus)]
[TestCase(8.3, DifficultyRating.ExpertPlus)]
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
{
var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
Assert.AreEqual(expectedBracket, actualBracket);
}
}
}

View File

@ -0,0 +1,146 @@
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Beatmaps
{
[HeadlessTest]
public class TestSceneBeatmapDifficultyCache : OsuTestScene
{
public const double BASE_STARS = 5.55;
private BeatmapSetInfo importedSet;
[Resolved]
private BeatmapManager beatmaps { get; set; }
private TestBeatmapDifficultyCache difficultyCache;
private IBindable<StarDifficulty?> starDifficultyBindable;
[BackgroundDependencyLoader]
private void load(OsuGameBase osu)
{
importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup difficulty cache", () =>
{
SelectedMods.Value = Array.Empty<Mod>();
Child = difficultyCache = new TestBeatmapDifficultyCache();
starDifficultyBindable = difficultyCache.GetBindableDifficulty(importedSet.Beatmaps.First());
});
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value?.Stars == BASE_STARS);
}
[Test]
public void TestStarDifficultyChangesOnModSettings()
{
OsuModDoubleTime dt = null;
AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.5);
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.25);
AddStep("change selected mod to NC", () => SelectedMods.Value = new[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.75);
}
[Test]
public void TestKeyEqualsWithDifferentModInstances()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
}
[Test]
public void TestKeyEqualsWithDifferentModOrder()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
}
[Test]
public void TestKeyDoesntEqualWithDifferentModSettings()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
Assert.That(key1, Is.Not.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
}
[Test]
public void TestKeyEqualWithMatchingModSettings()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
}
[TestCase(1.3, DifficultyRating.Easy)]
[TestCase(1.993, DifficultyRating.Easy)]
[TestCase(1.998, DifficultyRating.Normal)]
[TestCase(2.4, DifficultyRating.Normal)]
[TestCase(2.693, DifficultyRating.Normal)]
[TestCase(2.698, DifficultyRating.Hard)]
[TestCase(3.5, DifficultyRating.Hard)]
[TestCase(3.993, DifficultyRating.Hard)]
[TestCase(3.997, DifficultyRating.Insane)]
[TestCase(5.0, DifficultyRating.Insane)]
[TestCase(5.292, DifficultyRating.Insane)]
[TestCase(5.297, DifficultyRating.Expert)]
[TestCase(6.2, DifficultyRating.Expert)]
[TestCase(6.493, DifficultyRating.Expert)]
[TestCase(6.498, DifficultyRating.ExpertPlus)]
[TestCase(8.3, DifficultyRating.ExpertPlus)]
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
{
var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
Assert.AreEqual(expectedBracket, actualBracket);
}
private class TestBeatmapDifficultyCache : BeatmapDifficultyCache
{
protected override Task<StarDifficulty> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
{
var rateAdjust = lookup.OrderedMods.OfType<ModRateAdjust>().SingleOrDefault();
if (rateAdjust != null)
return Task.FromResult(new StarDifficulty(BASE_STARS + rateAdjust.SpeedChange.Value, 0));
return Task.FromResult(new StarDifficulty(BASE_STARS, 0));
}
}
}
}

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Collections.IO namespace osu.Game.Tests.Collections.IO
@ -127,7 +128,7 @@ namespace osu.Game.Tests.Collections.IO
[Test] [Test]
public async Task TestSaveAndReload() public async Task TestSaveAndReload()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload", bypassCleanup: true))
{ {
try try
{ {
@ -148,7 +149,7 @@ namespace osu.Game.Tests.Collections.IO
} }
} }
using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload")) using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload"))
{ {
try try
{ {

View File

@ -8,7 +8,7 @@ using osu.Framework.Input;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Tests.Visual.Navigation; using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Input namespace osu.Game.Tests.Input
{ {

View File

@ -6,10 +6,10 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.IO; using osu.Game.IO;
@ -278,7 +278,7 @@ namespace osu.Game.Tests.NonVisual
private static string getDefaultLocationFor(string testTypeName) private static string getDefaultLocationFor(string testTypeName)
{ {
string path = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testTypeName); string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, testTypeName);
if (Directory.Exists(path)) if (Directory.Exists(path))
Directory.Delete(path, true); Directory.Delete(path, true);
@ -288,7 +288,7 @@ namespace osu.Game.Tests.NonVisual
private string prepareCustomPath(string suffix = "") private string prepareCustomPath(string suffix = "")
{ {
string path = Path.Combine(RuntimeInfo.StartupDirectory, $"custom-path{suffix}"); string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path{suffix}");
if (Directory.Exists(path)) if (Directory.Exists(path))
Directory.Delete(path, true); Directory.Delete(path, true);
@ -308,6 +308,19 @@ namespace osu.Game.Tests.NonVisual
InitialStorage = new DesktopStorage(defaultStorageLocation, this); InitialStorage = new DesktopStorage(defaultStorageLocation, this);
InitialStorage.DeleteDirectory(string.Empty); InitialStorage.DeleteDirectory(string.Empty);
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
try
{
// the storage may have changed from the initial location.
// this handles cleanup of the initial location.
InitialStorage.DeleteDirectory(string.Empty);
}
catch { }
}
} }
} }
} }

View File

@ -0,0 +1,72 @@
// 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 MessagePack;
using NUnit.Framework;
using osu.Game.Online;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Tests.Online
{
[TestFixture]
public class TestMultiplayerMessagePackSerialization
{
[Test]
public void TestSerialiseRoom()
{
var room = new MultiplayerRoom(1)
{
MatchState = new TeamVersusRoomState()
};
var serialized = MessagePackSerializer.Serialize(room);
var deserialized = MessagePackSerializer.Deserialize<MultiplayerRoom>(serialized);
Assert.IsTrue(deserialized.MatchState is TeamVersusRoomState);
}
[Test]
public void TestSerialiseUserStateExpected()
{
var state = new TeamVersusUserState();
var serialized = MessagePackSerializer.Serialize(typeof(MatchUserState), state);
var deserialized = MessagePackSerializer.Deserialize<MatchUserState>(serialized);
Assert.IsTrue(deserialized is TeamVersusUserState);
}
[Test]
public void TestSerialiseUnionFailsWithSingalR()
{
var state = new TeamVersusUserState();
// SignalR serialises using the actual type, rather than a base specification.
var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state);
// works with explicit type specified.
MessagePackSerializer.Deserialize<TeamVersusUserState>(serialized);
// fails with base (union) type.
Assert.Throws<MessagePackSerializationException>(() => MessagePackSerializer.Deserialize<MatchUserState>(serialized));
}
[Test]
public void TestSerialiseUnionSucceedsWithWorkaround()
{
var state = new TeamVersusUserState();
// SignalR serialises using the actual type, rather than a base specification.
var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state, SignalRUnionWorkaroundResolver.OPTIONS);
// works with explicit type specified.
MessagePackSerializer.Deserialize<TeamVersusUserState>(serialized);
// works with custom resolver.
var deserialized = MessagePackSerializer.Deserialize<MatchUserState>(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
Assert.IsTrue(deserialized is TeamVersusUserState);
}
}
}

View File

@ -50,10 +50,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(1)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(1));
// the first should be overwritten by the second import. // the first should be overwritten by the second import.
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
} }
finally finally
{ {
@ -76,10 +76,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(2)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
} }
finally finally
{ {
@ -101,10 +101,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(2)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
} }
finally finally
{ {

View File

@ -78,6 +78,24 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
} }
[Test]
public void TestClampWhenSeekOutsideBeatmapBounds()
{
AddStep("stop clock", Clock.Stop);
AddStep("seek before start time", () => Clock.Seek(-1000));
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
Beatmap.Disabled = false; Beatmap.Disabled = false;

View File

@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{ {
private OsuConfigManager localConfig;
private HUDOverlay hudOverlay; private HUDOverlay hudOverlay;
[Cached] [Cached]
@ -31,8 +33,14 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.KeyCounter; private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First(); private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
[Resolved] [BackgroundDependencyLoader]
private OsuConfigManager config { get; set; } private void load()
{
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
}
[SetUp]
public void SetUp() => Schedule(() => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
[Test] [Test]
public void TestComboCounterIncrementing() public void TestComboCounterIncrementing()
@ -85,11 +93,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
createNew(); createNew();
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@ -98,37 +102,28 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
} }
[Test] [Test]
public void TestExternalHideDoesntAffectConfig() public void TestExternalHideDoesntAffectConfig()
{ {
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
createNew(); createNew();
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)); AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)); AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
} }
[Test] [Test]
public void TestChangeHUDVisibilityOnHiddenKeyCounter() public void TestChangeHUDVisibilityOnHiddenKeyCounter()
{ {
bool keyCounterVisibleValue = false;
createNew(); createNew();
AddStep("save keycounter visible value", () => keyCounterVisibleValue = config.Get<bool>(OsuSetting.KeyOverlay));
AddStep("set keycounter visible false", () => AddStep("hide key overlay", () =>
{ {
config.SetValue(OsuSetting.KeyOverlay, false); localConfig.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false; hudOverlay.KeyCounter.AlwaysVisible.Value = false;
}); });
@ -139,24 +134,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
} }
[Test] [Test]
public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad() public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
{ {
HUDVisibilityMode originalConfigValue = default; AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
createNew(); createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded); AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded);
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
} }
private void createNew(Action<HUDOverlay> action = null) private void createNew(Action<HUDOverlay> action = null)
@ -175,5 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
Child = hudOverlay; Child = hudOverlay;
}); });
} }
protected override void Dispose(bool isDisposing)
{
localConfig?.Dispose();
base.Dispose(isDisposing);
}
} }
} }

View File

@ -3,7 +3,6 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -66,7 +65,6 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class OverlayTestPlayer : TestPlayer protected class OverlayTestPlayer : TestPlayer
{ {
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
} }
} }
} }

View File

@ -10,7 +10,6 @@ using osu.Game.Database;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Navigation;
namespace osu.Game.Tests.Visual.Menus namespace osu.Game.Tests.Visual.Menus
{ {

View File

@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Tests.Visual.Navigation;
namespace osu.Game.Tests.Visual.Menus namespace osu.Game.Tests.Visual.Menus
{ {
@ -22,21 +25,48 @@ namespace osu.Game.Tests.Visual.Menus
[Test] [Test]
public void TestScreenOffsettingOnSettingsOverlay() public void TestScreenOffsettingOnSettingsOverlay()
{ {
foreach (var scalingMode in Enum.GetValues(typeof(ScalingMode)).Cast<ScalingMode>())
{
AddStep($"set scaling mode to {scalingMode}", () =>
{
Game.LocalConfig.SetValue(OsuSetting.Scaling, scalingMode);
if (scalingMode != ScalingMode.Off)
{
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeX, 0.5f);
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeY, 0.5f);
}
});
AddStep("open settings", () => Game.Settings.Show()); AddStep("open settings", () => Game.Settings.Show());
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO); AddUntilStep("right screen offset applied", () => Precision.AlmostEquals(Game.ScreenOffsetContainer.X, SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO));
AddStep("hide settings", () => Game.Settings.Hide()); AddStep("hide settings", () => Game.Settings.Hide());
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f); AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
} }
}
[Test] [Test]
public void TestScreenOffsettingOnNotificationOverlay() public void TestScreenOffsettingOnNotificationOverlay()
{ {
foreach (var scalingMode in Enum.GetValues(typeof(ScalingMode)).Cast<ScalingMode>())
{
if (scalingMode != ScalingMode.Off)
{
AddStep($"set scaling mode to {scalingMode}", () =>
{
Game.LocalConfig.SetValue(OsuSetting.Scaling, scalingMode);
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeX, 0.5f);
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeY, 0.5f);
});
}
AddStep("open notifications", () => Game.Notifications.Show()); AddStep("open notifications", () => Game.Notifications.Show());
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO); AddUntilStep("right screen offset applied", () => Precision.AlmostEquals(Game.ScreenOffsetContainer.X, -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO));
AddStep("hide notifications", () => Game.Notifications.Hide()); AddStep("hide notifications", () => Game.Notifications.Hide());
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f); AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
} }
} }
}
} }

View File

@ -10,11 +10,11 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osu.Game.Users; using osu.Game.Users;
@ -108,12 +108,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
EndDate = { Value = DateTimeOffset.Now }, EndDate = { Value = DateTimeOffset.Now },
}), }),
createDrawableRoom(new Room createDrawableRoom(new Room
{
Name = { Value = "Room 4 (realtime)" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime },
}),
createDrawableRoom(new Room
{ {
Name = { Value = "Room 4 (spotlight)" }, Name = { Value = "Room 4 (spotlight)" },
Status = { Value = new RoomStatusOpen() }, Status = { Value = new RoomStatusOpen() },
@ -134,7 +128,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Name = { Value = "Room with password" }, Name = { Value = "Room with password" },
Status = { Value = new RoomStatusOpen() }, Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime }, Type = { Value = MatchType.HeadToHead },
})); }));
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha)); AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
@ -159,10 +153,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
})); }));
} }
var drawableRoom = new DrawableRoom(room) { MatchingFilter = true }; return new DrawableLoungeRoom(room) { MatchingFilter = true };
drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
return drawableRoom;
} }
} }
} }

View File

@ -0,0 +1,131 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneGameplayChatDisplay : MultiplayerTestScene
{
private GameplayChatDisplay chatDisplay;
[Cached(typeof(ILocalUserPlayInfo))]
private ILocalUserPlayInfo localUserInfo;
private readonly Bindable<bool> localUserPlaying = new Bindable<bool>();
private TextBox textBox => chatDisplay.ChildrenOfType<TextBox>().First();
public TestSceneGameplayChatDisplay()
{
var mockLocalUserInfo = new Mock<ILocalUserPlayInfo>();
mockLocalUserInfo.SetupGet(i => i.IsPlaying).Returns(localUserPlaying);
localUserInfo = mockLocalUserInfo.Object;
}
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
});
AddStep("expand", () => chatDisplay.Expanded.Value = true);
}
[Test]
public void TestCantClickWhenPlaying()
{
setLocalUserPlaying(true);
AddStep("attempt focus chat", () =>
{
InputManager.MoveMouseTo(textBox);
InputManager.Click(MouseButton.Left);
});
assertChatFocused(false);
}
[Test]
public void TestFocusDroppedWhenPlaying()
{
assertChatFocused(false);
AddStep("focus chat", () =>
{
InputManager.MoveMouseTo(textBox);
InputManager.Click(MouseButton.Left);
});
setLocalUserPlaying(true);
assertChatFocused(false);
// should still stay non-focused even after entering a new break section.
setLocalUserPlaying(false);
assertChatFocused(false);
}
[Test]
public void TestFocusOnTabKeyWhenExpanded()
{
setLocalUserPlaying(true);
assertChatFocused(false);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(true);
}
[Test]
public void TestFocusOnTabKeyWhenNotExpanded()
{
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(true);
AddUntilStep("is visible", () => chatDisplay.IsPresent);
AddStep("press enter", () => InputManager.Key(Key.Enter));
assertChatFocused(false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
}
[Test]
public void TestFocusToggleViaAction()
{
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(true);
AddUntilStep("is visible", () => chatDisplay.IsPresent);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
}
private void assertChatFocused(bool isFocused) =>
AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
private void setLocalUserPlaying(bool playing) =>
AddStep($"local user {(playing ? "playing" : "not playing")}", () => localUserPlaying.Value = playing);
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Testing;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input; using osuTK.Input;
@ -17,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
{ {
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private RoomsContainer container; private RoomsContainer container;
@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add rooms", () => RoomManager.AddRooms(3)); AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3); AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault())); AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2); AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var room = RoomManager.Rooms[1]; var room = RoomManager.Rooms[1];
RoomManager.RemoveRoom(room); RoomManager.RemoveRoom(room);
RoomManager.AddRoom(room); RoomManager.AddOrUpdateRoom(room);
}); });
AddAssert("no selection", () => checkRoomSelected(null)); AddAssert("no selection", () => checkRoomSelected(null));
@ -115,11 +116,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
AddStep("filter one room", () => container.Filter(new FilterCriteria { SearchString = "1" })); AddStep("filter one room", () => container.Filter.Value = new FilterCriteria { SearchString = "1" });
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1); AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
AddStep("remove filter", () => container.Filter(null)); AddStep("remove filter", () => container.Filter.Value = null);
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
} }
@ -131,13 +132,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
// Todo: What even is this case...? // Todo: What even is this case...?
AddStep("set empty filter criteria", () => container.Filter(null)); AddStep("set empty filter criteria", () => container.Filter.Value = null);
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo })); AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo });
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo })); AddStep("filter catch rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo });
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
} }
@ -150,6 +151,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
private Room getRoomInFlow(int index) => private Room getRoomInFlow(int index) =>
(container.ChildrenOfType<FillFlowContainer<DrawableRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room; (container.ChildrenOfType<FillFlowContainer<DrawableLoungeRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
} }
} }

View File

@ -1,55 +0,0 @@
// 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.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchHeader : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room
{
Name = { Value = "A very awesome room" },
Host = { Value = new User { Id = 2, Username = "peppy" } },
Playlist =
{
new PlaylistItem
{
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Title = "Title",
Artist = "Artist",
AuthorString = "Author",
},
Version = "Version",
Ruleset = new OsuRuleset().RulesetInfo
}
},
RequiredMods =
{
new OsuModDoubleTime(),
new OsuModNoFail(),
new OsuModRelax(),
}
}
}
};
Child = new Header();
});
}
}

View File

@ -6,13 +6,17 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Users; using osu.Game.Users;
@ -23,6 +27,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved] [Resolved]
private OsuGameBase game { get; set; } private OsuGameBase game { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved] [Resolved]
private BeatmapManager beatmapManager { get; set; } private BeatmapManager beatmapManager { get; set; }
@ -80,6 +87,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20); AddWaitStep("wait a bit", 20);
} }
[Test]
public void TestSpectatorPlayerInteractiveElementsHidden()
{
HUDVisibilityMode originalConfigValue = default;
AddStep("get original config hud visibility", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set config hud visibility to always", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen(false);
AddUntilStep("wait for player loaders", () => this.ChildrenOfType<PlayerLoader>().Count() == 2);
AddAssert("all player loader settings hidden", () => this.ChildrenOfType<PlayerLoader>().All(l => !l.ChildrenOfType<FillFlowContainer<PlayerSettingsGroup>>().Any()));
AddUntilStep("wait for players to load", () => spectatorScreen.AllPlayersLoaded);
// components wrapped in skinnable target containers load asynchronously, potentially taking more than one frame to load.
// therefore use until step rather than direct assert to account for that.
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p =>
!p.ChildrenOfType<PlayerSettingsOverlay>().Any() &&
!p.ChildrenOfType<HoldForMenuButton>().Any() &&
p.ChildrenOfType<SongProgressBar>().SingleOrDefault()?.ShowHandle == false));
AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test] [Test]
public void TestTeamDisplay() public void TestTeamDisplay()
{ {

View File

@ -44,6 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayer multiplayerScreen; private TestMultiplayer multiplayerScreen;
private TestMultiplayerClient client; private TestMultiplayerClient client;
private TestRequestHandlingMultiplayerRoomManager roomManager => multiplayerScreen.RoomManager;
[Cached(typeof(UserLookupCache))] [Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache(); private UserLookupCache lookupCache = new TestUserLookupCache();
@ -68,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("load dependencies", () => AddStep("load dependencies", () =>
{ {
client = new TestMultiplayerClient(multiplayerScreen.RoomManager); client = new TestMultiplayerClient(roomManager);
// The screen gets suspended so it stops receiving updates. // The screen gets suspended so it stops receiving updates.
Child = client; Child = client;
@ -132,11 +134,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestExitMidJoin() public void TestExitMidJoin()
{ {
Room room = null;
AddStep("create room", () => AddStep("create room", () =>
{ {
room = new Room roomManager.AddServerSideRoom(new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
Playlist = Playlist =
@ -147,14 +147,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
Ruleset = { Value = new OsuRuleset().RulesetInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo },
} }
} }
}; });
}); });
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria()); AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room and immediately exit", () => AddStep("join room and immediately exit select", () =>
{ {
multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room); InputManager.Key(Key.Enter);
Schedule(() => Stack.CurrentScreen.Exit()); Schedule(() => Stack.CurrentScreen.Exit());
}); });
} }
@ -164,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
AddStep("create room", () => AddStep("create room", () =>
{ {
multiplayerScreen.RoomManager.AddRoom(new Room roomManager.AddServerSideRoom(new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
Playlist = Playlist =
@ -178,7 +180,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
}); });
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria()); AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter)); AddStep("join room", () => InputManager.Key(Key.Enter));
@ -211,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
AddStep("create room", () => AddStep("create room", () =>
{ {
multiplayerScreen.RoomManager.AddRoom(new Room roomManager.AddServerSideRoom(new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
Password = { Value = "password" }, Password = { Value = "password" },
@ -226,12 +230,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
}); });
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria()); AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter)); AddStep("join room", () => InputManager.Key(Key.Enter));
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null; DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password"); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick()); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());

View File

@ -9,7 +9,6 @@ using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input; using osuTK.Input;
@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
{ {
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen; private LoungeSubScreen loungeScreen;
@ -59,20 +58,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().Any()); AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddStep("exit screen", () => Stack.Exit()); AddStep("exit screen", () => Stack.Exit());
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().Any()); AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
} }
[Test] [Test]
public void TestJoinRoomWithPassword() public void TestJoinRoomWithPassword()
{ {
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null; DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password"); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick()); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
@ -83,12 +82,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestJoinRoomWithPasswordViaKeyboardOnly() public void TestJoinRoomWithPasswordViaKeyboardOnly()
{ {
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null; DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password"); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press enter", () => InputManager.Key(Key.Enter)); AddStep("press enter", () => InputManager.Key(Key.Enter));

View File

@ -59,23 +59,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for load", () => screen.IsCurrentScreen()); AddUntilStep("wait for load", () => screen.IsCurrentScreen());
} }
[Test]
public void TestSettingValidity()
{
AddAssert("create button not enabled", () => !this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
});
});
AddAssert("create button enabled", () => this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
}
[Test] [Test]
public void TestCreatedRoom() public void TestCreatedRoom()
{ {
@ -97,6 +80,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => Client.Room != null); AddUntilStep("wait for join", () => Client.Room != null);
} }
[Test]
public void TestSettingValidity()
{
AddAssert("create button not enabled", () => !this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
});
});
AddAssert("create button enabled", () => this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
}
[Test] [Test]
public void TestStartMatchWhileSpectating() public void TestStartMatchWhileSpectating()
{ {

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerPlayer : MultiplayerTestScene
{
private MultiplayerPlayer player;
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("set beatmap", () =>
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
});
AddStep("initialise gameplay", () =>
{
Stack.Push(player = new MultiplayerPlayer(Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
});
}
[Test]
public void TestGameplay()
{
AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value);
}
}
}

View File

@ -1,157 +0,0 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
[HeadlessTest]
public class TestSceneMultiplayerRoomManager : MultiplayerTestScene
{
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
public TestSceneMultiplayerRoomManager()
: base(false)
{
}
[Test]
public void TestPollsInitially()
{
AddStep("create room manager with a few rooms", () =>
{
RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
RoomManager.PartRoom();
RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
RoomManager.PartRoom();
RoomManager.ClearRooms();
});
AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
}
[Test]
public void TestRoomsClearedOnDisconnection()
{
AddStep("create room manager with a few rooms", () =>
{
RoomManager.CreateRoom(createRoom());
RoomManager.PartRoom();
RoomManager.CreateRoom(createRoom());
RoomManager.PartRoom();
});
AddStep("disconnect", () => Client.Disconnect());
AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
}
[Test]
public void TestRoomsPolledOnReconnect()
{
AddStep("create room manager with a few rooms", () =>
{
RoomManager.CreateRoom(createRoom());
RoomManager.PartRoom();
RoomManager.CreateRoom(createRoom());
RoomManager.PartRoom();
});
AddStep("disconnect", () => Client.Disconnect());
AddStep("connect", () => Client.Connect());
AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
}
[Test]
public void TestRoomsNotPolledWhenJoined()
{
AddStep("create room manager with a room", () =>
{
RoomManager.CreateRoom(createRoom());
RoomManager.ClearRooms();
});
AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
}
[Test]
public void TestMultiplayerRoomJoinedWhenCreated()
{
AddStep("create room manager with a room", () =>
{
RoomManager.CreateRoom(createRoom());
});
AddUntilStep("multiplayer room joined", () => Client.Room != null);
}
[Test]
public void TestMultiplayerRoomPartedWhenAPIRoomParted()
{
AddStep("create room manager with a room", () =>
{
RoomManager.CreateRoom(createRoom());
RoomManager.PartRoom();
});
AddAssert("multiplayer room parted", () => Client.Room == null);
}
[Test]
public void TestMultiplayerRoomJoinedWhenAPIRoomJoined()
{
AddStep("create room manager with a room", () =>
{
var r = createRoom();
RoomManager.CreateRoom(r);
RoomManager.PartRoom();
RoomManager.JoinRoom(r);
});
AddUntilStep("multiplayer room joined", () => Client.Room != null);
}
private Room createRoom(Action<Room> initFunc = null)
{
var room = new Room
{
Name =
{
Value = "test room"
},
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
Ruleset = { Value = Ruleset.Value }
}
}
};
initFunc?.Invoke(room);
return room;
}
private class TestDependencies : MultiplayerTestSceneDependencies
{
public TestDependencies()
{
// Need to set these values as early as possible.
RoomManager.TimeBetweenListingPolls.Value = 1;
RoomManager.TimeBetweenSelectionPolls.Value = 1;
}
}
}
}

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -88,8 +89,13 @@ namespace osu.Game.Tests.Visual.Navigation
[Resolved] [Resolved]
private OsuGameBase gameBase { get; set; } private OsuGameBase gameBase { get; set; }
[BackgroundDependencyLoader] [Resolved]
private void load(GameHost host) private GameHost host { get; set; }
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create game", () =>
{ {
game = new OsuGame(); game = new OsuGame();
game.SetHost(host); game.SetHost(host);
@ -103,6 +109,7 @@ namespace osu.Game.Tests.Visual.Navigation
}, },
game game
}; };
});
AddUntilStep("wait for load", () => game.IsLoaded); AddUntilStep("wait for load", () => game.IsLoaded);
} }

View File

@ -9,9 +9,12 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
public class TestSettingsMigration : OsuGameTestScene public class TestSettingsMigration : OsuGameTestScene
{ {
public override void RecycleLocalStorage() public override void RecycleLocalStorage(bool isDisposing)
{ {
base.RecycleLocalStorage(); base.RecycleLocalStorage(isDisposing);
if (isDisposing)
return;
using (var config = new OsuConfigManager(LocalStorage)) using (var config = new OsuConfigManager(LocalStorage))
{ {

View File

@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{ {
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager; protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen; private LoungeSubScreen loungeScreen;
@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddStep("add rooms", () => RoomManager.AddRooms(30)); AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0])); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
@ -53,6 +54,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void TestScrollSelectedIntoView() public void TestScrollSelectedIntoView()
{ {
AddStep("add rooms", () => RoomManager.AddRooms(30)); AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0])); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));

View File

@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent); AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent);
} }
private class TestRoomSettings : PlaylistsMatchSettingsOverlay private class TestRoomSettings : PlaylistsRoomSettingsOverlay
{ {
public TriangleButton ApplyButton => ((MatchSettings)Settings).ApplyButton; public TriangleButton ApplyButton => ((MatchSettings)Settings).ApplyButton;
@ -141,6 +141,12 @@ namespace osu.Game.Tests.Visual.Playlists
public IBindableList<Room> Rooms => null; public IBindableList<Room> Rooms => null;
public void AddOrUpdateRoom(Room room) => throw new NotImplementedException();
public void RemoveRoom(Room room) => throw new NotImplementedException();
public void ClearRooms() => throw new NotImplementedException();
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{ {
if (CreateRequested == null) if (CreateRequested == null)

View File

@ -11,7 +11,6 @@ using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -19,7 +18,6 @@ using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Playlists namespace osu.Game.Tests.Visual.Playlists
@ -36,18 +34,6 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
}
return false;
};
} }
[SetUpSteps] [SetUpSteps]
@ -66,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
SelectedRoom.Value.RoomID.Value = 1; SelectedRoom.Value.RoomID.Value = 1;
SelectedRoom.Value.Name.Value = "my awesome room"; SelectedRoom.Value.Name.Value = "my awesome room";
SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value); SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value);
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
SelectedRoom.Value.Playlist.Add(new PlaylistItem SelectedRoom.Value.Playlist.Add(new PlaylistItem
@ -86,7 +72,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("set room properties", () => AddStep("set room properties", () =>
{ {
SelectedRoom.Value.Name.Value = "my awesome room"; SelectedRoom.Value.Name.Value = "my awesome room";
SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.Playlist.Add(new PlaylistItem SelectedRoom.Value.Playlist.Add(new PlaylistItem
{ {
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
@ -96,7 +82,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("move mouse to create button", () => AddStep("move mouse to create button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsMatchSettingsOverlay.CreateRoomButton>().Single()); InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
}); });
AddStep("click", () => InputManager.Click(MouseButton.Left)); AddStep("click", () => InputManager.Click(MouseButton.Left));
@ -137,7 +123,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("load room", () => AddStep("load room", () =>
{ {
SelectedRoom.Value.Name.Value = "my awesome room"; SelectedRoom.Value.Name.Value = "my awesome room";
SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" }; SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.Playlist.Add(new PlaylistItem SelectedRoom.Value.Playlist.Add(new PlaylistItem
{ {
Beatmap = { Value = importedSet.Beatmaps[0] }, Beatmap = { Value = importedSet.Beatmaps[0] },
@ -147,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("create room", () => AddStep("create room", () =>
{ {
InputManager.MoveMouseTo(match.ChildrenOfType<PlaylistsMatchSettingsOverlay.CreateRoomButton>().Single()); InputManager.MoveMouseTo(match.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });

View File

@ -1,65 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Screens.Ranking.Expanded;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneStarRatingDisplay : OsuTestScene
{
[Test]
public void TestDisplay()
{
AddStep("load displays", () => Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ChildrenEnumerable = new[]
{
1.23,
2.34,
3.45,
4.56,
5.67,
6.78,
10.11,
}.Select(starRating => new StarRatingDisplay(new StarDifficulty(starRating, 0))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
})
});
}
[Test]
public void TestChangingStarRatingDisplay()
{
StarRatingDisplay starRating = null;
AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(3f),
});
AddRepeatStep("set random value", () =>
{
starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), 1);
}, 10);
AddSliderStep("set exact stars", 0.0, 11.0, 5.55, d =>
{
if (starRating != null)
starRating.Current.Value = new StarDifficulty(d, 1);
});
}
}
}

View File

@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Settings
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {
AddUntilStep("wait for load", () => panel.ChildrenOfType<GlobalKeyBindingsSection>().Any());
AddStep("Scroll to top", () => panel.ChildrenOfType<SettingsPanel.SettingsSectionsContainer>().First().ScrollToTop()); AddStep("Scroll to top", () => panel.ChildrenOfType<SettingsPanel.SettingsSectionsContainer>().First().ScrollToTop());
AddWaitStep("wait for scroll", 5); AddWaitStep("wait for scroll", 5);
} }

View File

@ -76,5 +76,23 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("restore default", () => sliderBar.Current.SetDefault()); AddStep("restore default", () => sliderBar.Current.SetDefault());
AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
} }
[Test]
public void TestWarningTextVisibility()
{
SettingsNumberBox numberBox = null;
AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox());
AddAssert("warning text not created", () => !numberBox.ChildrenOfType<SettingsNoticeText>().Any());
AddStep("set warning text", () => numberBox.WarningText = "this is a warning!");
AddAssert("warning text created", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 1);
AddStep("unset warning text", () => numberBox.WarningText = default);
AddAssert("warning text hidden", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 0);
AddStep("set warning text again", () => numberBox.WarningText = "another warning!");
AddAssert("warning text shown again", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 1);
}
} }
} }

View File

@ -4,6 +4,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Settings namespace osu.Game.Tests.Visual.Settings
@ -11,27 +12,39 @@ namespace osu.Game.Tests.Visual.Settings
[TestFixture] [TestFixture]
public class TestSceneSettingsPanel : OsuTestScene public class TestSceneSettingsPanel : OsuTestScene
{ {
private readonly SettingsPanel settings; private SettingsPanel settings;
private readonly DialogOverlay dialogOverlay; private DialogOverlay dialogOverlay;
public TestSceneSettingsPanel() [SetUpSteps]
public void SetUpSteps()
{ {
settings = new SettingsOverlay AddStep("create settings", () =>
{
settings?.Expire();
Add(settings = new SettingsOverlay
{ {
State = { Value = Visibility.Visible } State = { Value = Visibility.Visible }
};
Add(dialogOverlay = new DialogOverlay
{
Depth = -1
}); });
});
}
[Test]
public void ToggleVisibility()
{
AddWaitStep("wait some", 5);
AddToggleStep("toggle editor visibility", visible => settings.ToggleVisibility());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Dependencies.Cache(dialogOverlay); Add(dialogOverlay = new DialogOverlay
{
Depth = -1
});
Add(settings); Dependencies.Cache(dialogOverlay);
} }
} }
} }

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -65,6 +66,12 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("show", () => { infoWedge.Show(); }); AddStep("show", () => { infoWedge.Show(); });
AddSliderStep("change star difficulty", 0, 11.9, 5.55, v =>
{
foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType<IHasCurrentValue<StarDifficulty>>())
hasCurrentValue.Current.Value = new StarDifficulty(v, 0);
});
foreach (var rulesetInfo in rulesets.AvailableRulesets) foreach (var rulesetInfo in rulesets.AvailableRulesets)
{ {
var instance = rulesetInfo.CreateInstance(); var instance = rulesetInfo.CreateInstance();

View File

@ -14,7 +14,6 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Tests.Visual.Navigation;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Tests.Visual.SongSelect namespace osu.Game.Tests.Visual.SongSelect

View File

@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -174,6 +176,60 @@ namespace osu.Game.Tests.Visual.UserInterface
checkBindableAtValue("Circle Size", null); checkBindableAtValue("Circle Size", null);
} }
[Test]
public void TestModSettingChangeTracker()
{
ModSettingChangeTracker tracker = null;
Queue<Mod> settingsChangedQueue = null;
setBeatmapWithDifficultyParameters(5);
AddStep("add mod settings change tracker", () =>
{
settingsChangedQueue = new Queue<Mod>();
tracker = new ModSettingChangeTracker(modDifficultyAdjust.Yield())
{
SettingChanged = settingsChangedQueue.Enqueue
};
});
AddAssert("no settings changed", () => settingsChangedQueue.Count == 0);
setSliderValue("Circle Size", 3);
settingsChangedFired();
setSliderValue("Circle Size", 5);
checkBindableAtValue("Circle Size", 5);
settingsChangedFired();
AddStep("reset mod settings", () => modDifficultyAdjust.CircleSize.SetDefault());
checkBindableAtValue("Circle Size", null);
settingsChangedFired();
setExtendedLimits(true);
settingsChangedFired();
AddStep("dispose tracker", () =>
{
tracker.Dispose();
tracker = null;
});
void settingsChangedFired()
{
AddAssert("setting changed event fired", () =>
{
settingsChangedQueue.Dequeue();
return settingsChangedQueue.Count == 0;
});
}
}
private void resetToDefault(string name) private void resetToDefault(string name)
{ {
AddStep($"Reset {name} to default", () => AddStep($"Reset {name} to default", () =>

View File

@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osuTK.Graphics; using osuTK.Graphics;
@ -61,10 +62,12 @@ namespace osu.Game.Tests.Visual.UserInterface
)); ));
AddStep("scroll up", () => triggerUserScroll(1)); AddStep("scroll up", () => triggerUserScroll(1));
AddStep("scroll down", () => triggerUserScroll(-1)); AddStep("scroll down", () => triggerUserScroll(-1));
AddStep("scroll up a bit", () => triggerUserScroll(0.1f));
AddStep("scroll down a bit", () => triggerUserScroll(-0.1f));
} }
[Test] [Test]
public void TestCorrectSectionSelected() public void TestCorrectSelectionAndVisibleTop()
{ {
const int sections_count = 11; const int sections_count = 11;
float[] alternating = { 0.07f, 0.33f, 0.16f, 0.33f }; float[] alternating = { 0.07f, 0.33f, 0.16f, 0.33f };
@ -79,6 +82,12 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
AddStep($"scroll to section {scrollIndex + 1}", () => container.ScrollTo(container.Children[scrollIndex])); AddStep($"scroll to section {scrollIndex + 1}", () => container.ScrollTo(container.Children[scrollIndex]));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[scrollIndex]); AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[scrollIndex]);
AddUntilStep("section top is visible", () =>
{
float scrollPosition = container.ChildrenOfType<UserTrackingScrollContainer>().First().Current;
float sectionTop = container.Children[scrollIndex].BoundingBox.Top;
return scrollPosition < sectionTop;
});
} }
for (int i = 1; i < sections_count; i++) for (int i = 1; i < sections_count; i++)

View File

@ -0,0 +1,71 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osuTK;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneStarRatingDisplay : OsuTestScene
{
[TestCase(StarRatingDisplaySize.Regular)]
[TestCase(StarRatingDisplaySize.Small)]
public void TestDisplay(StarRatingDisplaySize size)
{
AddStep("load displays", () =>
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2f),
Direction = FillDirection.Horizontal,
ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2f),
Direction = FillDirection.Vertical,
ChildrenEnumerable = Enumerable.Range(0, 10).Select(j => new StarRatingDisplay(new StarDifficulty(i * (i >= 11 ? 25f : 1f) + j * 0.1f, 0), size)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}),
})
};
});
}
[Test]
public void TestSpectrum()
{
StarRatingDisplay starRating = null;
AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1), animated: true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(3f),
});
AddRepeatStep("set random value", () =>
{
starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), 1);
}, 10);
AddSliderStep("set exact stars", 0.0, 11.0, 5.55, d =>
{
if (starRating != null)
starRating.Current.Value = new StarDifficulty(d, 1);
});
}
}
}

View File

@ -14,6 +14,7 @@ using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -56,12 +57,28 @@ namespace osu.Game.Beatmaps
[Resolved] [Resolved]
private Bindable<IReadOnlyList<Mod>> currentMods { get; set; } private Bindable<IReadOnlyList<Mod>> currentMods { get; set; }
private ModSettingChangeTracker modSettingChangeTracker;
private ScheduledDelegate debouncedModSettingsChange;
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
currentRuleset.BindValueChanged(_ => updateTrackedBindables()); currentRuleset.BindValueChanged(_ => updateTrackedBindables());
currentMods.BindValueChanged(_ => updateTrackedBindables(), true);
currentMods.BindValueChanged(mods =>
{
modSettingChangeTracker?.Dispose();
updateTrackedBindables();
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
modSettingChangeTracker.SettingChanged += _ =>
{
debouncedModSettingsChange?.Cancel();
debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
};
}, true);
} }
/// <summary> /// <summary>
@ -84,7 +101,7 @@ namespace osu.Game.Beatmaps
/// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination. /// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The bindable will not update to follow the currently-selected ruleset and mods. /// The bindable will not update to follow the currently-selected ruleset and mods or its settings.
/// </remarks> /// </remarks>
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param> /// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param> /// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param>
@ -275,6 +292,8 @@ namespace osu.Game.Beatmaps
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
modSettingChangeTracker?.Dispose();
cancelTrackedBindableUpdate(); cancelTrackedBindableUpdate();
updateScheduler?.Dispose(); updateScheduler?.Dispose();
} }
@ -297,7 +316,7 @@ namespace osu.Game.Beatmaps
public bool Equals(DifficultyCacheLookup other) public bool Equals(DifficultyCacheLookup other)
=> Beatmap.ID == other.Beatmap.ID => Beatmap.ID == other.Beatmap.ID
&& Ruleset.ID == other.Ruleset.ID && Ruleset.ID == other.Ruleset.ID
&& OrderedMods.Select(m => m.Acronym).SequenceEqual(other.OrderedMods.Select(m => m.Acronym)); && OrderedMods.SequenceEqual(other.OrderedMods);
public override int GetHashCode() public override int GetHashCode()
{ {
@ -307,7 +326,7 @@ namespace osu.Game.Beatmaps
hashCode.Add(Ruleset.ID); hashCode.Add(Ruleset.ID);
foreach (var mod in OrderedMods) foreach (var mod in OrderedMods)
hashCode.Add(mod.Acronym); hashCode.Add(mod);
return hashCode.ToHashCode(); return hashCode.ToHashCode();
} }

View File

@ -365,6 +365,10 @@ namespace osu.Game.Beatmaps
queryable = beatmaps.BeatmapSetsOverview; queryable = beatmaps.BeatmapSetsOverview;
break; break;
case IncludedDetails.AllButRuleset:
queryable = beatmaps.BeatmapSetsWithoutRuleset;
break;
case IncludedDetails.AllButFiles: case IncludedDetails.AllButFiles:
queryable = beatmaps.BeatmapSetsWithoutFiles; queryable = beatmaps.BeatmapSetsWithoutFiles;
break; break;
@ -384,8 +388,33 @@ namespace osu.Game.Beatmaps
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary> /// </summary>
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <param name="includes">The level of detail to include in the returned objects.</param>
/// <returns>Results from the provided query.</returns> /// <returns>Results from the provided query.</returns>
public IEnumerable<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.ConsumableItems.AsNoTracking().Where(query); public IEnumerable<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query, IncludedDetails includes = IncludedDetails.All)
{
IQueryable<BeatmapSetInfo> queryable;
switch (includes)
{
case IncludedDetails.Minimal:
queryable = beatmaps.BeatmapSetsOverview;
break;
case IncludedDetails.AllButRuleset:
queryable = beatmaps.BeatmapSetsWithoutRuleset;
break;
case IncludedDetails.AllButFiles:
queryable = beatmaps.BeatmapSetsWithoutFiles;
break;
default:
queryable = beatmaps.ConsumableItems;
break;
}
return queryable.AsNoTracking().Where(query);
}
/// <summary> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
@ -554,6 +583,11 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
AllButFiles, AllButFiles,
/// <summary>
/// Include everything except ruleset. Used for cases where we aren't sure the ruleset is present but still want to consume the beatmap.
/// </summary>
AllButRuleset,
/// <summary> /// <summary>
/// Include everything. /// Include everything.
/// </summary> /// </summary>

View File

@ -92,6 +92,13 @@ namespace osu.Game.Beatmaps
.Include(s => s.Beatmaps) .Include(s => s.Beatmaps)
.AsNoTracking(); .AsNoTracking();
public IQueryable<BeatmapSetInfo> BeatmapSetsWithoutRuleset => ContextFactory.Get().BeatmapSetInfo
.Include(s => s.Metadata)
.Include(s => s.Files).ThenInclude(f => f.FileInfo)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
.AsNoTracking();
public IQueryable<BeatmapSetInfo> BeatmapSetsWithoutFiles => ContextFactory.Get().BeatmapSetInfo public IQueryable<BeatmapSetInfo> BeatmapSetsWithoutFiles => ContextFactory.Get().BeatmapSetInfo
.Include(s => s.Metadata) .Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)

View File

@ -0,0 +1,168 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
/// <summary>
/// A pill that displays the star rating of a beatmap.
/// </summary>
public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue<StarDifficulty>
{
private readonly bool animated;
private readonly Box background;
private readonly SpriteIcon starIcon;
private readonly OsuSpriteText starsText;
private readonly BindableWithCurrent<StarDifficulty> current = new BindableWithCurrent<StarDifficulty>();
public Bindable<StarDifficulty> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly Bindable<double> displayedStars = new BindableDouble();
/// <summary>
/// The currently displayed stars of this display wrapped in a bindable.
/// This bindable gets transformed on change rather than instantaneous, if animation is enabled.
/// </summary>
public IBindable<double> DisplayedStars => displayedStars;
[Resolved]
private OsuColour colours { get; set; }
[Resolved(canBeNull: true)]
private OverlayColourProvider colourProvider { get; set; }
/// <summary>
/// Creates a new <see cref="StarRatingDisplay"/> using an already computed <see cref="StarDifficulty"/>.
/// </summary>
/// <param name="starDifficulty">The already computed <see cref="StarDifficulty"/> to display.</param>
/// <param name="size">The size of the star rating display.</param>
/// <param name="animated">Whether the star rating display will perform transforms on change rather than updating instantaneously.</param>
public StarRatingDisplay(StarDifficulty starDifficulty, StarRatingDisplaySize size = StarRatingDisplaySize.Regular, bool animated = false)
{
this.animated = animated;
Current.Value = starDifficulty;
AutoSizeAxes = Axes.Both;
MarginPadding margin = default;
switch (size)
{
case StarRatingDisplaySize.Small:
margin = new MarginPadding { Horizontal = 7f };
break;
case StarRatingDisplaySize.Range:
margin = new MarginPadding { Horizontal = 8f };
break;
case StarRatingDisplaySize.Regular:
margin = new MarginPadding { Horizontal = 8f, Vertical = 2f };
break;
}
InternalChild = new CircularContainer
{
Masking = true,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new GridContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Margin = margin,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 3f),
new Dimension(GridSizeMode.AutoSize, minSize: 25f),
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
new[]
{
starIcon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Star,
Size = new Vector2(8f),
},
Empty(),
starsText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Bottom = 1.5f },
// todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f
// see https://github.com/ppy/osu-framework/issues/3271.
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
Shadow = false,
},
}
}
},
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(c =>
{
if (animated)
this.TransformBindableTo(displayedStars, c.NewValue.Stars, 750, Easing.OutQuint);
else
displayedStars.Value = c.NewValue.Stars;
});
displayedStars.Value = Current.Value.Stars;
displayedStars.BindValueChanged(s =>
{
starsText.Text = s.NewValue.ToLocalisableString("0.00");
background.Colour = colours.ForStarDifficulty(s.NewValue);
starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
}, true);
}
}
public enum StarRatingDisplaySize
{
Small,
Range,
Regular,
}
}

View File

@ -101,7 +101,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.HitLighting, true); SetDefault(OsuSetting.HitLighting, true);
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
SetDefault(OsuSetting.ShowDifficultyGraph, true); SetDefault(OsuSetting.ShowProgressGraph, true);
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false); SetDefault(OsuSetting.KeyOverlay, false);
@ -201,6 +201,8 @@ namespace osu.Game.Configuration
public Func<GlobalAction, string> LookupKeyBindings { get; set; } public Func<GlobalAction, string> LookupKeyBindings { get; set; }
} }
// IMPORTANT: These are used in user configuration files.
// The naming of these keys should not be changed once they are deployed in a release, unless migration logic is also added.
public enum OsuSetting public enum OsuSetting
{ {
Ruleset, Ruleset,
@ -217,7 +219,7 @@ namespace osu.Game.Configuration
AlwaysPlayFirstComboBreak, AlwaysPlayFirstComboBreak,
FloatingComments, FloatingComments,
HUDVisibilityMode, HUDVisibilityMode,
ShowDifficultyGraph, ShowProgressGraph,
ShowHealthDisplayWhenCantFail, ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow, FadePlayfieldWhenHealthLow,
MouseDisableButtons, MouseDisableButtons,

View File

@ -36,6 +36,11 @@ namespace osu.Game.Database
/// </summary> /// </summary>
public IQueryable<T> ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set<T>()); public IQueryable<T> ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set<T>());
/// <summary>
/// Access barebones items with no includes.
/// </summary>
public IQueryable<T> Items => ContextFactory.Get().Set<T>();
/// <summary> /// <summary>
/// Add a <typeparamref name="T"/> to the database. /// Add a <typeparamref name="T"/> to the database.
/// </summary> /// </summary>

View File

@ -22,7 +22,8 @@ namespace osu.Game.Graphics.Containers
where T : Drawable where T : Drawable
{ {
public Bindable<T> SelectedSection { get; } = new Bindable<T>(); public Bindable<T> SelectedSection { get; } = new Bindable<T>();
private Drawable lastClickedSection;
private T lastClickedSection;
public Drawable ExpandableHeader public Drawable ExpandableHeader
{ {
@ -144,10 +145,25 @@ namespace osu.Game.Graphics.Containers
footerHeight = null; footerHeight = null;
} }
public void ScrollTo(Drawable section) public void ScrollTo(Drawable target)
{ {
lastKnownScroll = null;
float fixedHeaderSize = FixedHeader?.BoundingBox.Height ?? 0;
// implementation similar to ScrollIntoView but a bit more nuanced.
float top = scrollContainer.GetChildPosInContent(target);
float bottomScrollExtent = scrollContainer.ScrollableExtent - fixedHeaderSize;
float scrollTarget = top - fixedHeaderSize - scrollContainer.DisplayableContent * scroll_y_centre;
if (scrollTarget > bottomScrollExtent)
scrollContainer.ScrollToEnd();
else
scrollContainer.ScrollTo(scrollTarget);
if (target is T section)
lastClickedSection = section; lastClickedSection = section;
scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - scrollContainer.DisplayableContent * scroll_y_centre - (FixedHeader?.BoundingBox.Height ?? 0));
} }
public void ScrollToTop() => scrollContainer.ScrollTo(0); public void ScrollToTop() => scrollContainer.ScrollTo(0);
@ -170,13 +186,22 @@ namespace osu.Game.Graphics.Containers
if (source == InvalidationSource.Child && (invalidation & Invalidation.DrawSize) != 0) if (source == InvalidationSource.Child && (invalidation & Invalidation.DrawSize) != 0)
{ {
lastKnownScroll = null; InvalidateScrollPosition();
result = true; result = true;
} }
return result; return result;
} }
protected void InvalidateScrollPosition()
{
Schedule(() =>
{
lastKnownScroll = null;
lastClickedSection = null;
});
}
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
@ -224,15 +249,19 @@ namespace osu.Game.Graphics.Containers
float scrollCentre = fixedHeaderSize + scrollContainer.DisplayableContent * scroll_y_centre + selectionLenienceAboveSection; float scrollCentre = fixedHeaderSize + scrollContainer.DisplayableContent * scroll_y_centre + selectionLenienceAboveSection;
if (Precision.AlmostBigger(0, scrollContainer.Current)) var presentChildren = Children.Where(c => c.IsPresent);
SelectedSection.Value = lastClickedSection as T ?? Children.FirstOrDefault();
if (lastClickedSection != null)
SelectedSection.Value = lastClickedSection;
else if (Precision.AlmostBigger(0, scrollContainer.Current))
SelectedSection.Value = presentChildren.FirstOrDefault();
else if (Precision.AlmostBigger(scrollContainer.Current, scrollContainer.ScrollableExtent)) else if (Precision.AlmostBigger(scrollContainer.Current, scrollContainer.ScrollableExtent))
SelectedSection.Value = lastClickedSection as T ?? Children.LastOrDefault(); SelectedSection.Value = presentChildren.LastOrDefault();
else else
{ {
SelectedSection.Value = Children SelectedSection.Value = presentChildren
.TakeWhile(section => scrollContainer.GetChildPosInContent(section) - currentScroll - scrollCentre <= 0) .TakeWhile(section => scrollContainer.GetChildPosInContent(section) - currentScroll - scrollCentre <= 0)
.LastOrDefault() ?? Children.FirstOrDefault(); .LastOrDefault() ?? presentChildren.FirstOrDefault();
} }
} }
} }

View File

@ -42,6 +42,12 @@ namespace osu.Game.Graphics.Containers
base.OnUserScroll(value, animated, distanceDecay); base.OnUserScroll(value, animated, distanceDecay);
} }
public new void ScrollIntoView(Drawable target, bool animated = true)
{
UserScrolling = false;
base.ScrollIntoView(target, animated);
}
public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null)
{ {
UserScrolling = false; UserScrolling = false;

View File

@ -22,9 +22,14 @@ namespace osu.Game.Graphics.UserInterface
public void TakeFocus() public void TakeFocus()
{ {
if (allowImmediateFocus) GetContainingInputManager().ChangeFocus(this); if (!allowImmediateFocus)
return;
Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this), false);
} }
public new void KillFocus() => base.KillFocus();
public bool HoldFocus public bool HoldFocus
{ {
get => allowImmediateFocus && focus; get => allowImmediateFocus && focus;

View File

@ -6,6 +6,7 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
@ -57,18 +58,13 @@ namespace osu.Game.Graphics.UserInterface
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Colour = GlowColour, Colour = GlowColour.Opacity(0),
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 10, Radius = 10,
Roundness = 8, Roundness = 8,
}; };
} }
protected override void LoadComplete()
{
FadeEdgeEffectTo(0);
}
private bool glowing; private bool glowing;
public bool Glowing public bool Glowing
@ -153,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface
glowColour = value; glowColour = value;
var effect = EdgeEffect; var effect = EdgeEffect;
effect.Colour = value; effect.Colour = Glowing ? value : value.Opacity(0);
EdgeEffect = effect; EdgeEffect = effect;
} }
} }

View File

@ -36,6 +36,7 @@ namespace osu.Game.Graphics.UserInterface
public Color4 BackgroundColour public Color4 BackgroundColour
{ {
get => backgroundColour ?? Color4.White;
set set
{ {
backgroundColour = value; backgroundColour = value;

View File

@ -69,6 +69,7 @@ namespace osu.Game.Graphics.UserInterface
BackgroundColour = Color4.Black.Opacity(0.5f); BackgroundColour = Color4.Black.Opacity(0.5f);
MaskingContainer.CornerRadius = corner_radius; MaskingContainer.CornerRadius = corner_radius;
Alpha = 0;
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
ItemsContainer.Padding = new MarginPadding(5); ItemsContainer.Padding = new MarginPadding(5);
@ -94,10 +95,12 @@ namespace osu.Game.Graphics.UserInterface
protected override void AnimateClose() protected override void AnimateClose()
{ {
this.FadeOut(300, Easing.OutQuint);
if (wasOpened) if (wasOpened)
{
this.FadeOut(300, Easing.OutQuint);
sampleClose?.Play(); sampleClose?.Play();
} }
}
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
protected override void UpdateSize(Vector2 newSize) protected override void UpdateSize(Vector2 newSize)

View File

@ -90,6 +90,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward), new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward), new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus),
}; };
public IEnumerable<KeyBinding> SongSelectKeyBindings => new[] public IEnumerable<KeyBinding> SongSelectKeyBindings => new[]
@ -280,5 +281,8 @@ namespace osu.Game.Input.Bindings
[Description("Seek replay backward")] [Description("Seek replay backward")]
SeekReplayBackward, SeekReplayBackward,
[Description("Toggle chat focus")]
ToggleChatFocus
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Game.Input namespace osu.Game.Input
{ {
@ -24,14 +25,14 @@ namespace osu.Game.Input
private IBindable<bool> localUserPlaying; private IBindable<bool> localUserPlaying;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
{ {
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode); frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode); frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
frameworkWindowMode.BindValueChanged(_ => updateConfineMode()); frameworkWindowMode.BindValueChanged(_ => updateConfineMode());
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
osuConfineMode.ValueChanged += _ => updateConfineMode(); osuConfineMode.ValueChanged += _ => updateConfineMode();
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true); localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);

View File

@ -0,0 +1,64 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class AudioSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.AudioSettings";
/// <summary>
/// "Audio"
/// </summary>
public static LocalisableString AudioSectionHeader => new TranslatableString(getKey(@"audio_section_header"), @"Audio");
/// <summary>
/// "Devices"
/// </summary>
public static LocalisableString AudioDevicesHeader => new TranslatableString(getKey(@"audio_devices_header"), @"Devices");
/// <summary>
/// "Volume"
/// </summary>
public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
/// <summary>
/// "Master"
/// </summary>
public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
/// <summary>
/// "Master (window inactive)"
/// </summary>
public static LocalisableString MasterVolumeInactive => new TranslatableString(getKey(@"master_volume_inactive"), @"Master (window inactive)");
/// <summary>
/// "Effect"
/// </summary>
public static LocalisableString EffectVolume => new TranslatableString(getKey(@"effect_volume"), @"Effect");
/// <summary>
/// "Music"
/// </summary>
public static LocalisableString MusicVolume => new TranslatableString(getKey(@"music_volume"), @"Music");
/// <summary>
/// "Offset Adjustment"
/// </summary>
public static LocalisableString OffsetHeader => new TranslatableString(getKey(@"offset_header"), @"Offset Adjustment");
/// <summary>
/// "Audio offset"
/// </summary>
public static LocalisableString AudioOffset => new TranslatableString(getKey(@"audio_offset"), @"Audio offset");
/// <summary>
/// "Offset wizard"
/// </summary>
public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -14,11 +14,21 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel"); public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel");
/// <summary>
/// "Clear"
/// </summary>
public static LocalisableString Clear => new TranslatableString(getKey(@"clear"), @"Clear");
/// <summary> /// <summary>
/// "Enabled" /// "Enabled"
/// </summary> /// </summary>
public static LocalisableString Enabled => new TranslatableString(getKey(@"enabled"), @"Enabled"); public static LocalisableString Enabled => new TranslatableString(getKey(@"enabled"), @"Enabled");
/// <summary>
/// "Default"
/// </summary>
public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default");
/// <summary> /// <summary>
/// "Width" /// "Width"
/// </summary> /// </summary>

View File

@ -0,0 +1,49 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class DebugSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings";
/// <summary>
/// "Debug"
/// </summary>
public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug");
/// <summary>
/// "General"
/// </summary>
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
/// <summary>
/// "Show log overlay"
/// </summary>
public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay");
/// <summary>
/// "Bypass front-to-back render pass"
/// </summary>
public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass");
/// <summary>
/// "Import files"
/// </summary>
public static LocalisableString ImportFiles => new TranslatableString(getKey(@"import_files"), @"Import files");
/// <summary>
/// "Memory"
/// </summary>
public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory");
/// <summary>
/// "Clear all caches"
/// </summary>
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,94 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class GameplaySettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.GameplaySettings";
/// <summary>
/// "Gameplay"
/// </summary>
public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
/// <summary>
/// "General"
/// </summary>
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
/// <summary>
/// "Background dim"
/// </summary>
public static LocalisableString BackgroundDim => new TranslatableString(getKey(@"dim"), @"Background dim");
/// <summary>
/// "Background blur"
/// </summary>
public static LocalisableString BackgroundBlur => new TranslatableString(getKey(@"blur"), @"Background blur");
/// <summary>
/// "Lighten playfield during breaks"
/// </summary>
public static LocalisableString LightenDuringBreaks => new TranslatableString(getKey(@"lighten_during_breaks"), @"Lighten playfield during breaks");
/// <summary>
/// "HUD overlay visibility mode"
/// </summary>
public static LocalisableString HUDVisibilityMode => new TranslatableString(getKey(@"hud_visibility_mode"), @"HUD overlay visibility mode");
/// <summary>
/// "Show difficulty graph on progress bar"
/// </summary>
public static LocalisableString ShowDifficultyGraph => new TranslatableString(getKey(@"show_difficulty_graph"), @"Show difficulty graph on progress bar");
/// <summary>
/// "Show health display even when you can't fail"
/// </summary>
public static LocalisableString ShowHealthDisplayWhenCantFail => new TranslatableString(getKey(@"show_health_display_when_cant_fail"), @"Show health display even when you can't fail");
/// <summary>
/// "Fade playfield to red when health is low"
/// </summary>
public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low");
/// <summary>
/// "Always show key overlay"
/// </summary>
public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay");
/// <summary>
/// "Positional hitsounds"
/// </summary>
public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds");
/// <summary>
/// "Always play first combo break sound"
/// </summary>
public static LocalisableString AlwaysPlayFirstComboBreak => new TranslatableString(getKey(@"always_play_first_combo_break"), @"Always play first combo break sound");
/// <summary>
/// "Score display mode"
/// </summary>
public static LocalisableString ScoreDisplayMode => new TranslatableString(getKey(@"score_display_mode"), @"Score display mode");
/// <summary>
/// "Disable Windows key during gameplay"
/// </summary>
public static LocalisableString DisableWinKey => new TranslatableString(getKey(@"disable_win_key"), @"Disable Windows key during gameplay");
/// <summary>
/// "Mods"
/// </summary>
public static LocalisableString ModsHeader => new TranslatableString(getKey(@"mods_header"), @"Mods");
/// <summary>
/// "Increase visibility of first object when visual impairment mods are enabled"
/// </summary>
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,59 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class GeneralSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.GeneralSettings";
/// <summary>
/// "General"
/// </summary>
public static LocalisableString GeneralSectionHeader => new TranslatableString(getKey(@"general_section_header"), @"General");
/// <summary>
/// "Language"
/// </summary>
public static LocalisableString LanguageHeader => new TranslatableString(getKey(@"language_header"), @"Language");
/// <summary>
/// "Language"
/// </summary>
public static LocalisableString LanguageDropdown => new TranslatableString(getKey(@"language_dropdown"), @"Language");
/// <summary>
/// "Prefer metadata in original language"
/// </summary>
public static LocalisableString PreferOriginalMetadataLanguage => new TranslatableString(getKey(@"prefer_original"), @"Prefer metadata in original language");
/// <summary>
/// "Updates"
/// </summary>
public static LocalisableString UpdateHeader => new TranslatableString(getKey(@"update_header"), @"Updates");
/// <summary>
/// "Release stream"
/// </summary>
public static LocalisableString ReleaseStream => new TranslatableString(getKey(@"release_stream"), @"Release stream");
/// <summary>
/// "Check for updates"
/// </summary>
public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
/// <summary>
/// "Open osu! folder"
/// </summary>
public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder");
/// <summary>
/// "Change folder location..."
/// </summary>
public static LocalisableString ChangeFolderLocation => new TranslatableString(getKey(@"change_folder_location"), @"Change folder location...");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,119 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class GraphicsSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.GraphicsSettings";
/// <summary>
/// "Graphics"
/// </summary>
public static LocalisableString GraphicsSectionHeader => new TranslatableString(getKey(@"graphics_section_header"), @"Graphics");
/// <summary>
/// "Renderer"
/// </summary>
public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
/// <summary>
/// "Frame limiter"
/// </summary>
public static LocalisableString FrameLimiter => new TranslatableString(getKey(@"frame_limiter"), @"Frame limiter");
/// <summary>
/// "Threading mode"
/// </summary>
public static LocalisableString ThreadingMode => new TranslatableString(getKey(@"threading_mode"), @"Threading mode");
/// <summary>
/// "Show FPS"
/// </summary>
public static LocalisableString ShowFPS => new TranslatableString(getKey(@"show_fps"), @"Show FPS");
/// <summary>
/// "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. "2x refresh rate" is recommended."
/// </summary>
public static LocalisableString UnlimitedFramesNote => new TranslatableString(getKey(@"unlimited_frames_note"), @"Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. ""2x refresh rate"" is recommended.");
/// <summary>
/// "Layout"
/// </summary>
public static LocalisableString LayoutHeader => new TranslatableString(getKey(@"layout_header"), @"Layout");
/// <summary>
/// "Screen mode"
/// </summary>
public static LocalisableString ScreenMode => new TranslatableString(getKey(@"screen_mode"), @"Screen mode");
/// <summary>
/// "Resolution"
/// </summary>
public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution");
/// <summary>
/// "UI scaling"
/// </summary>
public static LocalisableString UIScaling => new TranslatableString(getKey(@"ui_scaling"), @"UI scaling");
/// <summary>
/// "Screen scaling"
/// </summary>
public static LocalisableString ScreenScaling => new TranslatableString(getKey(@"screen_scaling"), @"Screen scaling");
/// <summary>
/// "Horizontal position"
/// </summary>
public static LocalisableString HorizontalPosition => new TranslatableString(getKey(@"horizontal_position"), @"Horizontal position");
/// <summary>
/// "Vertical position"
/// </summary>
public static LocalisableString VerticalPosition => new TranslatableString(getKey(@"vertical_position"), @"Vertical position");
/// <summary>
/// "Horizontal scale"
/// </summary>
public static LocalisableString HorizontalScale => new TranslatableString(getKey(@"horizontal_scale"), @"Horizontal scale");
/// <summary>
/// "Vertical scale"
/// </summary>
public static LocalisableString VerticalScale => new TranslatableString(getKey(@"vertical_scale"), @"Vertical scale");
/// <summary>
/// "Running without fullscreen mode will increase your input latency!"
/// </summary>
public static LocalisableString NotFullscreenNote => new TranslatableString(getKey(@"not_fullscreen_note"), @"Running without fullscreen mode will increase your input latency!");
/// <summary>
/// "Detail Settings"
/// </summary>
public static LocalisableString DetailSettingsHeader => new TranslatableString(getKey(@"detail_settings_header"), @"Detail Settings");
/// <summary>
/// "Storyboard / video"
/// </summary>
public static LocalisableString StoryboardVideo => new TranslatableString(getKey(@"storyboard_video"), @"Storyboard / video");
/// <summary>
/// "Hit lighting"
/// </summary>
public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
/// <summary>
/// "Screenshot format"
/// </summary>
public static LocalisableString ScreenshotFormat => new TranslatableString(getKey(@"screenshot_format"), @"Screenshot format");
/// <summary>
/// "Show menu cursor in screenshots"
/// </summary>
public static LocalisableString ShowCursorInScreenshots => new TranslatableString(getKey(@"show_cursor_in_screenshots"), @"Show menu cursor in screenshots");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,59 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class InputSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.InputSettings";
/// <summary>
/// "Input"
/// </summary>
public static LocalisableString InputSectionHeader => new TranslatableString(getKey(@"input_section_header"), @"Input");
/// <summary>
/// "Global"
/// </summary>
public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
/// <summary>
/// "Song Select"
/// </summary>
public static LocalisableString SongSelectSection => new TranslatableString(getKey(@"song_select_section"), @"Song Select");
/// <summary>
/// "In Game"
/// </summary>
public static LocalisableString InGameSection => new TranslatableString(getKey(@"in_game_section"), @"In Game");
/// <summary>
/// "Audio"
/// </summary>
public static LocalisableString AudioSection => new TranslatableString(getKey(@"audio_section"), @"Audio");
/// <summary>
/// "Editor"
/// </summary>
public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
/// <summary>
/// "Reset all bindings in section"
/// </summary>
public static LocalisableString ResetSectionButton => new TranslatableString(getKey(@"reset_section_button"), @"Reset all bindings in section");
/// <summary>
/// "key configuration"
/// </summary>
public static LocalisableString KeyBindingPanelHeader => new TranslatableString(getKey(@"key_binding_panel_header"), @"key configuration");
/// <summary>
/// "Customise your keys!"
/// </summary>
public static LocalisableString KeyBindingPanelDescription => new TranslatableString(getKey(@"key_binding_panel_description"), @"Customise your keys!");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,74 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class MaintenanceSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.MaintenanceSettings";
/// <summary>
/// "Maintenance"
/// </summary>
public static LocalisableString MaintenanceSectionHeader => new TranslatableString(getKey(@"maintenance_section_header"), @"Maintenance");
/// <summary>
/// "Select directory"
/// </summary>
public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
/// <summary>
/// "Import beatmaps from stable"
/// </summary>
public static LocalisableString ImportBeatmapsFromStable => new TranslatableString(getKey(@"import_beatmaps_from_stable"), @"Import beatmaps from stable");
/// <summary>
/// "Delete ALL beatmaps"
/// </summary>
public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
/// <summary>
/// "Import scores from stable"
/// </summary>
public static LocalisableString ImportScoresFromStable => new TranslatableString(getKey(@"import_scores_from_stable"), @"Import scores from stable");
/// <summary>
/// "Delete ALL scores"
/// </summary>
public static LocalisableString DeleteAllScores => new TranslatableString(getKey(@"delete_all_scores"), @"Delete ALL scores");
/// <summary>
/// "Import skins from stable"
/// </summary>
public static LocalisableString ImportSkinsFromStable => new TranslatableString(getKey(@"import_skins_from_stable"), @"Import skins from stable");
/// <summary>
/// "Delete ALL skins"
/// </summary>
public static LocalisableString DeleteAllSkins => new TranslatableString(getKey(@"delete_all_skins"), @"Delete ALL skins");
/// <summary>
/// "Import collections from stable"
/// </summary>
public static LocalisableString ImportCollectionsFromStable => new TranslatableString(getKey(@"import_collections_from_stable"), @"Import collections from stable");
/// <summary>
/// "Delete ALL collections"
/// </summary>
public static LocalisableString DeleteAllCollections => new TranslatableString(getKey(@"delete_all_collections"), @"Delete ALL collections");
/// <summary>
/// "Restore all hidden difficulties"
/// </summary>
public static LocalisableString RestoreAllHiddenDifficulties => new TranslatableString(getKey(@"restore_all_hidden_difficulties"), @"Restore all hidden difficulties");
/// <summary>
/// "Restore all recently deleted beatmaps"
/// </summary>
public static LocalisableString RestoreAllRecentlyDeletedBeatmaps => new TranslatableString(getKey(@"restore_all_recently_deleted_beatmaps"), @"Restore all recently deleted beatmaps");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,69 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class OnlineSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.OnlineSettings";
/// <summary>
/// "Online"
/// </summary>
public static LocalisableString OnlineSectionHeader => new TranslatableString(getKey(@"online_section_header"), @"Online");
/// <summary>
/// "Alerts and Privacy"
/// </summary>
public static LocalisableString AlertsAndPrivacyHeader => new TranslatableString(getKey(@"alerts_and_privacy_header"), @"Alerts and Privacy");
/// <summary>
/// "Show a notification when someone mentions your name"
/// </summary>
public static LocalisableString NotifyOnMentioned => new TranslatableString(getKey(@"notify_on_mentioned"), @"Show a notification when someone mentions your name");
/// <summary>
/// "Show a notification when you receive a private message"
/// </summary>
public static LocalisableString NotifyOnPrivateMessage => new TranslatableString(getKey(@"notify_on_private_message"), @"Show a notification when you receive a private message");
/// <summary>
/// "Integrations"
/// </summary>
public static LocalisableString IntegrationsHeader => new TranslatableString(getKey(@"integrations_header"), @"Integrations");
/// <summary>
/// "Discord Rich Presence"
/// </summary>
public static LocalisableString DiscordRichPresence => new TranslatableString(getKey(@"discord_rich_presence"), @"Discord Rich Presence");
/// <summary>
/// "Web"
/// </summary>
public static LocalisableString WebHeader => new TranslatableString(getKey(@"web_header"), @"Web");
/// <summary>
/// "Warn about opening external links"
/// </summary>
public static LocalisableString ExternalLinkWarning => new TranslatableString(getKey(@"external_link_warning"), @"Warn about opening external links");
/// <summary>
/// "Prefer downloads without video"
/// </summary>
public static LocalisableString PreferNoVideo => new TranslatableString(getKey(@"prefer_no_video"), @"Prefer downloads without video");
/// <summary>
/// "Automatically download beatmaps when spectating"
/// </summary>
public static LocalisableString AutomaticallyDownloadWhenSpectating => new TranslatableString(getKey(@"automatically_download_when_spectating"), @"Automatically download beatmaps when spectating");
/// <summary>
/// "Show explicit content in search results"
/// </summary>
public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,54 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class SkinSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.SkinSettings";
/// <summary>
/// "Skin"
/// </summary>
public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
/// <summary>
/// "Skin layout editor"
/// </summary>
public static LocalisableString SkinLayoutEditor => new TranslatableString(getKey(@"skin_layout_editor"), @"Skin layout editor");
/// <summary>
/// "Gameplay cursor size"
/// </summary>
public static LocalisableString GameplayCursorSize => new TranslatableString(getKey(@"gameplay_cursor_size"), @"Gameplay cursor size");
/// <summary>
/// "Adjust gameplay cursor size based on current beatmap"
/// </summary>
public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
/// <summary>
/// "Beatmap skins"
/// </summary>
public static LocalisableString BeatmapSkins => new TranslatableString(getKey(@"beatmap_skins"), @"Beatmap skins");
/// <summary>
/// "Beatmap colours"
/// </summary>
public static LocalisableString BeatmapColours => new TranslatableString(getKey(@"beatmap_colours"), @"Beatmap colours");
/// <summary>
/// "Beatmap hitsounds"
/// </summary>
public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds");
/// <summary>
/// "Export selected skin"
/// </summary>
public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -0,0 +1,114 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class UserInterfaceStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.UserInterface";
/// <summary>
/// "User Interface"
/// </summary>
public static LocalisableString UserInterfaceSectionHeader => new TranslatableString(getKey(@"user_interface_section_header"), @"User Interface");
/// <summary>
/// "General"
/// </summary>
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
/// <summary>
/// "Rotate cursor when dragging"
/// </summary>
public static LocalisableString CursorRotation => new TranslatableString(getKey(@"cursor_rotation"), @"Rotate cursor when dragging");
/// <summary>
/// "Menu cursor size"
/// </summary>
public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size");
/// <summary>
/// "Parallax"
/// </summary>
public static LocalisableString Parallax => new TranslatableString(getKey(@"parallax"), @"Parallax");
/// <summary>
/// "Hold-to-confirm activation time"
/// </summary>
public static LocalisableString HoldToConfirmActivationTime => new TranslatableString(getKey(@"hold_to_confirm_activation_time"), @"Hold-to-confirm activation time");
/// <summary>
/// "Main Menu"
/// </summary>
public static LocalisableString MainMenuHeader => new TranslatableString(getKey(@"main_menu_header"), @"Main Menu");
/// <summary>
/// "Interface voices"
/// </summary>
public static LocalisableString InterfaceVoices => new TranslatableString(getKey(@"interface_voices"), @"Interface voices");
/// <summary>
/// "osu! music theme"
/// </summary>
public static LocalisableString OsuMusicTheme => new TranslatableString(getKey(@"osu_music_theme"), @"osu! music theme");
/// <summary>
/// "Intro sequence"
/// </summary>
public static LocalisableString IntroSequence => new TranslatableString(getKey(@"intro_sequence"), @"Intro sequence");
/// <summary>
/// "Background source"
/// </summary>
public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
/// <summary>
/// "Seasonal backgrounds"
/// </summary>
public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
/// <summary>
/// "Changes to this setting will only apply with an active osu!supporter tag."
/// </summary>
public static LocalisableString NotSupporterNote => new TranslatableString(getKey(@"not_supporter_note"), @"Changes to this setting will only apply with an active osu!supporter tag.");
/// <summary>
/// "Song Select"
/// </summary>
public static LocalisableString SongSelectHeader => new TranslatableString(getKey(@"song_select_header"), @"Song Select");
/// <summary>
/// "Right mouse drag to absolute scroll"
/// </summary>
public static LocalisableString RightMouseScroll => new TranslatableString(getKey(@"right_mouse_scroll"), @"Right mouse drag to absolute scroll");
/// <summary>
/// "Show converted beatmaps"
/// </summary>
public static LocalisableString ShowConvertedBeatmaps => new TranslatableString(getKey(@"show_converted_beatmaps"), @"Show converted beatmaps");
/// <summary>
/// "Display beatmaps from"
/// </summary>
public static LocalisableString StarsMinimum => new TranslatableString(getKey(@"stars_minimum"), @"Display beatmaps from");
/// <summary>
/// "up to"
/// </summary>
public static LocalisableString StarsMaximum => new TranslatableString(getKey(@"stars_maximum"), @"up to");
/// <summary>
/// "Random selection algorithm"
/// </summary>
public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm");
/// <summary>
/// "no limit"
/// </summary>
public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -305,9 +305,11 @@ namespace osu.Game.Online.API
{ {
req.Perform(this); req.Perform(this);
if (req.CompletionState != APIRequestCompletionState.Completed)
return false;
// we could still be in initialisation, at which point we don't want to say we're Online yet. // we could still be in initialisation, at which point we don't want to say we're Online yet.
if (IsLoggedIn) state.Value = APIState.Online; if (IsLoggedIn) state.Value = APIState.Online;
failureCount = 0; failureCount = 0;
return true; return true;
} }
@ -381,7 +383,7 @@ namespace osu.Game.Online.API
} }
} }
public bool IsLoggedIn => localUser.Value.Id > 1; public bool IsLoggedIn => localUser.Value.Id > 1; // TODO: should this also be true if attempting to connect?
public void Queue(APIRequest request) public void Queue(APIRequest request)
{ {

View File

@ -84,7 +84,7 @@ namespace osu.Game.Online.API
/// The state of this request, from an outside perspective. /// The state of this request, from an outside perspective.
/// This is used to ensure correct notification events are fired. /// This is used to ensure correct notification events are fired.
/// </summary> /// </summary>
private APIRequestCompletionState completionState; public APIRequestCompletionState CompletionState { get; private set; }
public void Perform(IAPIProvider api) public void Perform(IAPIProvider api)
{ {
@ -127,10 +127,10 @@ namespace osu.Game.Online.API
{ {
lock (completionStateLock) lock (completionStateLock)
{ {
if (completionState != APIRequestCompletionState.Waiting) if (CompletionState != APIRequestCompletionState.Waiting)
return; return;
completionState = APIRequestCompletionState.Completed; CompletionState = APIRequestCompletionState.Completed;
} }
if (API == null) if (API == null)
@ -143,10 +143,10 @@ namespace osu.Game.Online.API
{ {
lock (completionStateLock) lock (completionStateLock)
{ {
if (completionState != APIRequestCompletionState.Waiting) if (CompletionState != APIRequestCompletionState.Waiting)
return; return;
completionState = APIRequestCompletionState.Failed; CompletionState = APIRequestCompletionState.Failed;
} }
if (API == null) if (API == null)
@ -161,7 +161,7 @@ namespace osu.Game.Online.API
{ {
lock (completionStateLock) lock (completionStateLock)
{ {
if (completionState != APIRequestCompletionState.Waiting) if (CompletionState != APIRequestCompletionState.Waiting)
return; return;
WebRequest?.Abort(); WebRequest?.Abort();
@ -200,7 +200,7 @@ namespace osu.Game.Online.API
get get
{ {
lock (completionStateLock) lock (completionStateLock)
return completionState == APIRequestCompletionState.Failed; return CompletionState == APIRequestCompletionState.Failed;
} }
} }

View File

@ -43,7 +43,8 @@ namespace osu.Game.Online.Chat
RegexOptions.IgnoreCase); RegexOptions.IgnoreCase);
// 00:00:000 (1,2,3) - test // 00:00:000 (1,2,3) - test
private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10
private static readonly Regex time_regex = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)");
// #osu // #osu
private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)");

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat;
@ -22,7 +23,7 @@ namespace osu.Game.Online.Chat
{ {
public readonly Bindable<Channel> Channel = new Bindable<Channel>(); public readonly Bindable<Channel> Channel = new Bindable<Channel>();
private readonly FocusedTextBox textbox; protected readonly ChatTextBox Textbox;
protected ChannelManager ChannelManager; protected ChannelManager ChannelManager;
@ -30,6 +31,8 @@ namespace osu.Game.Online.Chat
private readonly bool postingTextbox; private readonly bool postingTextbox;
protected readonly Box Background;
private const float textbox_height = 30; private const float textbox_height = 30;
/// <summary> /// <summary>
@ -44,7 +47,7 @@ namespace osu.Game.Online.Chat
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box Background = new Box
{ {
Colour = Color4.Black, Colour = Color4.Black,
Alpha = 0.8f, Alpha = 0.8f,
@ -54,7 +57,7 @@ namespace osu.Game.Online.Chat
if (postingTextbox) if (postingTextbox)
{ {
AddInternal(textbox = new FocusedTextBox AddInternal(Textbox = new ChatTextBox
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = textbox_height, Height = textbox_height,
@ -65,7 +68,7 @@ namespace osu.Game.Online.Chat
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
textbox.OnCommit += postMessage; Textbox.OnCommit += postMessage;
} }
Channel.BindValueChanged(channelChanged); Channel.BindValueChanged(channelChanged);
@ -82,7 +85,7 @@ namespace osu.Game.Online.Chat
private void postMessage(TextBox sender, bool newtext) private void postMessage(TextBox sender, bool newtext)
{ {
var text = textbox.Text.Trim(); var text = Textbox.Text.Trim();
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
return; return;
@ -92,7 +95,7 @@ namespace osu.Game.Online.Chat
else else
ChannelManager?.PostMessage(text, target: Channel.Value); ChannelManager?.PostMessage(text, target: Channel.Value);
textbox.Text = string.Empty; Textbox.Text = string.Empty;
} }
protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message); protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message);
@ -110,6 +113,25 @@ namespace osu.Game.Online.Chat
AddInternal(drawableChannel); AddInternal(drawableChannel);
} }
public class ChatTextBox : FocusedTextBox
{
protected override void LoadComplete()
{
base.LoadComplete();
BackgroundUnfocused = new Color4(10, 10, 10, 10);
BackgroundFocused = new Color4(10, 10, 10, 255);
}
protected override void OnFocusLost(FocusLostEvent e)
{
base.OnFocusLost(e);
FocusLost?.Invoke();
}
public Action FocusLost;
}
public class StandAloneDrawableChannel : DrawableChannel public class StandAloneDrawableChannel : DrawableChannel
{ {
public Func<Message, ChatLine> CreateChatLineAction; public Func<Message, ChatLine> CreateChatLineAction;

View File

@ -148,7 +148,12 @@ namespace osu.Game.Online
}); });
if (RuntimeInfo.SupportsJIT && preferMessagePack) if (RuntimeInfo.SupportsJIT && preferMessagePack)
builder.AddMessagePackProtocol(); {
builder.AddMessagePackProtocol(options =>
{
options.SerializerOptions = SignalRUnionWorkaroundResolver.OPTIONS;
});
}
else else
{ {
// eventually we will precompile resolvers for messagepack, but this isn't working currently // eventually we will precompile resolvers for messagepack, but this isn't working currently

View File

@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
/// </summary> /// </summary>
[Serializable] [Serializable]
[MessagePackObject] [MessagePackObject]
[Union(0, typeof(TeamVersusRoomState))] [Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
// TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation. // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
public class MatchRoomState public abstract class MatchRoomState
{ {
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using MessagePack; using MessagePack;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Online.Multiplayer namespace osu.Game.Online.Multiplayer
{ {
@ -11,6 +12,7 @@ namespace osu.Game.Online.Multiplayer
/// </summary> /// </summary>
[Serializable] [Serializable]
[MessagePackObject] [MessagePackObject]
[Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
public abstract class MatchUserRequest public abstract class MatchUserRequest
{ {
} }

View File

@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
/// </summary> /// </summary>
[Serializable] [Serializable]
[MessagePackObject] [MessagePackObject]
[Union(0, typeof(TeamVersusUserState))] [Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
// TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation. // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
public class MatchUserState public abstract class MatchUserState
{ {
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Online.Multiplayer
{ {
// Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization.
// More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code.
connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint, false); connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint);
if (connector != null) if (connector != null)
{ {

View File

@ -47,39 +47,13 @@ namespace osu.Game.Online
pollIfNecessary(); pollIfNecessary();
} }
private bool pollIfNecessary() /// <summary>
/// Immediately performs a <see cref="Poll"/>.
/// </summary>
public void PollImmediately()
{ {
// we must be loaded so we have access to clock. lastTimePolled = Time.Current - TimeBetweenPolls.Value;
if (!IsLoaded) return false;
// there's already a poll process running.
if (pollingActive) return false;
// don't try polling if the time between polls hasn't been set.
if (TimeBetweenPolls.Value == 0) return false;
if (!lastTimePolled.HasValue)
{
doPoll();
return true;
}
if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
{
doPoll();
return true;
}
// not enough time has passed since the last poll. we do want to schedule a poll to happen, though.
scheduleNextPoll(); scheduleNextPoll();
return false;
}
private void doPoll()
{
scheduledPoll = null;
pollingActive = true;
Poll().ContinueWith(_ => pollComplete());
} }
/// <summary> /// <summary>
@ -90,13 +64,11 @@ namespace osu.Game.Online
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary> private void doPoll()
/// Immediately performs a <see cref="Poll"/>.
/// </summary>
public void PollImmediately()
{ {
lastTimePolled = Time.Current - TimeBetweenPolls.Value; scheduledPoll = null;
scheduleNextPoll(); pollingActive = true;
Poll().ContinueWith(_ => pollComplete());
} }
/// <summary> /// <summary>
@ -111,6 +83,33 @@ namespace osu.Game.Online
pollIfNecessary(); pollIfNecessary();
} }
private void pollIfNecessary()
{
// we must be loaded so we have access to clock.
if (!IsLoaded) return;
// there's already a poll process running.
if (pollingActive) return;
// don't try polling if the time between polls hasn't been set.
if (TimeBetweenPolls.Value == 0) return;
if (!lastTimePolled.HasValue)
{
doPoll();
return;
}
if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
{
doPoll();
return;
}
// not enough time has passed since the last poll. we do want to schedule a poll to happen, though.
scheduleNextPoll();
}
private void scheduleNextPoll() private void scheduleNextPoll()
{ {
scheduledPoll?.Cancel(); scheduledPoll?.Cancel();

View File

@ -134,7 +134,7 @@ namespace osu.Game.Online.Rooms
/// The position of this <see cref="Room"/> in the list. This is not read from or written to the API. /// The position of this <see cref="Room"/> in the list. This is not read from or written to the API.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public readonly Bindable<int> Position = new Bindable<int>(-1); public readonly Bindable<long> Position = new Bindable<long>(-1); // Todo: This does not need to exist.
public Room() public Room()
{ {

View File

@ -8,6 +8,5 @@ namespace osu.Game.Online.Rooms
// used for osu-web deserialization so names shouldn't be changed. // used for osu-web deserialization so names shouldn't be changed.
Normal, Normal,
Spotlight, Spotlight,
Realtime,
} }
} }

View File

@ -0,0 +1,61 @@
// 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 MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Online
{
/// <summary>
/// Handles SignalR being unable to comprehend [Union] types correctly by redirecting to a known base (union) type.
/// See https://github.com/dotnet/aspnetcore/issues/7298.
/// </summary>
public class SignalRUnionWorkaroundResolver : IFormatterResolver
{
public static readonly MessagePackSerializerOptions OPTIONS =
MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver());
private static readonly Dictionary<Type, IMessagePackFormatter> formatter_map = new Dictionary<Type, IMessagePackFormatter>
{
{ typeof(TeamVersusUserState), new TypeRedirectingFormatter<TeamVersusUserState, MatchUserState>() },
{ typeof(TeamVersusRoomState), new TypeRedirectingFormatter<TeamVersusRoomState, MatchRoomState>() },
{ typeof(ChangeTeamRequest), new TypeRedirectingFormatter<ChangeTeamRequest, MatchUserRequest>() },
// These should not be required. The fallback should work. But something is weird with the way caching is done.
// For future adventurers, I would not advise looking into this further. It's likely not worth the effort.
{ typeof(MatchUserState), new TypeRedirectingFormatter<MatchUserState, MatchUserState>() },
{ typeof(MatchRoomState), new TypeRedirectingFormatter<MatchRoomState, MatchRoomState>() },
{ typeof(MatchUserRequest), new TypeRedirectingFormatter<MatchUserRequest, MatchUserRequest>() },
{ typeof(MatchServerEvent), new TypeRedirectingFormatter<MatchServerEvent, MatchServerEvent>() },
};
public IMessagePackFormatter<T> GetFormatter<T>()
{
if (formatter_map.TryGetValue(typeof(T), out var formatter))
return (IMessagePackFormatter<T>)formatter;
return StandardResolver.Instance.GetFormatter<T>();
}
public class TypeRedirectingFormatter<TActual, TBase> : IMessagePackFormatter<TActual>
{
private readonly IMessagePackFormatter<TBase> baseFormatter;
public TypeRedirectingFormatter()
{
baseFormatter = StandardResolver.Instance.GetFormatter<TBase>();
}
public void Serialize(ref MessagePackWriter writer, TActual value, MessagePackSerializerOptions options) =>
baseFormatter.Serialize(ref writer, (TBase)(object)value, StandardResolver.Options);
public TActual Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) =>
(TActual)(object)baseFormatter.Deserialize(ref reader, StandardResolver.Options);
}
}
}

View File

@ -62,7 +62,7 @@ namespace osu.Game
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic /// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
/// for initial components that are generally retrieved via DI. /// for initial components that are generally retrieved via DI.
/// </summary> /// </summary>
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction> public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo
{ {
/// <summary> /// <summary>
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
@ -1018,10 +1018,13 @@ namespace osu.Game
var horizontalOffset = 0f; var horizontalOffset = 0f;
// Content.ToLocalSpace() is used instead of this.ToLocalSpace() to correctly calculate the offset with scaling modes active.
// Content is a child of a scaling container with ScalingMode.Everything set, while the game itself is never scaled.
// this avoids a visible jump in the positioning of the screen offset container.
if (Settings.IsLoaded && Settings.IsPresent) if (Settings.IsLoaded && Settings.IsPresent)
horizontalOffset += ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO; horizontalOffset += Content.ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
if (Notifications.IsLoaded && Notifications.IsPresent) if (Notifications.IsLoaded && Notifications.IsPresent)
horizontalOffset += (ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO; horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
ScreenOffsetContainer.X = horizontalOffset; ScreenOffsetContainer.X = horizontalOffset;
@ -1085,5 +1088,7 @@ namespace osu.Game
if (newScreen == null) if (newScreen == null)
Exit(); Exit();
} }
IBindable<bool> ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
} }
} }

View File

@ -138,6 +138,10 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
loading = true; loading = true;
LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview => LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview =>
{
// Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution.
// See: https://github.com/ppy/osu-framework/issues/4692
Schedule(() =>
{ {
// beatmapset may have changed. // beatmapset may have changed.
if (Preview != preview) if (Preview != preview)
@ -153,6 +157,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
if (playing.Value) if (playing.Value)
attemptStart(); attemptStart();
}); });
});
} }
else else
{ {

View File

@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Changelog
public class ChangelogSupporterPromo : CompositeDrawable public class ChangelogSupporterPromo : CompositeDrawable
{ {
private const float image_container_width = 164; private const float image_container_width = 164;
private const float heart_size = 75;
private readonly FillFlowContainer textContainer; private readonly FillFlowContainer textContainer;
private readonly Container imageContainer; private readonly Container imageContainer;
@ -134,19 +135,31 @@ namespace osu.Game.Overlays.Changelog
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Margin = new MarginPadding { Bottom = 28 },
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Texture = textures.Get(@"Online/supporter-pippi"), Texture = textures.Get(@"Online/supporter-pippi"),
}, },
new Sprite new Container
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Width = 75, Size = new Vector2(heart_size),
Height = 75,
Margin = new MarginPadding { Top = 70 }, Margin = new MarginPadding { Top = 70 },
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = colour.Pink,
Radius = 10,
Roundness = heart_size / 2,
},
Child = new Sprite
{
Size = new Vector2(heart_size),
Texture = textures.Get(@"Online/supporter-heart"), Texture = textures.Get(@"Online/supporter-heart"),
}, },
},
}; };
} }

View File

@ -110,15 +110,15 @@ namespace osu.Game.Overlays.Chat
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Roundness = 1, Roundness = 1,
Offset = new Vector2(0, 3), Radius = 1,
Radius = 3,
Colour = Color4.Black.Opacity(0.3f), Colour = Color4.Black.Opacity(0.3f),
Offset = new Vector2(0, 1),
Type = EdgeEffectType.Shadow, Type = EdgeEffectType.Shadow,
}, },
Child = new Container Child = new Container
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Y = 3, Y = 0,
Masking = true, Masking = true,
CornerRadius = 4, CornerRadius = 4,
Children = new Drawable[] Children = new Drawable[]

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
@ -38,12 +39,12 @@ namespace osu.Game.Overlays
current.ValueChanged += _ => UpdateState(); current.ValueChanged += _ => UpdateState();
current.DefaultChanged += _ => UpdateState(); current.DefaultChanged += _ => UpdateState();
current.DisabledChanged += _ => UpdateState(); current.DisabledChanged += _ => UpdateState();
if (IsLoaded)
UpdateState(); UpdateState();
} }
} }
private Color4 buttonColour;
private bool hovering; private bool hovering;
public RestoreDefaultValueButton() public RestoreDefaultValueButton()
@ -58,12 +59,11 @@ namespace osu.Game.Overlays
private void load(OsuColour colour) private void load(OsuColour colour)
{ {
BackgroundColour = colour.Yellow; BackgroundColour = colour.Yellow;
buttonColour = colour.Yellow;
Content.Width = 0.33f; Content.Width = 0.33f;
Content.CornerRadius = 3; Content.CornerRadius = 3;
Content.EdgeEffect = new EdgeEffectParameters Content.EdgeEffect = new EdgeEffectParameters
{ {
Colour = buttonColour.Opacity(0.1f), Colour = BackgroundColour.Opacity(0.1f),
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 2, Radius = 2,
}; };
@ -81,7 +81,10 @@ namespace osu.Game.Overlays
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
UpdateState();
// avoid unnecessary transforms on first display.
Alpha = currentAlpha;
Background.Colour = currentColour;
} }
public LocalisableString TooltipText => "revert to default"; public LocalisableString TooltipText => "revert to default";
@ -101,14 +104,16 @@ namespace osu.Game.Overlays
public void UpdateState() => Scheduler.AddOnce(updateState); public void UpdateState() => Scheduler.AddOnce(updateState);
private float currentAlpha => current.IsDefault ? 0f : hovering && !current.Disabled ? 1f : 0.65f;
private ColourInfo currentColour => current.Disabled ? Color4.Gray : BackgroundColour;
private void updateState() private void updateState()
{ {
if (current == null) if (current == null)
return; return;
this.FadeTo(current.IsDefault ? 0f : this.FadeTo(currentAlpha, 200, Easing.OutQuint);
hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); Background.FadeColour(currentColour, 200, Easing.OutQuint);
this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
} }
} }
} }

View File

@ -8,29 +8,39 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio namespace osu.Game.Overlays.Settings.Sections.Audio
{ {
public class AudioDevicesSettings : SettingsSubsection public class AudioDevicesSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Devices"; protected override LocalisableString Header => AudioSettingsStrings.AudioDevicesHeader;
[Resolved] [Resolved]
private AudioManager audio { get; set; } private AudioManager audio { get; set; }
private SettingsDropdown<string> dropdown; private SettingsDropdown<string> dropdown;
protected override void Dispose(bool isDisposing) [BackgroundDependencyLoader]
private void load()
{ {
base.Dispose(isDisposing); Children = new Drawable[]
{
dropdown = new AudioDeviceSettingsDropdown
{
Keywords = new[] { "speaker", "headphone", "output" }
}
};
if (audio != null) updateItems();
{
audio.OnNewDevice -= onDeviceChanged; audio.OnNewDevice += onDeviceChanged;
audio.OnLostDevice -= onDeviceChanged; audio.OnLostDevice += onDeviceChanged;
} dropdown.Current = audio.AudioDevice;
} }
private void onDeviceChanged(string name) => updateItems();
private void updateItems() private void updateItems()
{ {
var deviceItems = new List<string> { string.Empty }; var deviceItems = new List<string> { string.Empty };
@ -49,26 +59,15 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
dropdown.Items = deviceItems.Distinct().ToList(); dropdown.Items = deviceItems.Distinct().ToList();
} }
private void onDeviceChanged(string name) => updateItems(); protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
protected override void LoadComplete() if (audio != null)
{ {
base.LoadComplete(); audio.OnNewDevice -= onDeviceChanged;
audio.OnLostDevice -= onDeviceChanged;
Children = new Drawable[]
{
dropdown = new AudioDeviceSettingsDropdown
{
Keywords = new[] { "speaker", "headphone", "output" }
} }
};
updateItems();
dropdown.Current = audio.AudioDevice;
audio.OnNewDevice += onDeviceChanged;
audio.OnLostDevice += onDeviceChanged;
} }
private class AudioDeviceSettingsDropdown : SettingsDropdown<string> private class AudioDeviceSettingsDropdown : SettingsDropdown<string>
@ -78,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl private class AudioDeviceDropdownControl : DropdownControl
{ {
protected override LocalisableString GenerateItemText(string item) protected override LocalisableString GenerateItemText(string item)
=> string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item); => string.IsNullOrEmpty(item) ? CommonStrings.Default : base.GenerateItemText(item);
} }
} }
} }

View File

@ -1,17 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio namespace osu.Game.Overlays.Settings.Sections.Audio
{ {
public class OffsetSettings : SettingsSubsection public class OffsetSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Offset Adjustment"; protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(new[] { "universal", "uo", "timing" });
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
@ -20,13 +25,13 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{ {
new SettingsSlider<double, OffsetSlider> new SettingsSlider<double, OffsetSlider>
{ {
LabelText = "Audio offset", LabelText = AudioSettingsStrings.AudioOffset,
Current = config.GetBindable<double>(OsuSetting.AudioOffset), Current = config.GetBindable<double>(OsuSetting.AudioOffset),
KeyboardStep = 1f KeyboardStep = 1f
}, },
new SettingsButton new SettingsButton
{ {
Text = "Offset wizard" Text = AudioSettingsStrings.OffsetWizard
} }
}; };
} }

View File

@ -6,12 +6,13 @@ using osu.Framework.Audio;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio namespace osu.Game.Overlays.Settings.Sections.Audio
{ {
public class VolumeSettings : SettingsSubsection public class VolumeSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Volume"; protected override LocalisableString Header => AudioSettingsStrings.VolumeHeader;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config) private void load(AudioManager audio, OsuConfigManager config)
@ -20,28 +21,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{ {
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Master", LabelText = AudioSettingsStrings.MasterVolume,
Current = audio.Volume, Current = audio.Volume,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Master (window inactive)", LabelText = AudioSettingsStrings.MasterVolumeInactive,
Current = config.GetBindable<double>(OsuSetting.VolumeInactive), Current = config.GetBindable<double>(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Effect", LabelText = AudioSettingsStrings.EffectVolume,
Current = audio.VolumeSample, Current = audio.VolumeSample,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Music", LabelText = AudioSettingsStrings.MusicVolume,
Current = audio.VolumeTrack, Current = audio.VolumeTrack,
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true

View File

@ -3,15 +3,17 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Localisation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Overlays.Settings.Sections.Audio;
namespace osu.Game.Overlays.Settings.Sections namespace osu.Game.Overlays.Settings.Sections
{ {
public class AudioSection : SettingsSection public class AudioSection : SettingsSection
{ {
public override string Header => "Audio"; public override LocalisableString Header => AudioSettingsStrings.AudioSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon public override Drawable CreateIcon() => new SpriteIcon
{ {

View File

@ -6,13 +6,14 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Localisation;
using osu.Game.Screens.Import; using osu.Game.Screens.Import;
namespace osu.Game.Overlays.Settings.Sections.Debug namespace osu.Game.Overlays.Settings.Sections.Debug
{ {
public class GeneralSettings : SettingsSubsection public class GeneralSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "General"; protected override LocalisableString Header => DebugSettingsStrings.GeneralHeader;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, OsuGame game) private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, OsuGame game)
@ -21,18 +22,18 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{ {
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show log overlay", LabelText = DebugSettingsStrings.ShowLogOverlay,
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay) Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Bypass front-to-back render pass", LabelText = DebugSettingsStrings.BypassFrontToBackPass,
Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass) Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
} }
}; };
Add(new SettingsButton Add(new SettingsButton
{ {
Text = "Import files", Text = DebugSettingsStrings.ImportFiles,
Action = () => game?.PerformFromScreen(menu => menu.Push(new FileImportScreen())) Action = () => game?.PerformFromScreen(menu => menu.Push(new FileImportScreen()))
}); });
} }

View File

@ -6,12 +6,13 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Debug namespace osu.Game.Overlays.Settings.Sections.Debug
{ {
public class MemorySettings : SettingsSubsection public class MemorySettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Memory"; protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(FrameworkDebugConfigManager config, GameHost host) private void load(FrameworkDebugConfigManager config, GameHost host)
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{ {
new SettingsButton new SettingsButton
{ {
Text = "Clear all caches", Text = DebugSettingsStrings.ClearAllCaches,
Action = host.Collect Action = host.Collect
}, },
}; };

View File

@ -3,13 +3,15 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Debug; using osu.Game.Overlays.Settings.Sections.Debug;
namespace osu.Game.Overlays.Settings.Sections namespace osu.Game.Overlays.Settings.Sections
{ {
public class DebugSection : SettingsSection public class DebugSection : SettingsSection
{ {
public override string Header => "Debug"; public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon public override Drawable CreateIcon() => new SpriteIcon
{ {

View File

@ -6,13 +6,14 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
public class GeneralSettings : SettingsSubsection public class GeneralSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "General"; protected override LocalisableString Header => GameplaySettingsStrings.GeneralHeader;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
@ -21,62 +22,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Background dim", LabelText = GameplaySettingsStrings.BackgroundDim,
Current = config.GetBindable<double>(OsuSetting.DimLevel), Current = config.GetBindable<double>(OsuSetting.DimLevel),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsSlider<double> new SettingsSlider<double>
{ {
LabelText = "Background blur", LabelText = GameplaySettingsStrings.BackgroundBlur,
Current = config.GetBindable<double>(OsuSetting.BlurLevel), Current = config.GetBindable<double>(OsuSetting.BlurLevel),
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
DisplayAsPercentage = true DisplayAsPercentage = true
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Lighten playfield during breaks", LabelText = GameplaySettingsStrings.LightenDuringBreaks,
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks) Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
}, },
new SettingsEnumDropdown<HUDVisibilityMode> new SettingsEnumDropdown<HUDVisibilityMode>
{ {
LabelText = "HUD overlay visibility mode", LabelText = GameplaySettingsStrings.HUDVisibilityMode,
Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode) Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show difficulty graph on progress bar", LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
Current = config.GetBindable<bool>(OsuSetting.ShowDifficultyGraph) Current = config.GetBindable<bool>(OsuSetting.ShowProgressGraph)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Show health display even when you can't fail", LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail), Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" } Keywords = new[] { "hp", "bar" }
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Fade playfield to red when health is low", LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow), Current = config.GetBindable<bool>(OsuSetting.FadePlayfieldWhenHealthLow),
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Always show key overlay", LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay) Current = config.GetBindable<bool>(OsuSetting.KeyOverlay)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Positional hitsounds", LabelText = GameplaySettingsStrings.PositionalHitsounds,
Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds) Current = config.GetBindable<bool>(OsuSetting.PositionalHitSounds)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Always play first combo break sound", LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak) Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
}, },
new SettingsEnumDropdown<ScoringMode> new SettingsEnumDropdown<ScoringMode>
{ {
LabelText = "Score display mode", LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode), Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" } Keywords = new[] { "scoring" }
}, },
@ -86,7 +87,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
Add(new SettingsCheckbox Add(new SettingsCheckbox
{ {
LabelText = "Disable Windows key during gameplay", LabelText = GameplaySettingsStrings.DisableWinKey,
Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey) Current = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey)
}); });
} }

View File

@ -6,12 +6,13 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
public class ModsSettings : SettingsSubsection public class ModsSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Mods"; protected override LocalisableString Header => GameplaySettingsStrings.ModsHeader;
public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(new[] { "mod" }); public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(new[] { "mod" });
@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Increase visibility of first object when visual impairment mods are enabled", LabelText = GameplaySettingsStrings.IncreaseFirstObjectVisibility,
Current = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility), Current = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility),
}, },
}; };

Some files were not shown because too many files have changed in this diff Show More